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 %} + +
{% 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 %} + | +