diff --git a/re2o/templatetags/acl.py b/re2o/templatetags/acl.py index fccbea1d..11399633 100644 --- a/re2o/templatetags/acl.py +++ b/re2o/templatetags/acl.py @@ -129,6 +129,7 @@ MODEL_NAME = { 'Room': topologie.models.Room, 'Building': topologie.models.Building, 'SwitchBay': topologie.models.SwitchBay, + 'PortProfile': topologie.models.PortProfile, # users 'User': users.models.User, 'Adherent': users.models.Adherent, diff --git a/re2o/views.py b/re2o/views.py index 3c5cde09..d803505e 100644 --- a/re2o/views.py +++ b/re2o/views.py @@ -111,6 +111,7 @@ HISTORY_BIND = { 'accesspoint': topologie.models.AccessPoint, 'switchbay': topologie.models.SwitchBay, 'building': topologie.models.Building, + 'portprofile': topologie.models.PortProfile, }, 'machines': { 'machine': machines.models.Machine, diff --git a/topologie/admin.py b/topologie/admin.py index 62ffd6c4..d2a25461 100644 --- a/topologie/admin.py +++ b/topologie/admin.py @@ -38,7 +38,8 @@ from .models import ( ConstructorSwitch, AccessPoint, SwitchBay, - Building + Building, + PortProfile, ) @@ -86,6 +87,9 @@ class BuildingAdmin(VersionAdmin): """Administration d'un batiment""" pass +class PortProfileAdmin(VersionAdmin): + """Administration of a port profile""" + pass admin.site.register(Port, PortAdmin) admin.site.register(AccessPoint, AccessPointAdmin) @@ -96,3 +100,4 @@ admin.site.register(ModelSwitch, ModelSwitchAdmin) admin.site.register(ConstructorSwitch, ConstructorSwitchAdmin) admin.site.register(Building, BuildingAdmin) admin.site.register(SwitchBay, SwitchBayAdmin) +admin.site.register(PortProfile, PortProfileAdmin) diff --git a/topologie/forms.py b/topologie/forms.py index 18831217..377a39b3 100644 --- a/topologie/forms.py +++ b/topologie/forms.py @@ -35,6 +35,7 @@ from __future__ import unicode_literals from django import forms from django.forms import ModelForm from django.db.models import Prefetch +from django.utils.translation import ugettext_lazy as _ from machines.models import Interface from machines.forms import ( @@ -53,6 +54,7 @@ from .models import ( AccessPoint, SwitchBay, Building, + PortProfile, ) @@ -262,3 +264,59 @@ class EditBuildingForm(FormRevMixin, ModelForm): def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(EditBuildingForm, self).__init__(*args, prefix=prefix, **kwargs) + + +class NewPortProfileForm(FormRevMixin, ModelForm): + """Form to create a port profile""" + class Meta: + model = PortProfile + fields = '__all__' + + def __init__(self, *args, **kwargs): + prefix = kwargs.pop('prefix', self.Meta.model.__name__) + super(NewPortProfileForm, self).__init__(*args, + prefix=prefix, + **kwargs) + + def clean(self): + cleaned_data = super(NewPortProfileForm, self).clean() + radius_type = cleaned_data.get('radius_type') + radius_mode = cleaned_data.get('radius_mode') + + if radius_type == 'NO' and radius_mode: + raise forms.ValidationError(_("You can't specify a RADIUS mode" + " with RADIUS type NO")) + elif radius_type != 'NO' and not radius_mode: + raise forms.ValidationError(_("You have to specify a RADIUS" + " mode")) + + return cleaned_data + + +class EditPortProfileForm(FormRevMixin, ModelForm): + """Form to edit a port profile""" + class Meta: + model = PortProfile + fields = '__all__' + + def __init__(self, *args, **kwargs): + prefix = kwargs.pop('prefix', self.Meta.model.__name__) + super(EditPortProfileForm, self).__init__(*args, + prefix=prefix, + **kwargs) + + def clean(self): + cleaned_data = super(EditPortProfileForm, self).clean() + radius_type = cleaned_data.get('radius_type') + radius_mode = cleaned_data.get('radius_mode') + + if radius_type == 'NO' and radius_mode: + raise forms.ValidationError(_("You can't specify a RADIUS mode" + " with RADIUS type NO")) + elif radius_type != 'NO' and not radius_mode: + raise forms.ValidationError(_("You have to specify a RADIUS" + " mode")) + + return cleaned_data + + diff --git a/topologie/migrations/0061_portprofile.py b/topologie/migrations/0061_portprofile.py new file mode 100644 index 00000000..7b6af2ed --- /dev/null +++ b/topologie/migrations/0061_portprofile.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-05-26 22:26 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +import re2o.mixins + + +class Migration(migrations.Migration): + + dependencies = [ + ('machines', '0081_auto_20180521_1413'), + ('topologie', '0060_server'), + ] + + operations = [ + migrations.CreateModel( + name='PortProfile', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('room_default', models.BooleanField()), + ('hotspot_default', models.BooleanField()), + ('uplink_default', models.BooleanField()), + ('orga_machine_default', models.BooleanField()), + ('radius_type', models.CharField(choices=[('NO', 'NO'), ('802.1X', '802.1X'), ('MAC-radius', 'MAC-radius')], max_length=32)), + ('radius_mode', models.CharField(choices=[('STRICT', 'STRICT'), ('COMMON', 'COMMON')], max_length=32)), + ('vlan_tagged', models.ManyToManyField(related_name='vlan_tagged', to='machines.Vlan')), + ('vlan_untagged', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='vlan_untagged', to='machines.Vlan')), + ], + options={ + 'permissions': (('view_port_profile', 'Can view a port profile object'),), + }, + bases=(re2o.mixins.AclMixin, models.Model), + ), + ] diff --git a/topologie/migrations/0062_auto_20180609_1151.py b/topologie/migrations/0062_auto_20180609_1151.py new file mode 100644 index 00000000..3f074f7c --- /dev/null +++ b/topologie/migrations/0062_auto_20180609_1151.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-06-09 16:51 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('topologie', '0061_portprofile'), + ] + + operations = [ + migrations.AlterModelOptions( + name='portprofile', + options={'permissions': (('view_port_profile', 'Can view a port profile object'),), 'verbose_name': 'Port profile', 'verbose_name_plural': 'Port profiles'}, + ), + migrations.AddField( + model_name='portprofile', + name='name', + field=models.CharField(default='Sans nom', max_length=255, verbose_name='Name'), + preserve_default=False, + ), + migrations.AlterField( + model_name='portprofile', + name='hotspot_default', + field=models.BooleanField(verbose_name='Hotspot default'), + ), + migrations.AlterField( + model_name='portprofile', + name='orga_machine_default', + field=models.BooleanField(verbose_name='Organisation machine default'), + ), + migrations.AlterField( + model_name='portprofile', + name='radius_mode', + field=models.CharField(choices=[('STRICT', 'STRICT'), ('COMMON', 'COMMON')], max_length=32, verbose_name='RADIUS mode'), + ), + migrations.AlterField( + model_name='portprofile', + name='radius_type', + field=models.CharField(choices=[('NO', 'NO'), ('802.1X', '802.1X'), ('MAC-radius', 'MAC-radius')], max_length=32, verbose_name='RADIUS type'), + ), + migrations.AlterField( + model_name='portprofile', + name='room_default', + field=models.BooleanField(verbose_name='Room default'), + ), + migrations.AlterField( + model_name='portprofile', + name='uplink_default', + field=models.BooleanField(verbose_name='Uplink default'), + ), + migrations.AlterField( + model_name='portprofile', + name='vlan_tagged', + field=models.ManyToManyField(related_name='vlan_tagged', to='machines.Vlan', verbose_name='VLAN(s) tagged'), + ), + migrations.AlterField( + model_name='portprofile', + name='vlan_untagged', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='vlan_untagged', to='machines.Vlan', verbose_name='VLAN untagged'), + ), + ] diff --git a/topologie/migrations/0063_auto_20180609_1158.py b/topologie/migrations/0063_auto_20180609_1158.py new file mode 100644 index 00000000..59ea8732 --- /dev/null +++ b/topologie/migrations/0063_auto_20180609_1158.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-06-09 16:58 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topologie', '0062_auto_20180609_1151'), + ] + + operations = [ + migrations.AlterField( + model_name='portprofile', + name='vlan_tagged', + field=models.ManyToManyField(blank=True, related_name='vlan_tagged', to='machines.Vlan', verbose_name='VLAN(s) tagged'), + ), + ] diff --git a/topologie/migrations/0064_auto_20180609_1220.py b/topologie/migrations/0064_auto_20180609_1220.py new file mode 100644 index 00000000..f657a612 --- /dev/null +++ b/topologie/migrations/0064_auto_20180609_1220.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-06-09 17:20 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topologie', '0063_auto_20180609_1158'), + ] + + operations = [ + migrations.AlterField( + model_name='portprofile', + name='radius_mode', + field=models.CharField(blank=True, choices=[('STRICT', 'STRICT'), ('COMMON', 'COMMON')], max_length=32, null=True, verbose_name='RADIUS mode'), + ), + ] diff --git a/topologie/models.py b/topologie/models.py index e1c945c7..cdf30787 100644 --- a/topologie/models.py +++ b/topologie/models.py @@ -46,6 +46,7 @@ from django.dispatch import receiver from django.core.exceptions import ValidationError from django.db import IntegrityError from django.db import transaction +from django.utils.translation import ugettext_lazy as _ from reversion import revisions as reversion from machines.models import Machine, regen @@ -488,6 +489,60 @@ class Room(AclMixin, RevMixin, models.Model): return self.name +class PortProfile(AclMixin, models.Model): + """Contains the information of the ports' configuration for a switch""" + TYPES = ( + ('NO', 'NO'), + ('802.1X', '802.1X'), + ('MAC-radius', 'MAC-radius'), + ) + MODES = ( + ('STRICT', 'STRICT'), + ('COMMON', 'COMMON'), + ) + name = models.CharField(max_length=255, verbose_name=_("Name")) + room_default = models.BooleanField(verbose_name=_("Room default")) + hotspot_default = models.BooleanField(_("Hotspot default")) + uplink_default = models.BooleanField(_("Uplink default")) + orga_machine_default = models.BooleanField(_("Organisation machine" + " default")) + vlan_untagged = models.ForeignKey( + 'machines.Vlan', + related_name='vlan_untagged', + on_delete=models.SET_NULL, + blank=True, + null=True, + verbose_name=_("VLAN untagged") + ) + vlan_tagged = models.ManyToManyField( + 'machines.Vlan', + related_name='vlan_tagged', + blank=True, + verbose_name=_("VLAN(s) tagged")) + radius_type = models.CharField( + max_length=32, + choices=TYPES, + verbose_name=_("RADIUS type") + ) + radius_mode = models.CharField( + max_length=32, + choices=MODES, + blank=True, + null=True, + verbose_name=_("RADIUS mode") + ) + + class Meta: + permissions = ( + ("view_port_profile", _("Can view a port profile object")), + ) + verbose_name = _("Port profile") + verbose_name_plural = _("Port profiles") + + def __str__(self): + return self.name + + @receiver(post_save, sender=AccessPoint) def ap_post_save(**_kwargs): """Regeneration des noms des bornes vers le controleur""" diff --git a/topologie/templates/topologie/aff_port_profile.html b/topologie/templates/topologie/aff_port_profile.html new file mode 100644 index 00000000..b4b936c5 --- /dev/null +++ b/topologie/templates/topologie/aff_port_profile.html @@ -0,0 +1,46 @@ +{% load acl %} +{% load i18n %} + +
+ +{% if port_profile_list.paginator %} +{% include "pagination.html" with list=port_profile_list %} +{% endif %} + + + + + + + + + + + + {% for port_profile in port_profile_list %} + + + + + + + + + {% endfor %} +
{% trans "Name" %}{% trans "VLAN untagged" %}{% trans "VLAN(s) tagged" %}{% trans "RADIUS type" %}{% trans "RADIUS mode" %}
{{port_profile.name}}{{port_profile.vlan_untagged}} + {{port_profile.vlan_tagged.all|join:", "}} + {{port_profile.radius_type}}{{port_profile.radius_mode}} + {% include 'buttons/history.html' with href='topologie:history' name='portprofile' id=port_profile.pk %} + {% can_edit port_profile %} + {% include 'buttons/edit.html' with href='topologie:edit-port-profile' id=port_profile.pk %} + {% acl_end %} + {% can_delete port_profile %} + {% include 'buttons/suppr.html' with href='topologie:del-port-profile' id=port_profile.pk %} + {% acl_end %} +
+ +{% if port_profile_list.paginator %} +{% include "pagination.html" with list=port_profile_list %} +{% endif %} + +
diff --git a/topologie/templates/topologie/index.html b/topologie/templates/topologie/index.html index a7c4bb51..7902f4a3 100644 --- a/topologie/templates/topologie/index.html +++ b/topologie/templates/topologie/index.html @@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} {% load acl %} +{% load i18n %} {% block title %}Switchs{% endblock %} @@ -72,5 +73,14 @@ Topologie des Switchs

+

{% trans "Port profiles" %}

+{% can_create PortProfile %} +{% trans " Add a port profile" %} +
+{% acl_end %} + {% include "topologie/aff_port_profile.html" with port_profile_list=port_profile_list %} +
+
+
{% endblock %} diff --git a/topologie/urls.py b/topologie/urls.py index af3327b7..7cfc5714 100644 --- a/topologie/urls.py +++ b/topologie/urls.py @@ -113,4 +113,13 @@ urlpatterns = [ url(r'^del_building/(?P[0-9]+)$', views.del_building, name='del-building'), + url(r'^new_port_profile/$', + views.new_port_profile, + name='new-port-profile'), + url(r'^edit_port_profile/(?P[0-9]+)$', + views.edit_port_profile, + name='edit-port-profile'), + url(r'^del_port_profile/(?P[0-9]+)$', + views.del_port_profile, + name='del-port-profile'), ] diff --git a/topologie/views.py b/topologie/views.py index 3945abc5..eb86ce9c 100644 --- a/topologie/views.py +++ b/topologie/views.py @@ -47,6 +47,7 @@ from django.template.loader import get_template from django.template import Context, Template, loader from django.db.models.signals import post_save from django.dispatch import receiver +from django.utils.translation import ugettext as _ import tempfile @@ -80,6 +81,7 @@ from .models import ( SwitchBay, Building, Server, + PortProfile, ) from .forms import ( EditPortForm, @@ -94,7 +96,9 @@ from .forms import ( AddAccessPointForm, EditAccessPointForm, EditSwitchBayForm, - EditBuildingForm + EditBuildingForm, + NewPortProfileForm, + EditPortProfileForm, ) from subprocess import ( @@ -124,8 +128,12 @@ def index(request): request.GET.get('order'), SortTable.TOPOLOGIE_INDEX ) + + port_profile_list = PortProfile.objects.all() + pagination_number = GeneralOption.get_cached_value('pagination_number') switch_list = re2o_paginator(request, switch_list, pagination_number) + port_profile_list = re2o_paginator(request, port_profile_list, pagination_number) if any(service_link.need_regen() for service_link in Service_link.objects.filter(service__service_type='graph_topo')): make_machine_graph() @@ -137,7 +145,7 @@ def index(request): return render( request, 'topologie/index.html', - {'switch_list': switch_list} + {'switch_list': switch_list, 'port_profile_list': port_profile_list} ) @@ -955,6 +963,59 @@ def del_constructor_switch(request, constructor_switch, **_kwargs): }, 'topologie/delete.html', request) +@login_required +@can_create(PortProfile) +def new_port_profile(request): + """Create a new port profile""" + port_profile = NewPortProfileForm(request.POST or None) + if port_profile.is_valid(): + port_profile.save() + messages.success(request, _("Port profile created")) + return redirect(reverse('topologie:index')) + return form( + {'topoform': port_profile, 'action_name': _("Create")}, + 'topologie/topo.html', + request + ) + + +@login_required +@can_edit(PortProfile) +def edit_port_profile(request, port_profile, **_kwargs): + """Edit a port profile""" + port_profile = EditPortProfileForm(request.POST or None, instance=port_profile) + if port_profile.is_valid(): + if port_profile.changed_data: + port_profile.save() + messages.success(request, _("Port profile modified")) + return redirect(reverse('topologie:index')) + return form( + {'topoform': port_profile, 'action_name': _("Edit")}, + 'topologie/topo.html', + request + ) + + + +@login_required +@can_delete(PortProfile) +def del_port_profile(request, port_profile, **_kwargs): + """Delete a port profile""" + if request.method == 'POST': + try: + port_profile.delete() + messages.success(request, _("The port profile was successfully" + " deleted")) + except ProtectedError: + messages.success(request, _("Impossible to delete the port" + " profile")) + return redirect(reverse('topologie:index')) + return form( + {'objet': port_profile, 'objet_name': _("Port profile")}, + 'topologie/delete.html', + request + ) + def make_machine_graph(): """ Create the graph of switchs, machines and access points.