diff --git a/topologie/forms.py b/topologie/forms.py index fa089507..dc4a5e9b 100644 --- a/topologie/forms.py +++ b/topologie/forms.py @@ -55,6 +55,7 @@ from .models import ( SwitchBay, Building, PortProfile, + ModuleSwitch ) @@ -269,3 +270,23 @@ class EditPortProfileForm(FormRevMixin, ModelForm): prefix=prefix, **kwargs) +class EditModuleForm(FormRevMixin, ModelForm): + """Add and edit module instance""" + class Meta: + model = ModuleSwitch + fields = '__all__' + + def __init__(self, *args, **kwargs): + prefix = kwargs.pop('prefix', self.Meta.model.__name__) + super(EditModuleForm, self).__init__(*args, prefix=prefix, **kwargs) + self.fields['switchs'].queryset = (Switch.objects.filter(model__is_modular=True)) + + def save(self, commit=True): + # TODO : None of the parents of ServiceForm use the commit + # parameter in .save() + instance = super(EditModuleForm, self).save(commit=False) + if commit: + instance.save() + instance.process_link(self.cleaned_data.get('switchs')) + return instance + diff --git a/topologie/migrations/0067_auto_20181230_1556.py b/topologie/migrations/0067_auto_20181230_1556.py new file mode 100644 index 00000000..cd0d368c --- /dev/null +++ b/topologie/migrations/0067_auto_20181230_1556.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-12-30 14:56 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +import re2o.mixins + + +class Migration(migrations.Migration): + + dependencies = [ + ('topologie', '0066_modelswitch_commercial_name'), + ] + + operations = [ + migrations.CreateModel( + name='ModuleOnSwitch', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('slot', models.CharField(help_text='Slot on switch', max_length=15, verbose_name='Slot')), + ], + options={ + 'verbose_name': 'link between switchs and modules', + 'permissions': (('view_moduleonswitch', 'Can view a moduleonswitch object'),), + }, + bases=(re2o.mixins.AclMixin, re2o.mixins.RevMixin, models.Model), + ), + migrations.CreateModel( + name='ModuleSwitch', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('reference', models.CharField(help_text='Reference of a module', max_length=255, verbose_name='Module reference')), + ('comment', models.CharField(blank=True, help_text='Comment', max_length=255, null=True, verbose_name='Comment')), + ('switchs', models.ManyToManyField(through='topologie.ModuleOnSwitch', to='topologie.Switch')), + ], + options={ + 'verbose_name': 'Module of a switch', + 'permissions': (('view_moduleswitch', 'Can view a module object'),), + }, + bases=(re2o.mixins.AclMixin, re2o.mixins.RevMixin, models.Model), + ), + migrations.AddField( + model_name='modelswitch', + name='is_itself_module', + field=models.BooleanField(default=False, help_text='Does the switch, itself, considered as a module'), + ), + migrations.AddField( + model_name='modelswitch', + name='is_modular', + field=models.BooleanField(default=False, help_text='Is this switch model modular'), + ), + migrations.AddField( + model_name='moduleonswitch', + name='module', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='topologie.ModuleSwitch'), + ), + migrations.AddField( + model_name='moduleonswitch', + name='switch', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='topologie.Switch'), + ), + ] diff --git a/topologie/models.py b/topologie/models.py index cd191d7e..29bd3574 100644 --- a/topologie/models.py +++ b/topologie/models.py @@ -251,6 +251,7 @@ class Switch(AclMixin, Machine): default=False, help_text='Provision automatique de ce switch', ) + class Meta: unique_together = ('stack', 'stack_member_id') @@ -402,6 +403,14 @@ class ModelSwitch(AclMixin, RevMixin, models.Model): null=True, blank=True ) + is_modular = models.BooleanField( + default=False, + help_text=_("Is this switch model modular"), + ) + is_itself_module = models.BooleanField( + default=False, + help_text=_("Does the switch, itself, considered as a module"), + ) class Meta: permissions = ( @@ -417,6 +426,61 @@ class ModelSwitch(AclMixin, RevMixin, models.Model): return str(self.constructor) + ' ' + self.reference +class ModuleSwitch(AclMixin, RevMixin, models.Model): + """A module of a switch""" + reference = models.CharField( + max_length=255, + help_text=_("Reference of a module"), + verbose_name=_("Module reference") + ) + comment = models.CharField( + max_length=255, + null=True, + blank=True, + help_text=_("Comment"), + verbose_name=_("Comment") + ) + switchs = models.ManyToManyField('Switch', through='ModuleOnSwitch') + + class Meta: + permissions = ( + ("view_moduleswitch", _("Can view a module object")), + ) + verbose_name = _("Module of a switch") + + def process_link(self, switchs): + """Django can't create itself foreignkey with explicit through""" + ModuleOnSwitch.objects.bulk_create( + [ModuleOnSwitch( + module=self, switch=sw + ) for sw in switchs.exclude( + pk__in=Switch.objects.filter(moduleswitch=self) + )] + ) + ModuleOnSwitch.objects.filter(module=self).exclude(switch__in=switchs).delete() + return + + def __str__(self): + return str(self.reference) + + +class ModuleOnSwitch(AclMixin, RevMixin, models.Model): + """Link beetween module and switch""" + module = models.ForeignKey('ModuleSwitch', on_delete=models.CASCADE) + switch = models.ForeignKey('Switch', on_delete=models.CASCADE) + slot = models.CharField( + max_length=15, + help_text=_("Slot on switch"), + verbose_name=_("Slot") + ) + + class Meta: + permissions = ( + ("view_moduleonswitch", _("Can view a moduleonswitch object")), + ) + verbose_name = _("link between switchs and modules") + + class ConstructorSwitch(AclMixin, RevMixin, models.Model): """Un constructeur de switch""" diff --git a/topologie/templates/topologie/aff_modules.html b/topologie/templates/topologie/aff_modules.html new file mode 100644 index 00000000..8233260c --- /dev/null +++ b/topologie/templates/topologie/aff_modules.html @@ -0,0 +1,67 @@ +{% comment %} +Re2o est un logiciel d'administration développé initiallement au rezometz. Il +se veut agnostique au réseau considéré, de manière à être installable en +quelques clics. + +Copyright © 2017 Gabriel Détraz +Copyright © 2017 Goulven Kermarec +Copyright © 2017 Augustin Lemesle + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +{% endcomment %} + +{% load acl %} +{% load logs_extra %} +{% load i18n %} + +{% if module_list.paginator %} +{% include "pagination.html" with list=module_list %} +{% endif %} + + + + + + + + + + + {% for module in module_list %} + + + + + + + {% endfor %} +
{% trans "Reference" %}{% trans "Comment" %}{% trans "Switchs" %}
{{ module.reference }}{{ module.comment }}{{ module.switchs.all }} + {% can_edit module %} + + + + {% acl_end %} + {% history_button module %} + {% can_delete module %} + + + + {% acl_end %} +
+ +{% if module_list.paginator %} +{% include "pagination.html" with list=module_list %} +{% endif %} + diff --git a/topologie/templates/topologie/index_module.html b/topologie/templates/topologie/index_module.html new file mode 100644 index 00000000..5c4c5c7c --- /dev/null +++ b/topologie/templates/topologie/index_module.html @@ -0,0 +1,43 @@ +{% extends "topologie/sidebar.html" %} +{% comment %} +Re2o est un logiciel d'administration développé initiallement au rezometz. Il +se veut agnostique au réseau considéré, de manière à être installable en +quelques clics. + +Copyright © 2017 Gabriel Détraz +Copyright © 2017 Goulven Kermarec +Copyright © 2017 Augustin Lemesle + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +{% endcomment %} + +{% load bootstrap3 %} +{% load acl %} +{% load i18n %} + +{% block title %}{% trans "Topology" %}{% endblock %} + +{% block content %} +

{% trans "Modules of switchs" %}

+{% can_create ModuleSwitch %} +{% trans " Add a module" %} +
+{% acl_end %} + {% include "topologie/aff_modules.html" with module_list=module_list %} +
+
+
+{% endblock %} + diff --git a/topologie/templates/topologie/sidebar.html b/topologie/templates/topologie/sidebar.html index a35721f9..80317a16 100644 --- a/topologie/templates/topologie/sidebar.html +++ b/topologie/templates/topologie/sidebar.html @@ -33,6 +33,10 @@ with this program; if not, write to the Free Software Foundation, Inc., {% trans "Switches" %} + + + + {% trans "Switches modules" %} diff --git a/topologie/templates/topologie/topo.html b/topologie/templates/topologie/topo.html index bf9f760b..d1ec9bb3 100644 --- a/topologie/templates/topologie/topo.html +++ b/topologie/templates/topologie/topo.html @@ -37,7 +37,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endif %}
{% csrf_token %} - {% massive_bootstrap_form topoform 'room,related,machine_interface,members,vlan_tagged' %} + {% massive_bootstrap_form topoform 'room,related,machine_interface,members,vlan_tagged,switchs' %} {% bootstrap_button action_name icon='ok' button_class='btn-success' %}

diff --git a/topologie/urls.py b/topologie/urls.py index 77d68d50..7af7df9a 100644 --- a/topologie/urls.py +++ b/topologie/urls.py @@ -123,4 +123,10 @@ urlpatterns = [ url(r'^edit_vlanoptions/(?P[0-9]+)$', views.edit_vlanoptions, name='edit-vlanoptions'), - ] + url(r'^add_module/$', views.add_module, name='add-module'), + url(r'^edit_module/(?P[0-9]+)$', + views.edit_module, + name='edit-module'), + url(r'^del_module/(?P[0-9]+)$', views.del_module, name='del-module'), + url(r'^index_module/$', views.index_module, name='index-module'), +] diff --git a/topologie/views.py b/topologie/views.py index a4db2dc6..200f3874 100644 --- a/topologie/views.py +++ b/topologie/views.py @@ -86,6 +86,7 @@ from .models import ( Building, Server, PortProfile, + ModuleSwitch, ) from .forms import ( EditPortForm, @@ -102,6 +103,7 @@ from .forms import ( EditSwitchBayForm, EditBuildingForm, EditPortProfileForm, + EditModuleForm ) from subprocess import ( @@ -316,6 +318,20 @@ def index_model_switch(request): ) +@login_required +@can_view_all(ModuleSwitch) +def index_module(request): + """Display all modules of switchs""" + module_list = ModuleSwitch.objects.all() + pagination_number = GeneralOption.get_cached_value('pagination_number') + module_list = re2o_paginator(request, module_list, pagination_number) + return render( + request, + 'topologie/index_module.html', + {'module_list': module_list} + ) + + @login_required @can_edit(Vlan) def edit_vlanoptions(request, vlan_instance, **_kwargs): @@ -1054,6 +1070,60 @@ def del_port_profile(request, port_profile, **_kwargs): ) +@can_create(ModuleSwitch) +def add_module(request): + """ View used to add a Module object """ + module = EditModuleForm(request.POST or None) + if module.is_valid(): + module.save() + messages.success(request, _("The module was created.")) + return redirect(reverse('topologie:index-module')) + return form( + {'topoform': module, 'action_name': _("Create a module")}, + 'topologie/topo.html', + request + ) + + +@login_required +@can_edit(ModuleSwitch) +def edit_module(request, module_instance, **_kwargs): + """ View used to edit a Module object """ + module = EditModuleForm(request.POST or None, instance=module_instance) + if module.is_valid(): + if module.changed_data: + module.save() + messages.success(request, _("The module was edited.")) + return redirect(reverse('topologie:index-module')) + return form( + {'topoform': module, 'action_name': _("Edit")}, + 'topologie/topo.html', + request + ) + + +@login_required +@can_delete(ModuleSwitch) +def del_module(request, module, **_kwargs): + """Compleete delete a module""" + if request.method == "POST": + try: + module.delete() + messages.success(request, _("The module was deleted.")) + except ProtectedError: + messages.error( + request, + (_("The module %s is used by another object, impossible to" + " deleted it.") % module) + ) + return redirect(reverse('topologie:index-module')) + return form( + {'objet': module, 'objet_name': _("Module")}, + 'topologie/delete.html', + request + ) + + def make_machine_graph(): """ Create the graph of switchs, machines and access points.