From 7ab0d656c937d4ccd88df00ca694479ebc24fb64 Mon Sep 17 00:00:00 2001 From: Laouen Fernet Date: Sun, 27 May 2018 00:36:25 +0200 Subject: [PATCH 001/171] add model PortProfile --- re2o/templatetags/acl.py | 1 + re2o/views.py | 1 + topologie/admin.py | 7 +- topologie/forms.py | 58 ++++++++++++++++ topologie/migrations/0061_portprofile.py | 36 ++++++++++ .../migrations/0062_auto_20180609_1151.py | 66 +++++++++++++++++++ .../migrations/0063_auto_20180609_1158.py | 20 ++++++ .../migrations/0064_auto_20180609_1220.py | 20 ++++++ topologie/models.py | 55 ++++++++++++++++ .../templates/topologie/aff_port_profile.html | 46 +++++++++++++ topologie/templates/topologie/index.html | 10 +++ topologie/urls.py | 9 +++ topologie/views.py | 65 +++++++++++++++++- 13 files changed, 391 insertions(+), 3 deletions(-) create mode 100644 topologie/migrations/0061_portprofile.py create mode 100644 topologie/migrations/0062_auto_20180609_1151.py create mode 100644 topologie/migrations/0063_auto_20180609_1158.py create mode 100644 topologie/migrations/0064_auto_20180609_1220.py create mode 100644 topologie/templates/topologie/aff_port_profile.html 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 652ef475..f1f6a09f 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. From 983b5620aade099f321aab7e4dd4223f5f64f55d Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Tue, 26 Jun 2018 16:49:19 +0000 Subject: [PATCH 002/171] Refactor port_profil --- topologie/migrations/0061_portprofile.py | 30 +++-- .../migrations/0062_auto_20180609_1151.py | 66 ----------- .../migrations/0063_auto_20180609_1158.py | 20 ---- .../migrations/0064_auto_20180609_1220.py | 20 ---- topologie/models.py | 111 ++++++++++++++---- .../templates/topologie/aff_port_profile.html | 32 ++++- 6 files changed, 137 insertions(+), 142 deletions(-) delete mode 100644 topologie/migrations/0062_auto_20180609_1151.py delete mode 100644 topologie/migrations/0063_auto_20180609_1158.py delete mode 100644 topologie/migrations/0064_auto_20180609_1220.py diff --git a/topologie/migrations/0061_portprofile.py b/topologie/migrations/0061_portprofile.py index 7b6af2ed..2b326f28 100644 --- a/topologie/migrations/0061_portprofile.py +++ b/topologie/migrations/0061_portprofile.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.10.7 on 2018-05-26 22:26 +# Generated by Django 1.10.7 on 2018-06-26 16:37 from __future__ import unicode_literals from django.db import migrations, models @@ -10,7 +10,7 @@ import re2o.mixins class Migration(migrations.Migration): dependencies = [ - ('machines', '0081_auto_20180521_1413'), + ('machines', '0082_auto_20180525_2209'), ('topologie', '0060_server'), ] @@ -19,18 +19,26 @@ class Migration(migrations.Migration): 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')), + ('name', models.CharField(max_length=255, verbose_name='Name')), + ('profil_default', models.CharField(blank=True, choices=[('room', 'room'), ('accespoint', 'accesspoint'), ('uplink', 'uplink'), ('asso_machine', 'asso_machine')], max_length=32, null=True, unique=True, verbose_name='profil default')), + ('radius_type', models.CharField(choices=[('NO', 'NO'), ('802.1X', '802.1X'), ('MAC-radius', 'MAC-radius')], max_length=32, verbose_name='RADIUS type')), + ('radius_mode', models.CharField(choices=[('STRICT', 'STRICT'), ('COMMON', 'COMMON')], default='COMMON', max_length=32, verbose_name='RADIUS mode')), + ('speed', models.CharField(choices=[('10-half', '10-half'), ('100-half', '100-half'), ('10-full', '10-full'), ('100-full', '100-full'), ('1000-full', '1000-full'), ('auto', 'auto'), ('auto-10', 'auto-10'), ('auto-100', 'auto-100')], default='auto', help_text='Mode de transmission et vitesse du port', max_length=32, verbose_name='Speed')), + ('mac_limit', models.IntegerField(blank=True, help_text='Limit du nombre de mac sur le port', null=True, verbose_name='Mac limit')), + ('flow_control', models.BooleanField(default=False, help_text='Gestion des débits', verbose_name='Flow control')), + ('dhcp_snooping', models.BooleanField(default=False, help_text='Protection dhcp pirate', verbose_name='Dhcp snooping')), + ('dhcpv6_snooping', models.BooleanField(default=False, help_text='Protection dhcpv6 pirate', verbose_name='Dhcpv6 snooping')), + ('arp_protect', models.BooleanField(default=False, help_text="Verification assignation de l'IP par dhcp", verbose_name='Arp protect')), + ('ra_guard', models.BooleanField(default=False, help_text='Protection contre ra pirate', verbose_name='Ra guard')), + ('loop_protect', models.BooleanField(default=False, help_text='Protection contre les boucles', verbose_name='Loop Protect')), + ('vlan_tagged', models.ManyToManyField(blank=True, null=True, related_name='vlan_tagged', to='machines.Vlan', verbose_name='VLAN(s) tagged')), + ('vlan_untagged', 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')), ], options={ + 'verbose_name': 'Port profile', 'permissions': (('view_port_profile', 'Can view a port profile object'),), + 'verbose_name_plural': 'Port profiles', }, - bases=(re2o.mixins.AclMixin, models.Model), + bases=(re2o.mixins.AclMixin, re2o.mixins.RevMixin, models.Model), ), ] diff --git a/topologie/migrations/0062_auto_20180609_1151.py b/topologie/migrations/0062_auto_20180609_1151.py deleted file mode 100644 index 3f074f7c..00000000 --- a/topologie/migrations/0062_auto_20180609_1151.py +++ /dev/null @@ -1,66 +0,0 @@ -# -*- 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 deleted file mode 100644 index 59ea8732..00000000 --- a/topologie/migrations/0063_auto_20180609_1158.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- 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 deleted file mode 100644 index f657a612..00000000 --- a/topologie/migrations/0064_auto_20180609_1220.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- 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 cdf30787..c7f0e28f 100644 --- a/topologie/models.py +++ b/topologie/models.py @@ -489,7 +489,7 @@ class Room(AclMixin, RevMixin, models.Model): return self.name -class PortProfile(AclMixin, models.Model): +class PortProfile(AclMixin, RevMixin, models.Model): """Contains the information of the ports' configuration for a switch""" TYPES = ( ('NO', 'NO'), @@ -500,37 +500,100 @@ class PortProfile(AclMixin, models.Model): ('STRICT', 'STRICT'), ('COMMON', 'COMMON'), ) + SPEED = ( + ('10-half', '10-half'), + ('100-half', '100-half'), + ('10-full', '10-full'), + ('100-full', '100-full'), + ('1000-full', '1000-full'), + ('auto', 'auto'), + ('auto-10', 'auto-10'), + ('auto-100', 'auto-100'), + ) + PROFIL_DEFAULT= ( + ('room', 'room'), + ('accespoint', 'accesspoint'), + ('uplink', 'uplink'), + ('asso_machine', 'asso_machine'), + ) 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")) + profil_default = models.CharField( + max_length=32, + choices=PROFIL_DEFAULT, + blank=True, + null=True, + unique=True, + verbose_name=_("profil default") + ) vlan_untagged = models.ForeignKey( - 'machines.Vlan', - related_name='vlan_untagged', - on_delete=models.SET_NULL, - blank=True, - null=True, - verbose_name=_("VLAN untagged") + '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")) + 'machines.Vlan', + related_name='vlan_tagged', + blank=True, + null=True, + verbose_name=_("VLAN(s) tagged") + ) radius_type = models.CharField( - max_length=32, - choices=TYPES, - verbose_name=_("RADIUS type") + 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") + max_length=32, + choices=MODES, + default='COMMON', + verbose_name=_("RADIUS mode") ) + speed = models.CharField( + max_length=32, + choices=SPEED, + default='auto', + help_text='Mode de transmission et vitesse du port', + verbose_name=_("Speed") + ) + mac_limit = models.IntegerField( + null=True, + blank=True, + help_text='Limit du nombre de mac sur le port', + verbose_name=_("Mac limit") + ) + flow_control = models.BooleanField( + default=False, + help_text='Gestion des débits', + verbose_name=_("Flow control") + ) + dhcp_snooping = models.BooleanField( + default=False, + help_text='Protection dhcp pirate', + verbose_name=_("Dhcp snooping") + ) + dhcpv6_snooping = models.BooleanField( + default=False, + help_text='Protection dhcpv6 pirate', + verbose_name=_("Dhcpv6 snooping") + ) + arp_protect = models.BooleanField( + default=False, + help_text='Verification assignation de l\'IP par dhcp', + verbose_name=_("Arp protect") + ) + ra_guard = models.BooleanField( + default=False, + help_text='Protection contre ra pirate', + verbose_name=_("Ra guard") + ) + loop_protect = models.BooleanField( + default=False, + help_text='Protection contre les boucles', + verbose_name=_("Loop Protect") + ) class Meta: permissions = ( diff --git a/topologie/templates/topologie/aff_port_profile.html b/topologie/templates/topologie/aff_port_profile.html index b4b936c5..0c1ca622 100644 --- a/topologie/templates/topologie/aff_port_profile.html +++ b/topologie/templates/topologie/aff_port_profile.html @@ -8,13 +8,43 @@ {% endif %} - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% for port_profile in port_profile_list %} From e7b49bd5fa6985423f88549d7724ef273bdda8a1 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Tue, 26 Jun 2018 17:12:52 +0000 Subject: [PATCH 003/171] Pas de null sur manytomany --- topologie/migrations/0061_portprofile.py | 2 +- topologie/models.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/topologie/migrations/0061_portprofile.py b/topologie/migrations/0061_portprofile.py index 2b326f28..d0e84021 100644 --- a/topologie/migrations/0061_portprofile.py +++ b/topologie/migrations/0061_portprofile.py @@ -31,7 +31,7 @@ class Migration(migrations.Migration): ('arp_protect', models.BooleanField(default=False, help_text="Verification assignation de l'IP par dhcp", verbose_name='Arp protect')), ('ra_guard', models.BooleanField(default=False, help_text='Protection contre ra pirate', verbose_name='Ra guard')), ('loop_protect', models.BooleanField(default=False, help_text='Protection contre les boucles', verbose_name='Loop Protect')), - ('vlan_tagged', models.ManyToManyField(blank=True, null=True, related_name='vlan_tagged', to='machines.Vlan', verbose_name='VLAN(s) tagged')), + ('vlan_tagged', models.ManyToManyField(blank=True, related_name='vlan_tagged', to='machines.Vlan', verbose_name='VLAN(s) tagged')), ('vlan_untagged', 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')), ], options={ diff --git a/topologie/models.py b/topologie/models.py index c7f0e28f..86beed71 100644 --- a/topologie/models.py +++ b/topologie/models.py @@ -537,7 +537,6 @@ class PortProfile(AclMixin, RevMixin, models.Model): 'machines.Vlan', related_name='vlan_tagged', blank=True, - null=True, verbose_name=_("VLAN(s) tagged") ) radius_type = models.CharField( From 92f30fbe19cea3d14f5830b68595209949b12f41 Mon Sep 17 00:00:00 2001 From: chirac Date: Tue, 26 Jun 2018 23:29:40 +0000 Subject: [PATCH 004/171] =?UTF-8?q?Ajout=20reglages=20s=C3=A9curit=C3=A9?= =?UTF-8?q?=20+=20frontend?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- topologie/forms.py | 43 -------------- .../migrations/0062_auto_20180627_0123.py | 25 ++++++++ topologie/models.py | 8 +++ .../templates/topologie/aff_port_profile.html | 59 +++++++------------ topologie/views.py | 5 +- 5 files changed, 55 insertions(+), 85 deletions(-) create mode 100644 topologie/migrations/0062_auto_20180627_0123.py diff --git a/topologie/forms.py b/topologie/forms.py index 377a39b3..3ed9e8b3 100644 --- a/topologie/forms.py +++ b/topologie/forms.py @@ -265,34 +265,6 @@ class EditBuildingForm(FormRevMixin, ModelForm): 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: @@ -305,18 +277,3 @@ class EditPortProfileForm(FormRevMixin, ModelForm): 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/0062_auto_20180627_0123.py b/topologie/migrations/0062_auto_20180627_0123.py new file mode 100644 index 00000000..b8135de8 --- /dev/null +++ b/topologie/migrations/0062_auto_20180627_0123.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-06-26 23:23 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topologie', '0061_portprofile'), + ] + + operations = [ + migrations.AlterField( + model_name='portprofile', + name='radius_mode', + field=models.CharField(choices=[('STRICT', 'STRICT'), ('COMMON', 'COMMON')], default='COMMON', help_text="En cas d'auth par mac, auth common ou strcit sur le port", 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')], help_text="Choix du type d'authentification radius : non actif, mac ou 802.1X", max_length=32, verbose_name='RADIUS type'), + ), + ] diff --git a/topologie/models.py b/topologie/models.py index 86beed71..d3400e41 100644 --- a/topologie/models.py +++ b/topologie/models.py @@ -542,12 +542,14 @@ class PortProfile(AclMixin, RevMixin, models.Model): radius_type = models.CharField( max_length=32, choices=TYPES, + help_text="Choix du type d'authentification radius : non actif, mac ou 802.1X", verbose_name=_("RADIUS type") ) radius_mode = models.CharField( max_length=32, choices=MODES, default='COMMON', + help_text="En cas d'auth par mac, auth common ou strcit sur le port", verbose_name=_("RADIUS mode") ) speed = models.CharField( @@ -601,6 +603,12 @@ class PortProfile(AclMixin, RevMixin, models.Model): verbose_name = _("Port profile") verbose_name_plural = _("Port profiles") + security_parameters_fields = ['loop_protect', 'ra_guard', 'arp_protect', 'dhcpv6_snooping', 'dhcp_snooping', 'flow_control'] + + @cached_property + def security_parameters_enabled(self): + return [parameter for parameter in self.security_parameters_fields if getattr(self, parameter)] + def __str__(self): return self.name diff --git a/topologie/templates/topologie/aff_port_profile.html b/topologie/templates/topologie/aff_port_profile.html index 0c1ca622..7e044c0a 100644 --- a/topologie/templates/topologie/aff_port_profile.html +++ b/topologie/templates/topologie/aff_port_profile.html @@ -9,53 +9,34 @@
{% trans "Name" %} {% trans "VLAN untagged" %} {% trans "VLAN(s) tagged" %}
{% trans "RADIUS type" %} {% trans "RADIUS mode" %}{% trans "RADIUS type" %}
{% trans "speed" %}{% trans "Mac limit" %}{% trans "Flow control" %}
{% trans "dhcp snooping" %}{% trans "dhcpv6 snooping" %}{% trans "arp protect" %}
{% trans "ra guard" %}{% trans "loop protect" %}
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + {% for port_profile in port_profile_list %} - + - - + + {% endif %} + + + - - + @@ -66,8 +65,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% acl_end %} {% endif %} - - + - + + @@ -65,6 +66,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% acl_end %} {% endif %} + - + @@ -67,7 +67,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endif %} - + diff --git a/topologie/templates/topologie/aff_port_profile.html b/topologie/templates/topologie/aff_port_profile.html index 7e044c0a..577ac2a5 100644 --- a/topologie/templates/topologie/aff_port_profile.html +++ b/topologie/templates/topologie/aff_port_profile.html @@ -1,3 +1,25 @@ +{% 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 © 2018 Gabriel Détraz + +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 i18n %} @@ -7,45 +29,51 @@ {% include "pagination.html" with list=port_profile_list %} {% endif %} + +
{% trans "Name" %} - {% trans "VLAN untagged" %}{% trans "VLAN(s) tagged" %}
{% trans "RADIUS type" %}{% trans "RADIUS mode" %}{% trans "RADIUS type" %}
{% trans "speed" %}{% trans "Mac limit" %}{% trans "Flow control" %}
{% trans "dhcp snooping" %}{% trans "dhcpv6 snooping" %}{% trans "arp protect" %}
{% trans "ra guard" %}{% trans "loop protect" %}{% trans "Nom" %}{% trans "Default pour" %}{% trans "VLANs" %}{% trans "Réglages RADIUS" %}{% trans "Vitesse" %}{% trans "Mac address limit" %}{% trans "Sécurité" %}
{{port_profile.name}}{{port_profile.vlan_untagged}}{{port_profile.profil_default}} - {{port_profile.vlan_tagged.all|join:", "}} + Untagged : {{port_profile.vlan_untagged}} +
+ Tagged : {{port_profile.vlan_tagged.all|join:", "}}
{{port_profile.radius_type}}{{port_profile.radius_mode}} + Type : {{port_profile.radius_type}} + {% if port_profile.radius_type == "MAC-radius" %} +
+ Mode : {{port_profile.radius_mode}}
{{port_profile.speed}}{{port_profile.mac_limit}}{{port_profile.security_parameters_enabled|join:"
"}}
{% include 'buttons/history.html' with href='topologie:history' name='portprofile' id=port_profile.pk %} {% can_edit port_profile %} diff --git a/topologie/views.py b/topologie/views.py index eb86ce9c..4b21c651 100644 --- a/topologie/views.py +++ b/topologie/views.py @@ -97,7 +97,6 @@ from .forms import ( EditAccessPointForm, EditSwitchBayForm, EditBuildingForm, - NewPortProfileForm, EditPortProfileForm, ) @@ -135,7 +134,7 @@ def index(request): 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')): + if any(service_link.need_regen for service_link in Service_link.objects.filter(service__service_type='graph_topo')): make_machine_graph() for service_link in Service_link.objects.filter(service__service_type='graph_topo'): service_link.done_regen() @@ -967,7 +966,7 @@ def del_constructor_switch(request, constructor_switch, **_kwargs): @can_create(PortProfile) def new_port_profile(request): """Create a new port profile""" - port_profile = NewPortProfileForm(request.POST or None) + port_profile = EditPortProfileForm(request.POST or None) if port_profile.is_valid(): port_profile.save() messages.success(request, _("Port profile created")) From d123ce920d7231f1e3e18a769e9783d2314ec258 Mon Sep 17 00:00:00 2001 From: chirac Date: Wed, 27 Jun 2018 20:28:54 +0000 Subject: [PATCH 005/171] =?UTF-8?q?Menu=20s=C3=A9par=C3=A9=20pour=20les=20?= =?UTF-8?q?profils=20de=20ports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- topologie/templates/topologie/index.html | 10 ----- .../topologie/index_portprofile.html | 44 +++++++++++++++++++ topologie/templates/topologie/sidebar.html | 6 ++- topologie/urls.py | 3 ++ topologie/views.py | 17 +++++-- 5 files changed, 66 insertions(+), 14 deletions(-) create mode 100644 topologie/templates/topologie/index_portprofile.html diff --git a/topologie/templates/topologie/index.html b/topologie/templates/topologie/index.html index 7902f4a3..7949f412 100644 --- a/topologie/templates/topologie/index.html +++ b/topologie/templates/topologie/index.html @@ -73,14 +73,4 @@ 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/templates/topologie/index_portprofile.html b/topologie/templates/topologie/index_portprofile.html new file mode 100644 index 00000000..a4287da6 --- /dev/null +++ b/topologie/templates/topologie/index_portprofile.html @@ -0,0 +1,44 @@ +{% 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 %}Switchs{% endblock %} + +{% block content %} + +

{% 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/templates/topologie/sidebar.html b/topologie/templates/topologie/sidebar.html index ce7b4114..c7ea6337 100644 --- a/topologie/templates/topologie/sidebar.html +++ b/topologie/templates/topologie/sidebar.html @@ -33,7 +33,11 @@ with this program; if not, write to the Free Software Foundation, Inc., Switchs - + + + Profil des ports switchs + + Bornes WiFi diff --git a/topologie/urls.py b/topologie/urls.py index 7cfc5714..a827acf2 100644 --- a/topologie/urls.py +++ b/topologie/urls.py @@ -113,6 +113,9 @@ urlpatterns = [ url(r'^del_building/(?P[0-9]+)$', views.del_building, name='del-building'), + url(r'^index_port_profile/$', + views.index_port_profile, + name='index-port-profile'), url(r'^new_port_profile/$', views.new_port_profile, name='new-port-profile'), diff --git a/topologie/views.py b/topologie/views.py index 4b21c651..b7761e0c 100644 --- a/topologie/views.py +++ b/topologie/views.py @@ -128,11 +128,9 @@ def index(request): 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() @@ -144,7 +142,20 @@ def index(request): return render( request, 'topologie/index.html', - {'switch_list': switch_list, 'port_profile_list': port_profile_list} + {'switch_list': switch_list} + ) + + +@login_required +@can_view_all(PortProfile) +def index_port_profile(request): + pagination_number = GeneralOption.get_cached_value('pagination_number') + port_profile_list = PortProfile.objects.all().select_related('vlan_untagged') + port_profile_list = re2o_paginator(request, port_profile_list, pagination_number) + return render( + request, + 'topologie/index_portprofile.html', + {'port_profile_list': port_profile_list} ) From 447919a2afc0e8aa43621e0ce26914e3705f7444 Mon Sep 17 00:00:00 2001 From: chirac Date: Sat, 30 Jun 2018 15:29:00 +0000 Subject: [PATCH 006/171] =?UTF-8?q?Ajout=20et=20transfert=20des=20ancienne?= =?UTF-8?q?s=20donn=C3=A9es=20vers=20le=20nouveau=20syst=C3=A8me=20de=20pr?= =?UTF-8?q?ofil=20de=20ports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- topologie/forms.py | 7 +-- topologie/migrations/0061_portprofile.py | 2 +- .../migrations/0063_port_custom_profil.py | 21 +++++++ topologie/migrations/0064_createprofil.py | 57 +++++++++++++++++++ .../migrations/0065_auto_20180630_1703.py | 23 ++++++++ topologie/models.py | 32 +++++++---- topologie/templates/topologie/aff_port.html | 6 +- 7 files changed, 129 insertions(+), 19 deletions(-) create mode 100644 topologie/migrations/0063_port_custom_profil.py create mode 100644 topologie/migrations/0064_createprofil.py create mode 100644 topologie/migrations/0065_auto_20180630_1703.py diff --git a/topologie/forms.py b/topologie/forms.py index 3ed9e8b3..6e4d5a0d 100644 --- a/topologie/forms.py +++ b/topologie/forms.py @@ -80,8 +80,8 @@ class EditPortForm(FormRevMixin, ModelForm): optimiser le temps de chargement avec select_related (vraiment lent sans)""" class Meta(PortForm.Meta): - fields = ['room', 'related', 'machine_interface', 'radius', - 'vlan_force', 'details'] + fields = ['room', 'related', 'machine_interface', 'custom_profil', + 'details'] def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) @@ -109,8 +109,7 @@ class AddPortForm(FormRevMixin, ModelForm): 'room', 'machine_interface', 'related', - 'radius', - 'vlan_force', + 'custom_profil', 'details' ] diff --git a/topologie/migrations/0061_portprofile.py b/topologie/migrations/0061_portprofile.py index d0e84021..7e130163 100644 --- a/topologie/migrations/0061_portprofile.py +++ b/topologie/migrations/0061_portprofile.py @@ -20,7 +20,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=255, verbose_name='Name')), - ('profil_default', models.CharField(blank=True, choices=[('room', 'room'), ('accespoint', 'accesspoint'), ('uplink', 'uplink'), ('asso_machine', 'asso_machine')], max_length=32, null=True, unique=True, verbose_name='profil default')), + ('profil_default', models.CharField(blank=True, choices=[('room', 'room'), ('nothing', 'nothing'), ('accespoint', 'accesspoint'), ('uplink', 'uplink'), ('asso_machine', 'asso_machine')], max_length=32, null=True, unique=True, verbose_name='profil default')), ('radius_type', models.CharField(choices=[('NO', 'NO'), ('802.1X', '802.1X'), ('MAC-radius', 'MAC-radius')], max_length=32, verbose_name='RADIUS type')), ('radius_mode', models.CharField(choices=[('STRICT', 'STRICT'), ('COMMON', 'COMMON')], default='COMMON', max_length=32, verbose_name='RADIUS mode')), ('speed', models.CharField(choices=[('10-half', '10-half'), ('100-half', '100-half'), ('10-full', '10-full'), ('100-full', '100-full'), ('1000-full', '1000-full'), ('auto', 'auto'), ('auto-10', 'auto-10'), ('auto-100', 'auto-100')], default='auto', help_text='Mode de transmission et vitesse du port', max_length=32, verbose_name='Speed')), diff --git a/topologie/migrations/0063_port_custom_profil.py b/topologie/migrations/0063_port_custom_profil.py new file mode 100644 index 00000000..15feebce --- /dev/null +++ b/topologie/migrations/0063_port_custom_profil.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-06-28 07:49 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('topologie', '0062_auto_20180627_0123'), + ] + + operations = [ + migrations.AddField( + model_name='port', + name='custom_profil', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='topologie.PortProfile'), + ), + ] diff --git a/topologie/migrations/0064_createprofil.py b/topologie/migrations/0064_createprofil.py new file mode 100644 index 00000000..189d1812 --- /dev/null +++ b/topologie/migrations/0064_createprofil.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-12-31 19:53 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('topologie', '0063_port_custom_profil'), + ] + + def transfer_profil(apps, schema_editor): + db_alias = schema_editor.connection.alias + port = apps.get_model("topologie", "Port") + profil = apps.get_model("topologie", "PortProfile") + vlan = apps.get_model("machines", "Vlan") + port_list = port.objects.using(db_alias).all() + profil_nothing = profil.objects.using(db_alias).create(name='nothing', profil_default='nothing', radius_type='NO') + profil_uplink = profil.objects.using(db_alias).create(name='uplink', profil_default='uplink', radius_type='NO') + profil_machine = profil.objects.using(db_alias).create(name='asso_machine', profil_default='asso_machine', radius_type='NO') + profil_room = profil.objects.using(db_alias).create(name='room', profil_default='room', radius_type='NO') + profil_borne = profil.objects.using(db_alias).create(name='accesspoint', profil_default='accesspoint', radius_type='NO') + for vlan_instance in vlan.objects.using(db_alias).all(): + if port.objects.using(db_alias).filter(vlan_force=vlan_instance): + custom_profil = profil.objects.using(db_alias).create(name='vlan-force-' + str(vlan_instance.vlan_id), radius_type='NO', vlan_untagged=vlan_instance) + port.objects.using(db_alias).filter(vlan_force=vlan_instance).update(custom_profil=custom_profil) + if port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').count() and port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').count(): + profil_room.radius_type = 'MAC-radius' + profil_room.radius_mode = 'STRICT' + common_profil = profil.objects.using(db_alias).create(name='mac-radius-common', radius_type='MAC-radius', radius_mode='COMMON') + no_rad_profil = profil.objects.using(db_alias).create(name='no-radius', radius_type='NO') + port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').update(custom_profil=common_profil) + port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').update(custom_profil=no_rad_profil) + elif port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').count() and port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').count(): + profil_room.radius_type = 'MAC-radius' + profil_room.radius_mode = 'COMMON' + strict_profil = profil.objects.using(db_alias).create(name='mac-radius-strict', radius_type='MAC-radius', radius_mode='STRICT') + no_rad_profil = profil.objects.using(db_alias).create(name='no-radius', radius_type='NO') + port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').update(custom_profil=strict_profil) + port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').update(custom_profil=no_rad_profil) + else: + strict_profil = profil.objects.using(db_alias).create(name='mac-radius-strict', radius_type='MAC-radius', radius_mode='STRICT') + common_profil = profil.objects.using(db_alias).create(name='mac-radius-common', radius_type='MAC-radius', radius_mode='COMMON') + port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').update(custom_profil=strict_profil) + port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').update(custom_profil=common_profil) + profil_room.save() + + + + def untransfer_profil(apps, schema_editor): + return + + operations = [ + migrations.RunPython(transfer_profil, untransfer_profil), + ] diff --git a/topologie/migrations/0065_auto_20180630_1703.py b/topologie/migrations/0065_auto_20180630_1703.py new file mode 100644 index 00000000..9fed2d83 --- /dev/null +++ b/topologie/migrations/0065_auto_20180630_1703.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-06-30 15:03 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('topologie', '0064_createprofil'), + ] + + operations = [ + migrations.RemoveField( + model_name='port', + name='radius', + ), + migrations.RemoveField( + model_name='port', + name='vlan_force', + ), + ] diff --git a/topologie/models.py b/topologie/models.py index d3400e41..82c6833f 100644 --- a/topologie/models.py +++ b/topologie/models.py @@ -366,12 +366,6 @@ class Port(AclMixin, RevMixin, models.Model): de forcer un port sur un vlan particulier. S'additionne à la politique RADIUS""" PRETTY_NAME = "Port de switch" - STATES = ( - ('NO', 'NO'), - ('STRICT', 'STRICT'), - ('BLOQ', 'BLOQ'), - ('COMMON', 'COMMON'), - ) switch = models.ForeignKey( 'Switch', @@ -397,13 +391,13 @@ class Port(AclMixin, RevMixin, models.Model): blank=True, related_name='related_port' ) - radius = models.CharField(max_length=32, choices=STATES, default='NO') - vlan_force = models.ForeignKey( - 'machines.Vlan', - on_delete=models.SET_NULL, + custom_profil = models.ForeignKey( + 'PortProfile', + on_delete=models.PROTECT, blank=True, null=True ) + details = models.CharField(max_length=255, blank=True) class Meta: @@ -412,6 +406,23 @@ class Port(AclMixin, RevMixin, models.Model): ("view_port", "Peut voir un objet port"), ) + @cached_property + def get_port_profil(self): + """Return the config profil for this port""" + if self.custom_profil: + return custom_profil + elif self.related: + return PortProfil.objects.get(profil_default='uplink') + elif self.machine_interface: + if isinstance(self.machine_interface.machine, AccessPoint): + return PortProfil.objects.get(profil_default='access_point') + else: + return PortProfil.objects.get(profil_default='asso_machine') + elif self.room: + return PortProfil.objects.get(profil_default='room') + else: + return PortProfil.objects.get(profil_default='nothing') + @classmethod def get_instance(cls, portid, *_args, **kwargs): return (cls.objects @@ -515,6 +526,7 @@ class PortProfile(AclMixin, RevMixin, models.Model): ('accespoint', 'accesspoint'), ('uplink', 'uplink'), ('asso_machine', 'asso_machine'), + ('nothing', 'nothing'), ) name = models.CharField(max_length=255, verbose_name=_("Name")) profil_default = models.CharField( diff --git a/topologie/templates/topologie/aff_port.html b/topologie/templates/topologie/aff_port.html index deeb0655..f22ce4c9 100644 --- a/topologie/templates/topologie/aff_port.html +++ b/topologie/templates/topologie/aff_port.html @@ -32,8 +32,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% include "buttons/sort.html" with prefix='port' col='room' text='Room' %} {% include "buttons/sort.html" with prefix='port' col='interface' text='Interface machine' %} {% include "buttons/sort.html" with prefix='port' col='related' text='Related' %}{% include "buttons/sort.html" with prefix='port' col='radius' text='Radius' %}{% include "buttons/sort.html" with prefix='port' col='vlan' text='Vlan forcé' %}Profil du port Détails
{{ port.radius }}{% if not port.vlan_force %}Aucun{% else %}{{ port.vlan_force }}{% endif %}{% if not port.custom_profil %}Par défaut{% else %}{{port.custom_profil}}{% endif %} {{ port.details }} From e6b8c5c899277d80972452e9c48d457a5729d716 Mon Sep 17 00:00:00 2001 From: chirac Date: Sat, 30 Jun 2018 16:19:02 +0000 Subject: [PATCH 007/171] =?UTF-8?q?Finition,=20gestion=20du=20renvoie=20du?= =?UTF-8?q?=20profil=20par=20d=C3=A9faut=20du=20port?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- search/views.py | 4 +-- topologie/models.py | 30 ++++++++++++++------- topologie/templates/topologie/aff_port.html | 2 +- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/search/views.py b/search/views.py index 871515fa..0ae8470b 100644 --- a/search/views.py +++ b/search/views.py @@ -262,9 +262,9 @@ def search_single_word(word, filters, user, ) | Q( related__switch__interface__domain__name__icontains=word ) | Q( - radius__icontains=word + custom_profil__name__icontains=word ) | Q( - vlan_force__name__icontains=word + custom_profil__profil_default__icontains=word ) | Q( details__icontains=word ) diff --git a/topologie/models.py b/topologie/models.py index 82c6833f..3d30af76 100644 --- a/topologie/models.py +++ b/topologie/models.py @@ -409,19 +409,29 @@ class Port(AclMixin, RevMixin, models.Model): @cached_property def get_port_profil(self): """Return the config profil for this port""" - if self.custom_profil: - return custom_profil - elif self.related: - return PortProfil.objects.get(profil_default='uplink') - elif self.machine_interface: - if isinstance(self.machine_interface.machine, AccessPoint): - return PortProfil.objects.get(profil_default='access_point') + def profil_or_nothing(profil): + port_profil = PortProfile.objects.filter(profil_default=profil).first() + if port_profil: + return port_profil else: - return PortProfil.objects.get(profil_default='asso_machine') + nothing = PortProfile.objects.filter(profil_default='nothing').first() + if not nothing: + nothing = PortProfile.objects.create(profil_default='nothing', name='nothing', radius_type='NO') + return nothing + + if self.custom_profil: + return self.custom_profil + elif self.related: + return profil_or_nothing('uplink') + elif self.machine_interface: + if hasattr(self.machine_interface.machine, 'accesspoint'): + return profil_or_nothing('access_point') + else: + return profil_or_nothing('asso_machine') elif self.room: - return PortProfil.objects.get(profil_default='room') + return profil_or_nothing('room') else: - return PortProfil.objects.get(profil_default='nothing') + return profil_or_nothing('nothing') @classmethod def get_instance(cls, portid, *_args, **kwargs): diff --git a/topologie/templates/topologie/aff_port.html b/topologie/templates/topologie/aff_port.html index f22ce4c9..f878365a 100644 --- a/topologie/templates/topologie/aff_port.html +++ b/topologie/templates/topologie/aff_port.html @@ -65,7 +65,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% acl_end %} {% endif %} {% if not port.custom_profil %}Par défaut{% else %}{{port.custom_profil}}{% endif %}{% if not port.custom_profil %}Par défaut : {% endif %}{{port.get_port_profil}} {{ port.details }} From b2d45d002118458bc60fc42f16072d735ffa13f3 Mon Sep 17 00:00:00 2001 From: chirac Date: Sat, 30 Jun 2018 16:36:14 +0000 Subject: [PATCH 008/171] =?UTF-8?q?Adapte=20freeradius=20pour=20le=20nouve?= =?UTF-8?q?au=20syst=C3=A8me=20de=20profil=20de=20ports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- freeradius_utils/auth.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/freeradius_utils/auth.py b/freeradius_utils/auth.py index fcf55cfa..4e9ad24e 100644 --- a/freeradius_utils/auth.py +++ b/freeradius_utils/auth.py @@ -359,23 +359,26 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, if not port: return (sw_name, "Chambre inconnue", u'Port inconnu', VLAN_OK) + # On récupère le profil du port + port_profil = port.get_port_profil + # Si un vlan a été précisé, on l'utilise pour VLAN_OK - if port.vlan_force: - DECISION_VLAN = int(port.vlan_force.vlan_id) + if port_profil.vlan_untagged: + DECISION_VLAN = int(port_profil.vlan_untagged.vlan_id) extra_log = u"Force sur vlan " + str(DECISION_VLAN) else: DECISION_VLAN = VLAN_OK - if port.radius == 'NO': + if port_profil.radius_type == 'NO': return (sw_name, "", u"Pas d'authentification sur ce port" + extra_log, DECISION_VLAN) - if port.radius == 'BLOQ': + if port_profil.radius_type == 'BLOQ': return (sw_name, port.room, u'Port desactive', VLAN_NOK) - if port.radius == 'STRICT': + if port_profil.radius_type == 'STRICT': room = port.room if not room: return (sw_name, "Inconnue", u'Chambre inconnue', VLAN_NOK) @@ -390,7 +393,7 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, return (sw_name, room, u'Chambre resident desactive', VLAN_NOK) # else: user OK, on passe à la verif MAC - if port.radius == 'COMMON' or port.radius == 'STRICT': + if port_profil.radius_type == 'COMMON' or port_profil.radius_type == 'STRICT': # Authentification par mac interface = (Interface.objects .filter(mac_address=mac_address) From 51793bdee62ba6fbbf7f5feab6d73386886e5110 Mon Sep 17 00:00:00 2001 From: chirac Date: Sat, 30 Jun 2018 17:04:15 +0000 Subject: [PATCH 009/171] =?UTF-8?q?Boolean=20direct=20pour=20d=C3=A9sactiv?= =?UTF-8?q?er=20un=20port=20+=20logo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- freeradius_utils/auth.py | 6 ++--- topologie/forms.py | 3 ++- .../migrations/0066_auto_20180630_1855.py | 25 +++++++++++++++++++ topologie/models.py | 6 ++++- topologie/templates/topologie/aff_port.html | 2 ++ topologie/templates/topologie/sidebar.html | 4 +-- 6 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 topologie/migrations/0066_auto_20180630_1855.py diff --git a/freeradius_utils/auth.py b/freeradius_utils/auth.py index 4e9ad24e..b318ab63 100644 --- a/freeradius_utils/auth.py +++ b/freeradius_utils/auth.py @@ -369,15 +369,15 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, else: DECISION_VLAN = VLAN_OK + if not port.state: + return (sw_name, port.room, u'Port desactive', VLAN_NOK) + if port_profil.radius_type == 'NO': return (sw_name, "", u"Pas d'authentification sur ce port" + extra_log, DECISION_VLAN) - if port_profil.radius_type == 'BLOQ': - return (sw_name, port.room, u'Port desactive', VLAN_NOK) - if port_profil.radius_type == 'STRICT': room = port.room if not room: diff --git a/topologie/forms.py b/topologie/forms.py index 6e4d5a0d..63ffc652 100644 --- a/topologie/forms.py +++ b/topologie/forms.py @@ -81,7 +81,7 @@ class EditPortForm(FormRevMixin, ModelForm): lent sans)""" class Meta(PortForm.Meta): fields = ['room', 'related', 'machine_interface', 'custom_profil', - 'details'] + 'state', 'details'] def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) @@ -110,6 +110,7 @@ class AddPortForm(FormRevMixin, ModelForm): 'machine_interface', 'related', 'custom_profil', + 'state', 'details' ] diff --git a/topologie/migrations/0066_auto_20180630_1855.py b/topologie/migrations/0066_auto_20180630_1855.py new file mode 100644 index 00000000..b197f568 --- /dev/null +++ b/topologie/migrations/0066_auto_20180630_1855.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-06-30 16:55 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topologie', '0065_auto_20180630_1703'), + ] + + operations = [ + migrations.AddField( + model_name='port', + name='state', + field=models.BooleanField(default=True, help_text='Etat du port Actif', verbose_name='Etat du port Actif'), + ), + migrations.AlterField( + model_name='portprofile', + name='profil_default', + field=models.CharField(blank=True, choices=[('room', 'room'), ('accespoint', 'accesspoint'), ('uplink', 'uplink'), ('asso_machine', 'asso_machine'), ('nothing', 'nothing')], max_length=32, null=True, unique=True, verbose_name='profil default'), + ), + ] diff --git a/topologie/models.py b/topologie/models.py index 3d30af76..d0534dda 100644 --- a/topologie/models.py +++ b/topologie/models.py @@ -397,7 +397,11 @@ class Port(AclMixin, RevMixin, models.Model): blank=True, null=True ) - + state = models.BooleanField( + default=True, + help_text='Etat du port Actif', + verbose_name=_("Etat du port Actif") + ) details = models.CharField(max_length=255, blank=True) class Meta: diff --git a/topologie/templates/topologie/aff_port.html b/topologie/templates/topologie/aff_port.html index f878365a..45e81e06 100644 --- a/topologie/templates/topologie/aff_port.html +++ b/topologie/templates/topologie/aff_port.html @@ -32,6 +32,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% include "buttons/sort.html" with prefix='port' col='room' text='Room' %} {% include "buttons/sort.html" with prefix='port' col='interface' text='Interface machine' %} {% include "buttons/sort.html" with prefix='port' col='related' text='Related' %}Etat du port Profil du port Détails {% if port.state %} Actif{% else %}Désactivé{% endif %} {% if not port.custom_profil %}Par défaut : {% endif %}{{port.get_port_profil}} {{ port.details }} diff --git a/topologie/templates/topologie/sidebar.html b/topologie/templates/topologie/sidebar.html index c7ea6337..8652690e 100644 --- a/topologie/templates/topologie/sidebar.html +++ b/topologie/templates/topologie/sidebar.html @@ -34,8 +34,8 @@ with this program; if not, write to the Free Software Foundation, Inc., Switchs - - Profil des ports switchs + + Config des ports switchs From 2e092b3fde5ce23488f442abf2d84f9bceca1376 Mon Sep 17 00:00:00 2001 From: chirac Date: Sat, 30 Jun 2018 22:17:24 +0000 Subject: [PATCH 010/171] Fix langue et 802.X radius + divers --- freeradius_utils/auth.py | 39 +++++++- search/views.py | 4 +- topologie/forms.py | 6 +- topologie/migrations/0064_createprofil.py | 80 ++++++++-------- .../migrations/0067_auto_20180701_0016.py | 75 +++++++++++++++ topologie/models.py | 37 ++++---- topologie/templates/topologie/aff_port.html | 10 +- .../templates/topologie/aff_port_profile.html | 94 ++++++++++++------- topologie/templates/topologie/index.html | 1 - .../topologie/index_portprofile.html | 13 ++- topologie/templates/topologie/sidebar.html | 2 +- topologie/views.py | 8 +- 12 files changed, 250 insertions(+), 119 deletions(-) create mode 100644 topologie/migrations/0067_auto_20180701_0016.py diff --git a/freeradius_utils/auth.py b/freeradius_utils/auth.py index b318ab63..8450777b 100644 --- a/freeradius_utils/auth.py +++ b/freeradius_utils/auth.py @@ -355,30 +355,47 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, port=port_number ) .first()) + # Si le port est inconnu, on place sur le vlan defaut + # Aucune information particulière ne permet de déterminer quelle + # politique à appliquer sur ce port if not port: return (sw_name, "Chambre inconnue", u'Port inconnu', VLAN_OK) # On récupère le profil du port port_profil = port.get_port_profil - # Si un vlan a été précisé, on l'utilise pour VLAN_OK + # Si un vlan a été précisé dans la config du port, + # on l'utilise pour VLAN_OK if port_profil.vlan_untagged: DECISION_VLAN = int(port_profil.vlan_untagged.vlan_id) extra_log = u"Force sur vlan " + str(DECISION_VLAN) else: DECISION_VLAN = VLAN_OK + # Si le port est désactivé, on rejette sur le vlan de déconnexion if not port.state: - return (sw_name, port.room, u'Port desactive', VLAN_NOK) + return (sw_name, port.room, u'Port desactivé', VLAN_NOK) + # Si radius est désactivé, on laisse passer if port_profil.radius_type == 'NO': return (sw_name, "", u"Pas d'authentification sur ce port" + extra_log, DECISION_VLAN) - if port_profil.radius_type == 'STRICT': + # Si le 802.1X est activé sur ce port, cela veut dire que la personne a été accept précédemment + # Par conséquent, on laisse passer sur le bon vlan + if nas_type.port_access_mode == '802.1X' and port_profil.radius_type == '802.1X': + room = port.room or "Chambre/local inconnu" + return (sw_name, room, u'Acceptation authentification 802.1X', DECISION_VLAN) + + # Sinon, cela veut dire qu'on fait de l'auth radius par mac + # Si le port est en mode strict, on vérifie que tous les users + # rattachés à ce port sont bien à jour de cotisation. Sinon on rejette (anti squattage) + # Il n'est pas possible de se connecter sur une prise strict sans adhérent à jour de cotis + # dedans + if port_profil.radius_mode == 'STRICT': room = port.room if not room: return (sw_name, "Inconnue", u'Chambre inconnue', VLAN_NOK) @@ -393,7 +410,8 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, return (sw_name, room, u'Chambre resident desactive', VLAN_NOK) # else: user OK, on passe à la verif MAC - if port_profil.radius_type == 'COMMON' or port_profil.radius_type == 'STRICT': + # Si on fait de l'auth par mac, on cherche l'interface via sa mac dans la bdd + if port_profil.radius_mode == 'COMMON' or port_profil.radius_mode == 'STRICT': # Authentification par mac interface = (Interface.objects .filter(mac_address=mac_address) @@ -402,15 +420,19 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, .first()) if not interface: room = port.room - # On essaye de register la mac + # On essaye de register la mac, si l'autocapture a été activée + # Sinon on rejette sur vlan_nok if not nas_type.autocapture_mac: return (sw_name, "", u'Machine inconnue', VLAN_NOK) + # On ne peut autocapturer que si on connait la chambre et donc l'user correspondant elif not room: return (sw_name, "Inconnue", u'Chambre et machine inconnues', VLAN_NOK) else: + # Si la chambre est vide (local club, prises en libre services) + # Impossible d'autocapturer if not room_user: room_user = User.objects.filter( Q(club__room=port.room) | Q(adherent__room=port.room) @@ -421,6 +443,8 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, u'Machine et propriétaire de la chambre ' 'inconnus', VLAN_NOK) + # Si il y a plus d'un user dans la chambre, impossible de savoir à qui + # Ajouter la machine elif room_user.count() > 1: return (sw_name, room, @@ -428,11 +452,13 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, 'dans la chambre/local -> ajout de mac ' 'automatique impossible', VLAN_NOK) + # Si l'adhérent de la chambre n'est pas à jour de cotis, pas d'autocapture elif not room_user.first().has_access(): return (sw_name, room, u'Machine inconnue et adhérent non cotisant', VLAN_NOK) + # Sinon on capture et on laisse passer sur le bon vlan else: result, reason = (room_user .first() @@ -452,6 +478,9 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, reason + str(mac_address) ), VLAN_NOK) + # L'interface a été trouvée, on vérifie qu'elle est active, sinon on reject + # Si elle n'a pas d'ipv4, on lui en met une + # Enfin on laisse passer sur le vlan pertinent else: room = port.room if not interface.is_active: diff --git a/search/views.py b/search/views.py index 0ae8470b..73333834 100644 --- a/search/views.py +++ b/search/views.py @@ -262,9 +262,9 @@ def search_single_word(word, filters, user, ) | Q( related__switch__interface__domain__name__icontains=word ) | Q( - custom_profil__name__icontains=word + custom_profile__name__icontains=word ) | Q( - custom_profil__profil_default__icontains=word + custom_profile__profil_default__icontains=word ) | Q( details__icontains=word ) diff --git a/topologie/forms.py b/topologie/forms.py index 63ffc652..ba37d395 100644 --- a/topologie/forms.py +++ b/topologie/forms.py @@ -80,8 +80,8 @@ class EditPortForm(FormRevMixin, ModelForm): optimiser le temps de chargement avec select_related (vraiment lent sans)""" class Meta(PortForm.Meta): - fields = ['room', 'related', 'machine_interface', 'custom_profil', - 'state', 'details'] + fields = ['room', 'related', 'machine_interface', 'custom_profile', + 'state', 'details'] def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) @@ -109,7 +109,7 @@ class AddPortForm(FormRevMixin, ModelForm): 'room', 'machine_interface', 'related', - 'custom_profil', + 'custom_profile', 'state', 'details' ] diff --git a/topologie/migrations/0064_createprofil.py b/topologie/migrations/0064_createprofil.py index 189d1812..2f165386 100644 --- a/topologie/migrations/0064_createprofil.py +++ b/topologie/migrations/0064_createprofil.py @@ -5,53 +5,49 @@ from __future__ import unicode_literals from django.db import migrations +def transfer_profil(apps, schema_editor): + db_alias = schema_editor.connection.alias + port = apps.get_model("topologie", "Port") + profil = apps.get_model("topologie", "PortProfile") + vlan = apps.get_model("machines", "Vlan") + port_list = port.objects.using(db_alias).all() + profil_nothing = profil.objects.using(db_alias).create(name='nothing', profil_default='nothing', radius_type='NO') + profil_uplink = profil.objects.using(db_alias).create(name='uplink', profil_default='uplink', radius_type='NO') + profil_machine = profil.objects.using(db_alias).create(name='asso_machine', profil_default='asso_machine', radius_type='NO') + profil_room = profil.objects.using(db_alias).create(name='room', profil_default='room', radius_type='NO') + profil_borne = profil.objects.using(db_alias).create(name='accesspoint', profil_default='accesspoint', radius_type='NO') + for vlan_instance in vlan.objects.using(db_alias).all(): + if port.objects.using(db_alias).filter(vlan_force=vlan_instance): + custom_profil = profil.objects.using(db_alias).create(name='vlan-force-' + str(vlan_instance.vlan_id), radius_type='NO', vlan_untagged=vlan_instance) + port.objects.using(db_alias).filter(vlan_force=vlan_instance).update(custom_profil=custom_profil) + if port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').count() and port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').count(): + profil_room.radius_type = 'MAC-radius' + profil_room.radius_mode = 'STRICT' + common_profil = profil.objects.using(db_alias).create(name='mac-radius-common', radius_type='MAC-radius', radius_mode='COMMON') + no_rad_profil = profil.objects.using(db_alias).create(name='no-radius', radius_type='NO') + port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').update(custom_profil=common_profil) + port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').update(custom_profil=no_rad_profil) + elif port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').count() and port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').count(): + profil_room.radius_type = 'MAC-radius' + profil_room.radius_mode = 'COMMON' + strict_profil = profil.objects.using(db_alias).create(name='mac-radius-strict', radius_type='MAC-radius', radius_mode='STRICT') + no_rad_profil = profil.objects.using(db_alias).create(name='no-radius', radius_type='NO') + port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').update(custom_profil=strict_profil) + port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').update(custom_profil=no_rad_profil) + else: + strict_profil = profil.objects.using(db_alias).create(name='mac-radius-strict', radius_type='MAC-radius', radius_mode='STRICT') + common_profil = profil.objects.using(db_alias).create(name='mac-radius-common', radius_type='MAC-radius', radius_mode='COMMON') + port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').update(custom_profil=strict_profil) + port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').update(custom_profil=common_profil) + profil_room.save() + + class Migration(migrations.Migration): dependencies = [ ('topologie', '0063_port_custom_profil'), ] - def transfer_profil(apps, schema_editor): - db_alias = schema_editor.connection.alias - port = apps.get_model("topologie", "Port") - profil = apps.get_model("topologie", "PortProfile") - vlan = apps.get_model("machines", "Vlan") - port_list = port.objects.using(db_alias).all() - profil_nothing = profil.objects.using(db_alias).create(name='nothing', profil_default='nothing', radius_type='NO') - profil_uplink = profil.objects.using(db_alias).create(name='uplink', profil_default='uplink', radius_type='NO') - profil_machine = profil.objects.using(db_alias).create(name='asso_machine', profil_default='asso_machine', radius_type='NO') - profil_room = profil.objects.using(db_alias).create(name='room', profil_default='room', radius_type='NO') - profil_borne = profil.objects.using(db_alias).create(name='accesspoint', profil_default='accesspoint', radius_type='NO') - for vlan_instance in vlan.objects.using(db_alias).all(): - if port.objects.using(db_alias).filter(vlan_force=vlan_instance): - custom_profil = profil.objects.using(db_alias).create(name='vlan-force-' + str(vlan_instance.vlan_id), radius_type='NO', vlan_untagged=vlan_instance) - port.objects.using(db_alias).filter(vlan_force=vlan_instance).update(custom_profil=custom_profil) - if port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').count() and port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').count(): - profil_room.radius_type = 'MAC-radius' - profil_room.radius_mode = 'STRICT' - common_profil = profil.objects.using(db_alias).create(name='mac-radius-common', radius_type='MAC-radius', radius_mode='COMMON') - no_rad_profil = profil.objects.using(db_alias).create(name='no-radius', radius_type='NO') - port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').update(custom_profil=common_profil) - port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').update(custom_profil=no_rad_profil) - elif port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').count() and port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').count(): - profil_room.radius_type = 'MAC-radius' - profil_room.radius_mode = 'COMMON' - strict_profil = profil.objects.using(db_alias).create(name='mac-radius-strict', radius_type='MAC-radius', radius_mode='STRICT') - no_rad_profil = profil.objects.using(db_alias).create(name='no-radius', radius_type='NO') - port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').update(custom_profil=strict_profil) - port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').update(custom_profil=no_rad_profil) - else: - strict_profil = profil.objects.using(db_alias).create(name='mac-radius-strict', radius_type='MAC-radius', radius_mode='STRICT') - common_profil = profil.objects.using(db_alias).create(name='mac-radius-common', radius_type='MAC-radius', radius_mode='COMMON') - port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').update(custom_profil=strict_profil) - port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').update(custom_profil=common_profil) - profil_room.save() - - - - def untransfer_profil(apps, schema_editor): - return - operations = [ - migrations.RunPython(transfer_profil, untransfer_profil), + migrations.RunPython(transfer_profil), ] diff --git a/topologie/migrations/0067_auto_20180701_0016.py b/topologie/migrations/0067_auto_20180701_0016.py new file mode 100644 index 00000000..578ee7d6 --- /dev/null +++ b/topologie/migrations/0067_auto_20180701_0016.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-06-30 22:16 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topologie', '0066_auto_20180630_1855'), + ] + + operations = [ + migrations.RenameField( + model_name='port', + old_name='custom_profil', + new_name='custom_profile', + ), + migrations.AlterField( + model_name='port', + name='state', + field=models.BooleanField(default=True, help_text='Port state Active', verbose_name='Port State Active'), + ), + migrations.AlterField( + model_name='portprofile', + name='arp_protect', + field=models.BooleanField(default=False, help_text='Check if ip is dhcp assigned', verbose_name='Arp protect'), + ), + migrations.AlterField( + model_name='portprofile', + name='dhcp_snooping', + field=models.BooleanField(default=False, help_text='Protect against rogue dhcp', verbose_name='Dhcp snooping'), + ), + migrations.AlterField( + model_name='portprofile', + name='dhcpv6_snooping', + field=models.BooleanField(default=False, help_text='Protect against rogue dhcpv6', verbose_name='Dhcpv6 snooping'), + ), + migrations.AlterField( + model_name='portprofile', + name='flow_control', + field=models.BooleanField(default=False, help_text='Flow control', verbose_name='Flow control'), + ), + migrations.AlterField( + model_name='portprofile', + name='loop_protect', + field=models.BooleanField(default=False, help_text='Protect again loop', verbose_name='Loop Protect'), + ), + migrations.AlterField( + model_name='portprofile', + name='mac_limit', + field=models.IntegerField(blank=True, help_text='Limit of mac-address on this port', null=True, verbose_name='Mac limit'), + ), + migrations.AlterField( + model_name='portprofile', + name='ra_guard', + field=models.BooleanField(default=False, help_text='Protect against rogue ra', verbose_name='Ra guard'), + ), + migrations.AlterField( + model_name='portprofile', + name='radius_mode', + field=models.CharField(choices=[('STRICT', 'STRICT'), ('COMMON', 'COMMON')], default='COMMON', help_text='In case of mac-auth : mode common or strict on this port', 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')], help_text='Type of radius auth : inactive, mac-address or 802.1X', max_length=32, verbose_name='RADIUS type'), + ), + migrations.AlterField( + model_name='portprofile', + name='speed', + field=models.CharField(choices=[('10-half', '10-half'), ('100-half', '100-half'), ('10-full', '10-full'), ('100-full', '100-full'), ('1000-full', '1000-full'), ('auto', 'auto'), ('auto-10', 'auto-10'), ('auto-100', 'auto-100')], default='auto', help_text='Port speed limit', max_length=32, verbose_name='Speed'), + ), + ] diff --git a/topologie/models.py b/topologie/models.py index d0534dda..715ccf52 100644 --- a/topologie/models.py +++ b/topologie/models.py @@ -391,7 +391,7 @@ class Port(AclMixin, RevMixin, models.Model): blank=True, related_name='related_port' ) - custom_profil = models.ForeignKey( + custom_profile = models.ForeignKey( 'PortProfile', on_delete=models.PROTECT, blank=True, @@ -399,8 +399,8 @@ class Port(AclMixin, RevMixin, models.Model): ) state = models.BooleanField( default=True, - help_text='Etat du port Actif', - verbose_name=_("Etat du port Actif") + help_text='Port state Active', + verbose_name=_("Port State Active") ) details = models.CharField(max_length=255, blank=True) @@ -412,7 +412,8 @@ class Port(AclMixin, RevMixin, models.Model): @cached_property def get_port_profil(self): - """Return the config profil for this port""" + """Return the config profil for this port + :returns: the profile of self (port)""" def profil_or_nothing(profil): port_profil = PortProfile.objects.filter(profil_default=profil).first() if port_profil: @@ -423,8 +424,8 @@ class Port(AclMixin, RevMixin, models.Model): nothing = PortProfile.objects.create(profil_default='nothing', name='nothing', radius_type='NO') return nothing - if self.custom_profil: - return self.custom_profil + if self.custom_profile: + return self.custom_profile elif self.related: return profil_or_nothing('uplink') elif self.machine_interface: @@ -568,57 +569,57 @@ class PortProfile(AclMixin, RevMixin, models.Model): radius_type = models.CharField( max_length=32, choices=TYPES, - help_text="Choix du type d'authentification radius : non actif, mac ou 802.1X", + help_text="Type of radius auth : inactive, mac-address or 802.1X", verbose_name=_("RADIUS type") ) radius_mode = models.CharField( max_length=32, choices=MODES, default='COMMON', - help_text="En cas d'auth par mac, auth common ou strcit sur le port", + help_text="In case of mac-auth : mode common or strict on this port", verbose_name=_("RADIUS mode") ) speed = models.CharField( max_length=32, choices=SPEED, default='auto', - help_text='Mode de transmission et vitesse du port', + help_text='Port speed limit', verbose_name=_("Speed") ) mac_limit = models.IntegerField( null=True, blank=True, - help_text='Limit du nombre de mac sur le port', + help_text='Limit of mac-address on this port', verbose_name=_("Mac limit") ) flow_control = models.BooleanField( default=False, - help_text='Gestion des débits', + help_text='Flow control', verbose_name=_("Flow control") ) dhcp_snooping = models.BooleanField( default=False, - help_text='Protection dhcp pirate', + help_text='Protect against rogue dhcp', verbose_name=_("Dhcp snooping") ) dhcpv6_snooping = models.BooleanField( default=False, - help_text='Protection dhcpv6 pirate', + help_text='Protect against rogue dhcpv6', verbose_name=_("Dhcpv6 snooping") ) arp_protect = models.BooleanField( default=False, - help_text='Verification assignation de l\'IP par dhcp', + help_text='Check if ip is dhcp assigned', verbose_name=_("Arp protect") ) ra_guard = models.BooleanField( default=False, - help_text='Protection contre ra pirate', + help_text='Protect against rogue ra', verbose_name=_("Ra guard") ) loop_protect = models.BooleanField( default=False, - help_text='Protection contre les boucles', + help_text='Protect again loop', verbose_name=_("Loop Protect") ) @@ -635,6 +636,10 @@ class PortProfile(AclMixin, RevMixin, models.Model): def security_parameters_enabled(self): return [parameter for parameter in self.security_parameters_fields if getattr(self, parameter)] + @cached_property + def security_parameters_as_str(self): + return ','.join(self.security_parameters_enabled) + def __str__(self): return self.name diff --git a/topologie/templates/topologie/aff_port.html b/topologie/templates/topologie/aff_port.html index 45e81e06..86216f15 100644 --- a/topologie/templates/topologie/aff_port.html +++ b/topologie/templates/topologie/aff_port.html @@ -32,7 +32,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% include "buttons/sort.html" with prefix='port' col='room' text='Room' %} {% include "buttons/sort.html" with prefix='port' col='interface' text='Interface machine' %} {% include "buttons/sort.html" with prefix='port' col='related' text='Related' %}Etat du portEtat du port Profil du port Détails {% if port.state %} Actif{% else %}Désactivé{% endif %}{% if not port.custom_profil %}Par défaut : {% endif %}{{port.get_port_profil}}{% if not port.custom_profile %}Par défaut : {% endif %}{{port.get_port_profil}} {{ port.details }} @@ -77,10 +77,10 @@ with this program; if not, write to the Free Software Foundation, Inc., - {% acl_end %} + {% acl_end %} {% can_delete port %} - - + + {% acl_end %}
- - - - - - - - - - + + + + + + + + + + {% for port_profile in port_profile_list %} - - - {% endif %} - - - - + + + {% endif %} + + + + {% endfor %}
{% trans "Nom" %}{% trans "Default pour" %}{% trans "VLANs" %}{% trans "Réglages RADIUS" %}{% trans "Vitesse" %}{% trans "Mac address limit" %}{% trans "Sécurité" %}
{% trans "Name" %}{% trans "Default for" %}{% trans "VLANs" %}{% trans "RADIUS settings" %}{% trans "Speed" %}{% trans "Mac address limit" %}{% trans "Security" %}
{{port_profile.name}} {{port_profile.profil_default}} - Untagged : {{port_profile.vlan_untagged}} -
- Tagged : {{port_profile.vlan_tagged.all|join:", "}} -
- Type : {{port_profile.radius_type}} - {% if port_profile.radius_type == "MAC-radius" %} -
- Mode : {{port_profile.radius_mode}}
{{port_profile.speed}}{{port_profile.mac_limit}}{{port_profile.security_parameters_enabled|join:"
"}}
- {% 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.vlan_untagged %} + Untagged : {{port_profile.vlan_untagged}} +
+ {% endif %} + {% if port_profile.vlan_untagged %} + Tagged : {{port_profile.vlan_tagged.all|join:", "}} + {% endif %} +
+ Type : {{port_profile.radius_type}} + {% if port_profile.radius_type == "MAC-radius" %} +
+ Mode : {{port_profile.radius_mode}}
{{port_profile.speed}}{{port_profile.mac_limit}}{{port_profile.security_parameters_enabled|join:"
"}}
+ {% 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 %} +
diff --git a/topologie/templates/topologie/index.html b/topologie/templates/topologie/index.html index 7949f412..f596e6a5 100644 --- a/topologie/templates/topologie/index.html +++ b/topologie/templates/topologie/index.html @@ -25,7 +25,6 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} {% load acl %} -{% load i18n %} {% block title %}Switchs{% endblock %} diff --git a/topologie/templates/topologie/index_portprofile.html b/topologie/templates/topologie/index_portprofile.html index a4287da6..f95415c8 100644 --- a/topologie/templates/topologie/index_portprofile.html +++ b/topologie/templates/topologie/index_portprofile.html @@ -4,9 +4,8 @@ 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 +Copyright © 2018 Gabriel Détraz + 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 @@ -36,9 +35,9 @@ with this program; if not, write to the Free Software Foundation, Inc., {% trans " Add a port profile" %}
{% acl_end %} - {% include "topologie/aff_port_profile.html" with port_profile_list=port_profile_list %} -
-
-
+{% include "topologie/aff_port_profile.html" with port_profile_list=port_profile_list %} +
+
+
{% endblock %} diff --git a/topologie/templates/topologie/sidebar.html b/topologie/templates/topologie/sidebar.html index 8652690e..04ee5202 100644 --- a/topologie/templates/topologie/sidebar.html +++ b/topologie/templates/topologie/sidebar.html @@ -33,7 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc., Switchs - + Config des ports switchs diff --git a/topologie/views.py b/topologie/views.py index b7761e0c..e5453982 100644 --- a/topologie/views.py +++ b/topologie/views.py @@ -1014,11 +1014,11 @@ def del_port_profile(request, port_profile, **_kwargs): if request.method == 'POST': try: port_profile.delete() - messages.success(request, _("The port profile was successfully" - " deleted")) + messages.success(request, + _("The port profile was successfully deleted")) except ProtectedError: - messages.success(request, _("Impossible to delete the port" - " profile")) + messages.success(request, + _("Impossible to delete the port profile")) return redirect(reverse('topologie:index')) return form( {'objet': port_profile, 'objet_name': _("Port profile")}, From a07e0d922acd2a0f4cd511b1dfd708844ceb6772 Mon Sep 17 00:00:00 2001 From: chirac Date: Sat, 30 Jun 2018 22:23:00 +0000 Subject: [PATCH 011/171] Petit bug affichage vlans --- topologie/templates/topologie/aff_port_profile.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topologie/templates/topologie/aff_port_profile.html b/topologie/templates/topologie/aff_port_profile.html index 577ac2a5..12c8b365 100644 --- a/topologie/templates/topologie/aff_port_profile.html +++ b/topologie/templates/topologie/aff_port_profile.html @@ -52,7 +52,7 @@ with this program; if not, write to the Free Software Foundation, Inc., Untagged : {{port_profile.vlan_untagged}}
{% endif %} - {% if port_profile.vlan_untagged %} + {% if port_profile.vlan_tagged.all %} Tagged : {{port_profile.vlan_tagged.all|join:", "}} {% endif %} From eefa0b4add2f4ecc1fa8ef48cb3a28ef6d7ac183 Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Wed, 11 Jul 2018 19:37:22 +0200 Subject: [PATCH 012/171] PEP8 mon amour + typos --- freeradius_utils/auth.py | 18 +-- topologie/models.py | 115 ++++++++----- .../templates/topologie/aff_port_profile.html | 4 +- topologie/views.py | 153 ++++++++++-------- 4 files changed, 172 insertions(+), 118 deletions(-) diff --git a/freeradius_utils/auth.py b/freeradius_utils/auth.py index 8450777b..d743495f 100644 --- a/freeradius_utils/auth.py +++ b/freeradius_utils/auth.py @@ -355,7 +355,7 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, port=port_number ) .first()) - + # Si le port est inconnu, on place sur le vlan defaut # Aucune information particulière ne permet de déterminer quelle # politique à appliquer sur ce port @@ -363,12 +363,12 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, return (sw_name, "Chambre inconnue", u'Port inconnu', VLAN_OK) # On récupère le profil du port - port_profil = port.get_port_profil + port_profile = port.get_port_profile - # Si un vlan a été précisé dans la config du port, + # Si un vlan a été précisé dans la config du port, # on l'utilise pour VLAN_OK - if port_profil.vlan_untagged: - DECISION_VLAN = int(port_profil.vlan_untagged.vlan_id) + if port_profile.vlan_untagged: + DECISION_VLAN = int(port_profile.vlan_untagged.vlan_id) extra_log = u"Force sur vlan " + str(DECISION_VLAN) else: DECISION_VLAN = VLAN_OK @@ -378,7 +378,7 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, return (sw_name, port.room, u'Port desactivé', VLAN_NOK) # Si radius est désactivé, on laisse passer - if port_profil.radius_type == 'NO': + if port_profile.radius_type == 'NO': return (sw_name, "", u"Pas d'authentification sur ce port" + extra_log, @@ -386,7 +386,7 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, # Si le 802.1X est activé sur ce port, cela veut dire que la personne a été accept précédemment # Par conséquent, on laisse passer sur le bon vlan - if nas_type.port_access_mode == '802.1X' and port_profil.radius_type == '802.1X': + if nas_type.port_access_mode == '802.1X' and port_profile.radius_type == '802.1X': room = port.room or "Chambre/local inconnu" return (sw_name, room, u'Acceptation authentification 802.1X', DECISION_VLAN) @@ -395,7 +395,7 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, # rattachés à ce port sont bien à jour de cotisation. Sinon on rejette (anti squattage) # Il n'est pas possible de se connecter sur une prise strict sans adhérent à jour de cotis # dedans - if port_profil.radius_mode == 'STRICT': + if port_profile.radius_mode == 'STRICT': room = port.room if not room: return (sw_name, "Inconnue", u'Chambre inconnue', VLAN_NOK) @@ -411,7 +411,7 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, # else: user OK, on passe à la verif MAC # Si on fait de l'auth par mac, on cherche l'interface via sa mac dans la bdd - if port_profil.radius_mode == 'COMMON' or port_profil.radius_mode == 'STRICT': + if port_profile.radius_mode == 'COMMON' or port_profile.radius_mode == 'STRICT': # Authentification par mac interface = (Interface.objects .filter(mac_address=mac_address) diff --git a/topologie/models.py b/topologie/models.py index 715ccf52..bbcab749 100644 --- a/topologie/models.py +++ b/topologie/models.py @@ -40,7 +40,7 @@ from __future__ import unicode_literals import itertools from django.db import models -from django.db.models.signals import pre_save, post_save, post_delete +from django.db.models.signals import post_save, post_delete from django.utils.functional import cached_property from django.dispatch import receiver from django.core.exceptions import ValidationError @@ -52,11 +52,6 @@ from reversion import revisions as reversion from machines.models import Machine, regen from re2o.mixins import AclMixin, RevMixin -from os.path import isfile -from os import remove - - - class Stack(AclMixin, RevMixin, models.Model): """Un objet stack. Regrouppe des switchs en foreign key @@ -123,7 +118,10 @@ class AccessPoint(AclMixin, Machine): ) def building(self): - """Return the building of the AP/Server (building of the switchs connected to...)""" + """ + Return the building of the AP/Server (building of the switchs + connected to...) + """ return Building.objects.filter( switchbay__switch=self.switch() ) @@ -135,14 +133,18 @@ class AccessPoint(AclMixin, Machine): @classmethod def all_ap_in(cls, building_instance): """Get a building as argument, returns all ap of a building""" - return cls.objects.filter(interface__port__switch__switchbay__building=building_instance) + return cls.objects.filter( + interface__port__switch__switchbay__building=building_instance + ) def __str__(self): return str(self.interface_set.first()) class Server(Machine): - """Dummy class, to retrieve servers of a building, or get switch of a server""" + """ + Dummy class, to retrieve servers of a building, or get switch of a server + """ class Meta: proxy = True @@ -160,7 +162,10 @@ class Server(Machine): ) def building(self): - """Return the building of the AP/Server (building of the switchs connected to...)""" + """ + Return the building of the AP/Server + (building of the switchs connected to...) + """ return Building.objects.filter( switchbay__switch=self.switch() ) @@ -172,7 +177,9 @@ class Server(Machine): @classmethod def all_server_in(cls, building_instance): """Get a building as argument, returns all server of a building""" - return cls.objects.filter(interface__port__switch__switchbay__building=building_instance).exclude(accesspoint__isnull=False) + return cls.objects.filter( + interface__port__switch__switchbay__building=building_instance + ).exclude(accesspoint__isnull=False) def __str__(self): return str(self.interface_set.first()) @@ -200,7 +207,7 @@ class Switch(AclMixin, Machine): blank=True, null=True, on_delete=models.SET_NULL - ) + ) stack_member_id = models.PositiveIntegerField( blank=True, null=True, @@ -238,7 +245,7 @@ class Switch(AclMixin, Machine): raise ValidationError( {'stack_member_id': "L'id de ce switch est en\ dehors des bornes permises pas la stack"} - ) + ) else: raise ValidationError({'stack_member_id': "L'id dans la stack\ ne peut être nul"}) @@ -378,25 +385,25 @@ class Port(AclMixin, RevMixin, models.Model): on_delete=models.PROTECT, blank=True, null=True - ) + ) machine_interface = models.ForeignKey( 'machines.Interface', on_delete=models.SET_NULL, blank=True, null=True - ) + ) related = models.OneToOneField( 'self', null=True, blank=True, related_name='related_port' - ) + ) custom_profile = models.ForeignKey( 'PortProfile', on_delete=models.PROTECT, blank=True, null=True - ) + ) state = models.BooleanField( default=True, help_text='Port state Active', @@ -411,32 +418,35 @@ class Port(AclMixin, RevMixin, models.Model): ) @cached_property - def get_port_profil(self): - """Return the config profil for this port + def get_port_profile(self): + """Return the config profile for this port :returns: the profile of self (port)""" - def profil_or_nothing(profil): - port_profil = PortProfile.objects.filter(profil_default=profil).first() - if port_profil: - return port_profil + def profile_or_nothing(profile): + port_profile = PortProfile.objects.filter( + profile_default=profile).first() + if port_profile: + return port_profile else: - nothing = PortProfile.objects.filter(profil_default='nothing').first() - if not nothing: - nothing = PortProfile.objects.create(profil_default='nothing', name='nothing', radius_type='NO') - return nothing + nothing_profile, _created = PortProfile.objects.get_or_create( + profile_default='nothing', + name='nothing', + radius_type='NO' + ) + return nothing_profile if self.custom_profile: return self.custom_profile elif self.related: - return profil_or_nothing('uplink') + return profile_or_nothing('uplink') elif self.machine_interface: if hasattr(self.machine_interface.machine, 'accesspoint'): - return profil_or_nothing('access_point') + return profile_or_nothing('access_point') else: - return profil_or_nothing('asso_machine') + return profile_or_nothing('asso_machine') elif self.room: - return profil_or_nothing('room') + return profile_or_nothing('room') else: - return profil_or_nothing('nothing') + return profile_or_nothing('nothing') @classmethod def get_instance(cls, portid, *_args, **kwargs): @@ -521,11 +531,11 @@ class PortProfile(AclMixin, RevMixin, models.Model): ('NO', 'NO'), ('802.1X', '802.1X'), ('MAC-radius', 'MAC-radius'), - ) + ) MODES = ( ('STRICT', 'STRICT'), ('COMMON', 'COMMON'), - ) + ) SPEED = ( ('10-half', '10-half'), ('100-half', '100-half'), @@ -535,14 +545,14 @@ class PortProfile(AclMixin, RevMixin, models.Model): ('auto', 'auto'), ('auto-10', 'auto-10'), ('auto-100', 'auto-100'), - ) - PROFIL_DEFAULT= ( + ) + PROFIL_DEFAULT = ( ('room', 'room'), ('accespoint', 'accesspoint'), ('uplink', 'uplink'), ('asso_machine', 'asso_machine'), ('nothing', 'nothing'), - ) + ) name = models.CharField(max_length=255, verbose_name=_("Name")) profil_default = models.CharField( max_length=32, @@ -616,25 +626,36 @@ class PortProfile(AclMixin, RevMixin, models.Model): default=False, help_text='Protect against rogue ra', verbose_name=_("Ra guard") - ) + ) loop_protect = models.BooleanField( default=False, help_text='Protect again loop', verbose_name=_("Loop Protect") - ) + ) class Meta: permissions = ( - ("view_port_profile", _("Can view a port profile object")), + ("view_port_profile", _("Can view a port profile object")), ) verbose_name = _("Port profile") verbose_name_plural = _("Port profiles") - security_parameters_fields = ['loop_protect', 'ra_guard', 'arp_protect', 'dhcpv6_snooping', 'dhcp_snooping', 'flow_control'] + security_parameters_fields = [ + 'loop_protect', + 'ra_guard', + 'arp_protect', + 'dhcpv6_snooping', + 'dhcp_snooping', + 'flow_control' + ] @cached_property def security_parameters_enabled(self): - return [parameter for parameter in self.security_parameters_fields if getattr(self, parameter)] + return [ + parameter + for parameter in self.security_parameters_fields + if getattr(self, parameter) + ] @cached_property def security_parameters_as_str(self): @@ -650,45 +671,55 @@ def ap_post_save(**_kwargs): regen('unifi-ap-names') regen("graph_topo") + @receiver(post_delete, sender=AccessPoint) def ap_post_delete(**_kwargs): """Regeneration des noms des bornes vers le controleur""" regen('unifi-ap-names') regen("graph_topo") + @receiver(post_delete, sender=Stack) def stack_post_delete(**_kwargs): """Vide les id des switches membres d'une stack supprimée""" Switch.objects.filter(stack=None).update(stack_member_id=None) + @receiver(post_save, sender=Port) def port_post_save(**_kwargs): regen("graph_topo") + @receiver(post_delete, sender=Port) def port_post_delete(**_kwargs): regen("graph_topo") + @receiver(post_save, sender=ModelSwitch) def modelswitch_post_save(**_kwargs): regen("graph_topo") + @receiver(post_delete, sender=ModelSwitch) def modelswitch_post_delete(**_kwargs): regen("graph_topo") + @receiver(post_save, sender=Building) def building_post_save(**_kwargs): regen("graph_topo") + @receiver(post_delete, sender=Building) def building_post_delete(**_kwargs): regen("graph_topo") + @receiver(post_save, sender=Switch) def switch_post_save(**_kwargs): regen("graph_topo") + @receiver(post_delete, sender=Switch) def switch_post_delete(**_kwargs): regen("graph_topo") diff --git a/topologie/templates/topologie/aff_port_profile.html b/topologie/templates/topologie/aff_port_profile.html index 12c8b365..65446236 100644 --- a/topologie/templates/topologie/aff_port_profile.html +++ b/topologie/templates/topologie/aff_port_profile.html @@ -29,9 +29,9 @@ with this program; if not, write to the Free Software Foundation, Inc., {% include "pagination.html" with list=port_profile_list %} {% endif %} - + @@ -49,7 +49,7 @@ with this program; if not, write to the Free Software Foundation, Inc., + +
{% trans "Name" %} {% trans "Default for" %}{{port_profile.profil_default}} {% if port_profile.vlan_untagged %} - Untagged : {{port_profile.vlan_untagged}} + Untagged : {{port_profile.vlan_untagged}}
{% endif %} {% if port_profile.vlan_tagged.all %} diff --git a/topologie/views.py b/topologie/views.py index e5453982..a6c6ab69 100644 --- a/topologie/views.py +++ b/topologie/views.py @@ -42,11 +42,7 @@ from django.contrib.auth.decorators import login_required from django.db import IntegrityError from django.db.models import ProtectedError, Prefetch from django.core.exceptions import ValidationError -from django.contrib.staticfiles.storage import staticfiles_storage -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 @@ -105,8 +101,7 @@ from subprocess import ( PIPE ) -from os.path import isfile -from os import remove +from os.path import isfile @login_required @@ -128,13 +123,17 @@ def index(request): SortTable.TOPOLOGIE_INDEX ) - pagination_number = GeneralOption.get_cached_value('pagination_number') switch_list = re2o_paginator(request, switch_list, pagination_number) - if any(service_link.need_regen for service_link in Service_link.objects.filter(service__service_type='graph_topo')): + if any( + service_link.need_regen + for service_link in Service_link.objects.filter( + service__service_type='graph_topo') + ): make_machine_graph() - for service_link in Service_link.objects.filter(service__service_type='graph_topo'): + for service_link in Service_link.objects.filter( + service__service_type='graph_topo'): service_link.done_regen() if not isfile("/var/www/re2o/media/images/switchs.png"): @@ -150,8 +149,10 @@ def index(request): @can_view_all(PortProfile) def index_port_profile(request): pagination_number = GeneralOption.get_cached_value('pagination_number') - port_profile_list = PortProfile.objects.all().select_related('vlan_untagged') - port_profile_list = re2o_paginator(request, port_profile_list, pagination_number) + port_profile_list = PortProfile.objects.all().select_related( + 'vlan_untagged') + port_profile_list = re2o_paginator( + request, port_profile_list, pagination_number) return render( request, 'topologie/index_portprofile.html', @@ -460,7 +461,7 @@ def new_switch(request): ) domain = DomainForm( request.POST or None, - ) + ) if switch.is_valid() and interface.is_valid(): user = AssoOption.get_cached_value('utilisateur_asso') if not user: @@ -530,7 +531,7 @@ def create_ports(request, switchid): return redirect(reverse( 'topologie:index-port', kwargs={'switchid': switchid} - )) + )) return form( {'id_switch': switchid, 'topoform': port_form}, 'topologie/switch.html', @@ -548,16 +549,16 @@ def edit_switch(request, switch, switchid): request.POST or None, instance=switch, user=request.user - ) + ) interface_form = EditInterfaceForm( request.POST or None, instance=switch.interface_set.first(), user=request.user - ) + ) domain_form = DomainForm( request.POST or None, instance=switch.interface_set.first().domain - ) + ) if switch_form.is_valid() and interface_form.is_valid(): new_switch_obj = switch_form.save(commit=False) new_interface_obj = interface_form.save(commit=False) @@ -601,7 +602,7 @@ def new_ap(request): ) domain = DomainForm( request.POST or None, - ) + ) if ap.is_valid() and interface.is_valid(): user = AssoOption.get_cached_value('utilisateur_asso') if not user: @@ -656,7 +657,7 @@ def edit_ap(request, ap, **_kwargs): domain_form = DomainForm( request.POST or None, instance=ap.interface_set.first().domain - ) + ) if ap_form.is_valid() and interface_form.is_valid(): user = AssoOption.get_cached_value('utilisateur_asso') if not user: @@ -970,7 +971,7 @@ def del_constructor_switch(request, constructor_switch, **_kwargs): return form({ 'objet': constructor_switch, 'objet_name': 'Constructeur de switch' - }, 'topologie/delete.html', request) + }, 'topologie/delete.html', request) @login_required @@ -993,7 +994,8 @@ def new_port_profile(request): @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) + port_profile = EditPortProfileForm( + request.POST or None, instance=port_profile) if port_profile.is_valid(): if port_profile.changed_data: port_profile.save() @@ -1006,7 +1008,6 @@ def edit_port_profile(request, port_profile, **_kwargs): ) - @login_required @can_delete(PortProfile) def del_port_profile(request, port_profile, **_kwargs): @@ -1014,25 +1015,26 @@ def del_port_profile(request, port_profile, **_kwargs): if request.method == 'POST': try: port_profile.delete() - messages.success(request, - _("The port profile was successfully deleted")) + messages.success(request, + _("The port profile was successfully deleted")) except ProtectedError: - messages.success(request, - _("Impossible to delete the port profile")) + 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 + {'objet': port_profile, 'objet_name': _("Port profile")}, + 'topologie/delete.html', + request ) + def make_machine_graph(): """ Create the graph of switchs, machines and access points. """ dico = { 'subs': [], - 'links' : [], + 'links': [], 'alone': [], 'colors': { 'head': "#7f0505", # Color parameters for the graph @@ -1041,23 +1043,23 @@ def make_machine_graph(): 'border_bornes': "#02078e", 'head_bornes': "#25771c", 'head_server': "#1c3777" - } } + } missing = list(Switch.objects.all()) detected = [] for building in Building.objects.all(): # Visit all buildings dico['subs'].append( { - 'bat_id': building.id, - 'bat_name': building, - 'switchs': [], - 'bornes': [], - 'machines': [] + 'bat_id': building.id, + 'bat_name': building, + 'switchs': [], + 'bornes': [], + 'machines': [] } ) # Visit all switchs in this building - for switch in Switch.objects.filter(switchbay__building=building): + for switch in Switch.objects.filter(switchbay__building=building): dico['subs'][-1]['switchs'].append({ 'name': switch.main_interface().domain.name, 'nombre': switch.number, @@ -1067,7 +1069,7 @@ def make_machine_graph(): 'ports': [] }) # visit all ports of this switch and add the switchs linked to it - for port in switch.ports.filter(related__isnull=False): + for port in switch.ports.filter(related__isnull=False): dico['subs'][-1]['switchs'][-1]['ports'].append({ 'numero': port.port, 'related': port.related.switch.main_interface().domain.name @@ -1085,50 +1087,58 @@ def make_machine_graph(): dico['subs'][-1]['machines'].append({ 'name': server.short_name, 'switch': server.switch()[0].main_interface().domain.name, - 'port': Port.objects.filter(machine_interface__machine=server)[0].port + 'port': Port.objects.filter( + machine_interface__machine=server + )[0].port }) # While the list of forgotten ones is not empty while missing: if missing[0].ports.count(): # The switch is not empty - links, new_detected = recursive_switchs(missing[0], None, [missing[0]]) + links, new_detected = recursive_switchs( + missing[0], None, [missing[0]]) for link in links: dico['links'].append(link) # Update the lists of missings and already detected switchs - missing=[i for i in missing if i not in new_detected] + missing = [i for i in missing if i not in new_detected] detected += new_detected - else: # If the switch have no ports, don't explore it and hop to the next one + # If the switch have no ports, don't explore it and hop to the next one + else: del missing[0] # Switchs that are not connected or not in a building - for switch in Switch.objects.filter(switchbay__isnull=True).exclude(ports__related__isnull=False): + for switch in Switch.objects.filter( + switchbay__isnull=True).exclude(ports__related__isnull=False): dico['alone'].append({ 'id': switch.id, 'name': switch.main_interface().domain.name - }) + }) + # generate the dot file + dot_data = generate_dot(dico, 'topologie/graph_switch.dot') - dot_data=generate_dot(dico,'topologie/graph_switch.dot') # generate the dot file - - f = tempfile.NamedTemporaryFile(mode='w+', encoding='utf-8', delete=False) # Create a temporary file to store the dot data + # Create a temporary file to store the dot data + f = tempfile.NamedTemporaryFile(mode='w+', encoding='utf-8', delete=False) with f: - f.write(dot_data) + f.write(dot_data) unflatten = Popen( # unflatten the graph to make it look better - ["unflatten","-l", "3", f.name], + ["unflatten", "-l", "3", f.name], stdout=PIPE ) - image = Popen( # pipe the result of the first command into the second + Popen( # pipe the result of the first command into the second ["dot", "-Tpng", "-o", MEDIA_ROOT + "/images/switchs.png"], stdin=unflatten.stdout, stdout=PIPE ) -def generate_dot(data,template): + +def generate_dot(data, template): """create the dot file :param data: dictionary passed to the template :param template: path to the dot template :return: all the lines of the dot file""" t = loader.get_template(template) - if not isinstance(t, Template) and not (hasattr(t, 'template') and isinstance(t.template, Template)): + if not isinstance(t, Template) and \ + not (hasattr(t, 'template') and isinstance(t.template, Template)): raise Exception("Le template par défaut de Django n'est pas utilisé." "Cela peut mener à des erreurs de rendu." "Vérifiez les paramètres") @@ -1136,27 +1146,40 @@ def generate_dot(data,template): dot = t.render(c) return(dot) + def recursive_switchs(switch_start, switch_before, detected): """Visit the switch and travel to the switchs linked to it. :param switch_start: the switch to begin the visit on - :param switch_before: the switch that you come from. None if switch_start is the first one - :param detected: list of all switchs already visited. None if switch_start is the first one - :return: A list of all the links found and a list of all the switchs visited""" + :param switch_before: the switch that you come from. + None if switch_start is the first one + :param detected: list of all switchs already visited. + None if switch_start is the first one + :return: A list of all the links found and a list of + all the switchs visited + """ detected.append(switch_start) - links_return=[] # list of dictionaries of the links to be detected - for port in switch_start.ports.filter(related__isnull=False): # create links to every switchs below - if port.related.switch != switch_before and port.related.switch != port.switch and port.related.switch not in detected: # Not the switch that we come from, not the current switch + links_return = [] # list of dictionaries of the links to be detected + # create links to every switchs below + for port in switch_start.ports.filter(related__isnull=False): + # Not the switch that we come from, not the current switch + if port.related.switch != switch_before \ + and port.related.switch != port.switch \ + and port.related.switch not in detected: links = { # Dictionary of a link - 'depart':switch_start.id, - 'arrive':port.related.switch.id + 'depart': switch_start.id, + 'arrive': port.related.switch.id } links_return.append(links) # Add current and below levels links - for port in switch_start.ports.filter(related__isnull=False): # go down on every related switchs - if port.related.switch not in detected: # The switch at the end of this link has not been visited - links_down, detected = recursive_switchs(port.related.switch, switch_start, detected) # explore it and get the results - for link in links_down: # Add the non empty links to the current list + # go down on every related switchs + for port in switch_start.ports.filter(related__isnull=False): + # The switch at the end of this link has not been visited + if port.related.switch not in detected: + # explore it and get the results + links_down, detected = recursive_switchs( + port.related.switch, switch_start, detected) + # Add the non empty links to the current list + for link in links_down: if link: - links_return.append(link) + links_return.append(link) return (links_return, detected) - From 0ed68194d79c5c942e401af5172b15a76188f1e4 Mon Sep 17 00:00:00 2001 From: Laouen Fernet Date: Sun, 27 May 2018 00:36:25 +0200 Subject: [PATCH 013/171] add model PortProfile --- re2o/views.py | 2 +- topologie/admin.py | 7 +- topologie/forms.py | 58 ++++++++++++++++ topologie/migrations/0061_portprofile.py | 36 ++++++++++ .../migrations/0062_auto_20180609_1151.py | 66 +++++++++++++++++++ .../migrations/0063_auto_20180609_1158.py | 20 ++++++ .../migrations/0064_auto_20180609_1220.py | 20 ++++++ topologie/models.py | 55 ++++++++++++++++ .../templates/topologie/aff_port_profile.html | 46 +++++++++++++ topologie/templates/topologie/index.html | 10 +++ topologie/urls.py | 9 +++ topologie/views.py | 65 +++++++++++++++++- 12 files changed, 390 insertions(+), 4 deletions(-) create mode 100644 topologie/migrations/0061_portprofile.py create mode 100644 topologie/migrations/0062_auto_20180609_1151.py create mode 100644 topologie/migrations/0063_auto_20180609_1158.py create mode 100644 topologie/migrations/0064_auto_20180609_1220.py create mode 100644 topologie/templates/topologie/aff_port_profile.html diff --git a/re2o/views.py b/re2o/views.py index fb00b98e..8db2b4ea 100644 --- a/re2o/views.py +++ b/re2o/views.py @@ -37,7 +37,6 @@ from django.views.decorators.cache import cache_page from preferences.models import ( Service, MailContact, - GeneralOption, AssoOption, HomeOption ) @@ -108,6 +107,7 @@ def about_page(request): } ) + def contact_page(request): """The view for the contact page Send all the objects MailContact 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 0cc7761b..493b0fc6 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, ) @@ -254,3 +256,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 522b9673..c43f8fc1 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 @@ -492,6 +493,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 e4b3225c..be122191 100644 --- a/topologie/urls.py +++ b/topologie/urls.py @@ -108,4 +108,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. From 4c3f9f654013fd6e66c837044c5d82d4e01c2635 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Tue, 26 Jun 2018 16:49:19 +0000 Subject: [PATCH 014/171] Refactor port_profil --- topologie/migrations/0061_portprofile.py | 30 +++-- .../migrations/0062_auto_20180609_1151.py | 66 ----------- .../migrations/0063_auto_20180609_1158.py | 20 ---- .../migrations/0064_auto_20180609_1220.py | 20 ---- topologie/models.py | 111 ++++++++++++++---- .../templates/topologie/aff_port_profile.html | 32 ++++- 6 files changed, 137 insertions(+), 142 deletions(-) delete mode 100644 topologie/migrations/0062_auto_20180609_1151.py delete mode 100644 topologie/migrations/0063_auto_20180609_1158.py delete mode 100644 topologie/migrations/0064_auto_20180609_1220.py diff --git a/topologie/migrations/0061_portprofile.py b/topologie/migrations/0061_portprofile.py index 7b6af2ed..2b326f28 100644 --- a/topologie/migrations/0061_portprofile.py +++ b/topologie/migrations/0061_portprofile.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.10.7 on 2018-05-26 22:26 +# Generated by Django 1.10.7 on 2018-06-26 16:37 from __future__ import unicode_literals from django.db import migrations, models @@ -10,7 +10,7 @@ import re2o.mixins class Migration(migrations.Migration): dependencies = [ - ('machines', '0081_auto_20180521_1413'), + ('machines', '0082_auto_20180525_2209'), ('topologie', '0060_server'), ] @@ -19,18 +19,26 @@ class Migration(migrations.Migration): 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')), + ('name', models.CharField(max_length=255, verbose_name='Name')), + ('profil_default', models.CharField(blank=True, choices=[('room', 'room'), ('accespoint', 'accesspoint'), ('uplink', 'uplink'), ('asso_machine', 'asso_machine')], max_length=32, null=True, unique=True, verbose_name='profil default')), + ('radius_type', models.CharField(choices=[('NO', 'NO'), ('802.1X', '802.1X'), ('MAC-radius', 'MAC-radius')], max_length=32, verbose_name='RADIUS type')), + ('radius_mode', models.CharField(choices=[('STRICT', 'STRICT'), ('COMMON', 'COMMON')], default='COMMON', max_length=32, verbose_name='RADIUS mode')), + ('speed', models.CharField(choices=[('10-half', '10-half'), ('100-half', '100-half'), ('10-full', '10-full'), ('100-full', '100-full'), ('1000-full', '1000-full'), ('auto', 'auto'), ('auto-10', 'auto-10'), ('auto-100', 'auto-100')], default='auto', help_text='Mode de transmission et vitesse du port', max_length=32, verbose_name='Speed')), + ('mac_limit', models.IntegerField(blank=True, help_text='Limit du nombre de mac sur le port', null=True, verbose_name='Mac limit')), + ('flow_control', models.BooleanField(default=False, help_text='Gestion des débits', verbose_name='Flow control')), + ('dhcp_snooping', models.BooleanField(default=False, help_text='Protection dhcp pirate', verbose_name='Dhcp snooping')), + ('dhcpv6_snooping', models.BooleanField(default=False, help_text='Protection dhcpv6 pirate', verbose_name='Dhcpv6 snooping')), + ('arp_protect', models.BooleanField(default=False, help_text="Verification assignation de l'IP par dhcp", verbose_name='Arp protect')), + ('ra_guard', models.BooleanField(default=False, help_text='Protection contre ra pirate', verbose_name='Ra guard')), + ('loop_protect', models.BooleanField(default=False, help_text='Protection contre les boucles', verbose_name='Loop Protect')), + ('vlan_tagged', models.ManyToManyField(blank=True, null=True, related_name='vlan_tagged', to='machines.Vlan', verbose_name='VLAN(s) tagged')), + ('vlan_untagged', 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')), ], options={ + 'verbose_name': 'Port profile', 'permissions': (('view_port_profile', 'Can view a port profile object'),), + 'verbose_name_plural': 'Port profiles', }, - bases=(re2o.mixins.AclMixin, models.Model), + bases=(re2o.mixins.AclMixin, re2o.mixins.RevMixin, models.Model), ), ] diff --git a/topologie/migrations/0062_auto_20180609_1151.py b/topologie/migrations/0062_auto_20180609_1151.py deleted file mode 100644 index 3f074f7c..00000000 --- a/topologie/migrations/0062_auto_20180609_1151.py +++ /dev/null @@ -1,66 +0,0 @@ -# -*- 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 deleted file mode 100644 index 59ea8732..00000000 --- a/topologie/migrations/0063_auto_20180609_1158.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- 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 deleted file mode 100644 index f657a612..00000000 --- a/topologie/migrations/0064_auto_20180609_1220.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- 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 c43f8fc1..944eeaa5 100644 --- a/topologie/models.py +++ b/topologie/models.py @@ -493,7 +493,7 @@ class Room(AclMixin, RevMixin, models.Model): return self.name -class PortProfile(AclMixin, models.Model): +class PortProfile(AclMixin, RevMixin, models.Model): """Contains the information of the ports' configuration for a switch""" TYPES = ( ('NO', 'NO'), @@ -504,37 +504,100 @@ class PortProfile(AclMixin, models.Model): ('STRICT', 'STRICT'), ('COMMON', 'COMMON'), ) + SPEED = ( + ('10-half', '10-half'), + ('100-half', '100-half'), + ('10-full', '10-full'), + ('100-full', '100-full'), + ('1000-full', '1000-full'), + ('auto', 'auto'), + ('auto-10', 'auto-10'), + ('auto-100', 'auto-100'), + ) + PROFIL_DEFAULT= ( + ('room', 'room'), + ('accespoint', 'accesspoint'), + ('uplink', 'uplink'), + ('asso_machine', 'asso_machine'), + ) 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")) + profil_default = models.CharField( + max_length=32, + choices=PROFIL_DEFAULT, + blank=True, + null=True, + unique=True, + verbose_name=_("profil default") + ) vlan_untagged = models.ForeignKey( - 'machines.Vlan', - related_name='vlan_untagged', - on_delete=models.SET_NULL, - blank=True, - null=True, - verbose_name=_("VLAN untagged") + '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")) + 'machines.Vlan', + related_name='vlan_tagged', + blank=True, + null=True, + verbose_name=_("VLAN(s) tagged") + ) radius_type = models.CharField( - max_length=32, - choices=TYPES, - verbose_name=_("RADIUS type") + 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") + max_length=32, + choices=MODES, + default='COMMON', + verbose_name=_("RADIUS mode") ) + speed = models.CharField( + max_length=32, + choices=SPEED, + default='auto', + help_text='Mode de transmission et vitesse du port', + verbose_name=_("Speed") + ) + mac_limit = models.IntegerField( + null=True, + blank=True, + help_text='Limit du nombre de mac sur le port', + verbose_name=_("Mac limit") + ) + flow_control = models.BooleanField( + default=False, + help_text='Gestion des débits', + verbose_name=_("Flow control") + ) + dhcp_snooping = models.BooleanField( + default=False, + help_text='Protection dhcp pirate', + verbose_name=_("Dhcp snooping") + ) + dhcpv6_snooping = models.BooleanField( + default=False, + help_text='Protection dhcpv6 pirate', + verbose_name=_("Dhcpv6 snooping") + ) + arp_protect = models.BooleanField( + default=False, + help_text='Verification assignation de l\'IP par dhcp', + verbose_name=_("Arp protect") + ) + ra_guard = models.BooleanField( + default=False, + help_text='Protection contre ra pirate', + verbose_name=_("Ra guard") + ) + loop_protect = models.BooleanField( + default=False, + help_text='Protection contre les boucles', + verbose_name=_("Loop Protect") + ) class Meta: permissions = ( diff --git a/topologie/templates/topologie/aff_port_profile.html b/topologie/templates/topologie/aff_port_profile.html index b4b936c5..0c1ca622 100644 --- a/topologie/templates/topologie/aff_port_profile.html +++ b/topologie/templates/topologie/aff_port_profile.html @@ -8,13 +8,43 @@ {% endif %} - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% for port_profile in port_profile_list %} From 860385bffe5afa76a2f777894bf9821feafb1837 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Tue, 26 Jun 2018 17:12:52 +0000 Subject: [PATCH 015/171] Pas de null sur manytomany --- topologie/migrations/0061_portprofile.py | 2 +- topologie/models.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/topologie/migrations/0061_portprofile.py b/topologie/migrations/0061_portprofile.py index 2b326f28..d0e84021 100644 --- a/topologie/migrations/0061_portprofile.py +++ b/topologie/migrations/0061_portprofile.py @@ -31,7 +31,7 @@ class Migration(migrations.Migration): ('arp_protect', models.BooleanField(default=False, help_text="Verification assignation de l'IP par dhcp", verbose_name='Arp protect')), ('ra_guard', models.BooleanField(default=False, help_text='Protection contre ra pirate', verbose_name='Ra guard')), ('loop_protect', models.BooleanField(default=False, help_text='Protection contre les boucles', verbose_name='Loop Protect')), - ('vlan_tagged', models.ManyToManyField(blank=True, null=True, related_name='vlan_tagged', to='machines.Vlan', verbose_name='VLAN(s) tagged')), + ('vlan_tagged', models.ManyToManyField(blank=True, related_name='vlan_tagged', to='machines.Vlan', verbose_name='VLAN(s) tagged')), ('vlan_untagged', 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')), ], options={ diff --git a/topologie/models.py b/topologie/models.py index 944eeaa5..c8029b66 100644 --- a/topologie/models.py +++ b/topologie/models.py @@ -541,7 +541,6 @@ class PortProfile(AclMixin, RevMixin, models.Model): 'machines.Vlan', related_name='vlan_tagged', blank=True, - null=True, verbose_name=_("VLAN(s) tagged") ) radius_type = models.CharField( From ad79c5b5e5ccaf46234cc6476f47fe01ae4a63d7 Mon Sep 17 00:00:00 2001 From: chirac Date: Tue, 26 Jun 2018 23:29:40 +0000 Subject: [PATCH 016/171] =?UTF-8?q?Ajout=20reglages=20s=C3=A9curit=C3=A9?= =?UTF-8?q?=20+=20frontend?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- topologie/forms.py | 43 -------------- .../migrations/0062_auto_20180627_0123.py | 25 ++++++++ topologie/models.py | 8 +++ .../templates/topologie/aff_port_profile.html | 59 +++++++------------ topologie/views.py | 5 +- 5 files changed, 55 insertions(+), 85 deletions(-) create mode 100644 topologie/migrations/0062_auto_20180627_0123.py diff --git a/topologie/forms.py b/topologie/forms.py index 493b0fc6..1736a777 100644 --- a/topologie/forms.py +++ b/topologie/forms.py @@ -257,34 +257,6 @@ class EditBuildingForm(FormRevMixin, ModelForm): 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: @@ -297,18 +269,3 @@ class EditPortProfileForm(FormRevMixin, ModelForm): 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/0062_auto_20180627_0123.py b/topologie/migrations/0062_auto_20180627_0123.py new file mode 100644 index 00000000..b8135de8 --- /dev/null +++ b/topologie/migrations/0062_auto_20180627_0123.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-06-26 23:23 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topologie', '0061_portprofile'), + ] + + operations = [ + migrations.AlterField( + model_name='portprofile', + name='radius_mode', + field=models.CharField(choices=[('STRICT', 'STRICT'), ('COMMON', 'COMMON')], default='COMMON', help_text="En cas d'auth par mac, auth common ou strcit sur le port", 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')], help_text="Choix du type d'authentification radius : non actif, mac ou 802.1X", max_length=32, verbose_name='RADIUS type'), + ), + ] diff --git a/topologie/models.py b/topologie/models.py index c8029b66..00648465 100644 --- a/topologie/models.py +++ b/topologie/models.py @@ -546,12 +546,14 @@ class PortProfile(AclMixin, RevMixin, models.Model): radius_type = models.CharField( max_length=32, choices=TYPES, + help_text="Choix du type d'authentification radius : non actif, mac ou 802.1X", verbose_name=_("RADIUS type") ) radius_mode = models.CharField( max_length=32, choices=MODES, default='COMMON', + help_text="En cas d'auth par mac, auth common ou strcit sur le port", verbose_name=_("RADIUS mode") ) speed = models.CharField( @@ -605,6 +607,12 @@ class PortProfile(AclMixin, RevMixin, models.Model): verbose_name = _("Port profile") verbose_name_plural = _("Port profiles") + security_parameters_fields = ['loop_protect', 'ra_guard', 'arp_protect', 'dhcpv6_snooping', 'dhcp_snooping', 'flow_control'] + + @cached_property + def security_parameters_enabled(self): + return [parameter for parameter in self.security_parameters_fields if getattr(self, parameter)] + def __str__(self): return self.name diff --git a/topologie/templates/topologie/aff_port_profile.html b/topologie/templates/topologie/aff_port_profile.html index 0c1ca622..7e044c0a 100644 --- a/topologie/templates/topologie/aff_port_profile.html +++ b/topologie/templates/topologie/aff_port_profile.html @@ -9,53 +9,34 @@
{% trans "Name" %} {% trans "VLAN untagged" %} {% trans "VLAN(s) tagged" %}
{% trans "RADIUS type" %} {% trans "RADIUS mode" %}{% trans "RADIUS type" %}
{% trans "speed" %}{% trans "Mac limit" %}{% trans "Flow control" %}
{% trans "dhcp snooping" %}{% trans "dhcpv6 snooping" %}{% trans "arp protect" %}
{% trans "ra guard" %}{% trans "loop protect" %}
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + {% for port_profile in port_profile_list %} - + - - + + {% endif %} + + + - - + - + From bb39248376fc613659523d2ada22ed17ec680ca9 Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Wed, 11 Jul 2018 19:37:22 +0200 Subject: [PATCH 024/171] PEP8 mon amour + typos --- freeradius_utils/auth.py | 18 +-- topologie/models.py | 115 ++++++++----- .../templates/topologie/aff_port_profile.html | 4 +- topologie/views.py | 153 ++++++++++-------- 4 files changed, 172 insertions(+), 118 deletions(-) diff --git a/freeradius_utils/auth.py b/freeradius_utils/auth.py index 8450777b..d743495f 100644 --- a/freeradius_utils/auth.py +++ b/freeradius_utils/auth.py @@ -355,7 +355,7 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, port=port_number ) .first()) - + # Si le port est inconnu, on place sur le vlan defaut # Aucune information particulière ne permet de déterminer quelle # politique à appliquer sur ce port @@ -363,12 +363,12 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, return (sw_name, "Chambre inconnue", u'Port inconnu', VLAN_OK) # On récupère le profil du port - port_profil = port.get_port_profil + port_profile = port.get_port_profile - # Si un vlan a été précisé dans la config du port, + # Si un vlan a été précisé dans la config du port, # on l'utilise pour VLAN_OK - if port_profil.vlan_untagged: - DECISION_VLAN = int(port_profil.vlan_untagged.vlan_id) + if port_profile.vlan_untagged: + DECISION_VLAN = int(port_profile.vlan_untagged.vlan_id) extra_log = u"Force sur vlan " + str(DECISION_VLAN) else: DECISION_VLAN = VLAN_OK @@ -378,7 +378,7 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, return (sw_name, port.room, u'Port desactivé', VLAN_NOK) # Si radius est désactivé, on laisse passer - if port_profil.radius_type == 'NO': + if port_profile.radius_type == 'NO': return (sw_name, "", u"Pas d'authentification sur ce port" + extra_log, @@ -386,7 +386,7 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, # Si le 802.1X est activé sur ce port, cela veut dire que la personne a été accept précédemment # Par conséquent, on laisse passer sur le bon vlan - if nas_type.port_access_mode == '802.1X' and port_profil.radius_type == '802.1X': + if nas_type.port_access_mode == '802.1X' and port_profile.radius_type == '802.1X': room = port.room or "Chambre/local inconnu" return (sw_name, room, u'Acceptation authentification 802.1X', DECISION_VLAN) @@ -395,7 +395,7 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, # rattachés à ce port sont bien à jour de cotisation. Sinon on rejette (anti squattage) # Il n'est pas possible de se connecter sur une prise strict sans adhérent à jour de cotis # dedans - if port_profil.radius_mode == 'STRICT': + if port_profile.radius_mode == 'STRICT': room = port.room if not room: return (sw_name, "Inconnue", u'Chambre inconnue', VLAN_NOK) @@ -411,7 +411,7 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, # else: user OK, on passe à la verif MAC # Si on fait de l'auth par mac, on cherche l'interface via sa mac dans la bdd - if port_profil.radius_mode == 'COMMON' or port_profil.radius_mode == 'STRICT': + if port_profile.radius_mode == 'COMMON' or port_profile.radius_mode == 'STRICT': # Authentification par mac interface = (Interface.objects .filter(mac_address=mac_address) diff --git a/topologie/models.py b/topologie/models.py index 029aebc8..a0333d46 100644 --- a/topologie/models.py +++ b/topologie/models.py @@ -40,7 +40,7 @@ from __future__ import unicode_literals import itertools from django.db import models -from django.db.models.signals import pre_save, post_save, post_delete +from django.db.models.signals import post_save, post_delete from django.utils.functional import cached_property from django.dispatch import receiver from django.core.exceptions import ValidationError @@ -52,11 +52,6 @@ from reversion import revisions as reversion from machines.models import Machine, regen from re2o.mixins import AclMixin, RevMixin -from os.path import isfile -from os import remove - - - class Stack(AclMixin, RevMixin, models.Model): """Un objet stack. Regrouppe des switchs en foreign key @@ -123,7 +118,10 @@ class AccessPoint(AclMixin, Machine): ) def building(self): - """Return the building of the AP/Server (building of the switchs connected to...)""" + """ + Return the building of the AP/Server (building of the switchs + connected to...) + """ return Building.objects.filter( switchbay__switch=self.switch() ) @@ -135,14 +133,18 @@ class AccessPoint(AclMixin, Machine): @classmethod def all_ap_in(cls, building_instance): """Get a building as argument, returns all ap of a building""" - return cls.objects.filter(interface__port__switch__switchbay__building=building_instance) + return cls.objects.filter( + interface__port__switch__switchbay__building=building_instance + ) def __str__(self): return str(self.interface_set.first()) class Server(Machine): - """Dummy class, to retrieve servers of a building, or get switch of a server""" + """ + Dummy class, to retrieve servers of a building, or get switch of a server + """ class Meta: proxy = True @@ -160,7 +162,10 @@ class Server(Machine): ) def building(self): - """Return the building of the AP/Server (building of the switchs connected to...)""" + """ + Return the building of the AP/Server + (building of the switchs connected to...) + """ return Building.objects.filter( switchbay__switch=self.switch() ) @@ -172,7 +177,9 @@ class Server(Machine): @classmethod def all_server_in(cls, building_instance): """Get a building as argument, returns all server of a building""" - return cls.objects.filter(interface__port__switch__switchbay__building=building_instance).exclude(accesspoint__isnull=False) + return cls.objects.filter( + interface__port__switch__switchbay__building=building_instance + ).exclude(accesspoint__isnull=False) def __str__(self): return str(self.interface_set.first()) @@ -200,7 +207,7 @@ class Switch(AclMixin, Machine): blank=True, null=True, on_delete=models.SET_NULL - ) + ) stack_member_id = models.PositiveIntegerField( blank=True, null=True, @@ -238,7 +245,7 @@ class Switch(AclMixin, Machine): raise ValidationError( {'stack_member_id': "L'id de ce switch est en\ dehors des bornes permises pas la stack"} - ) + ) else: raise ValidationError({'stack_member_id': "L'id dans la stack\ ne peut être nul"}) @@ -382,25 +389,25 @@ class Port(AclMixin, RevMixin, models.Model): on_delete=models.PROTECT, blank=True, null=True - ) + ) machine_interface = models.ForeignKey( 'machines.Interface', on_delete=models.SET_NULL, blank=True, null=True - ) + ) related = models.OneToOneField( 'self', null=True, blank=True, related_name='related_port' - ) + ) custom_profile = models.ForeignKey( 'PortProfile', on_delete=models.PROTECT, blank=True, null=True - ) + ) state = models.BooleanField( default=True, help_text='Port state Active', @@ -415,32 +422,35 @@ class Port(AclMixin, RevMixin, models.Model): ) @cached_property - def get_port_profil(self): - """Return the config profil for this port + def get_port_profile(self): + """Return the config profile for this port :returns: the profile of self (port)""" - def profil_or_nothing(profil): - port_profil = PortProfile.objects.filter(profil_default=profil).first() - if port_profil: - return port_profil + def profile_or_nothing(profile): + port_profile = PortProfile.objects.filter( + profile_default=profile).first() + if port_profile: + return port_profile else: - nothing = PortProfile.objects.filter(profil_default='nothing').first() - if not nothing: - nothing = PortProfile.objects.create(profil_default='nothing', name='nothing', radius_type='NO') - return nothing + nothing_profile, _created = PortProfile.objects.get_or_create( + profile_default='nothing', + name='nothing', + radius_type='NO' + ) + return nothing_profile if self.custom_profile: return self.custom_profile elif self.related: - return profil_or_nothing('uplink') + return profile_or_nothing('uplink') elif self.machine_interface: if hasattr(self.machine_interface.machine, 'accesspoint'): - return profil_or_nothing('access_point') + return profile_or_nothing('access_point') else: - return profil_or_nothing('asso_machine') + return profile_or_nothing('asso_machine') elif self.room: - return profil_or_nothing('room') + return profile_or_nothing('room') else: - return profil_or_nothing('nothing') + return profile_or_nothing('nothing') @classmethod def get_instance(cls, portid, *_args, **kwargs): @@ -525,11 +535,11 @@ class PortProfile(AclMixin, RevMixin, models.Model): ('NO', 'NO'), ('802.1X', '802.1X'), ('MAC-radius', 'MAC-radius'), - ) + ) MODES = ( ('STRICT', 'STRICT'), ('COMMON', 'COMMON'), - ) + ) SPEED = ( ('10-half', '10-half'), ('100-half', '100-half'), @@ -539,14 +549,14 @@ class PortProfile(AclMixin, RevMixin, models.Model): ('auto', 'auto'), ('auto-10', 'auto-10'), ('auto-100', 'auto-100'), - ) - PROFIL_DEFAULT= ( + ) + PROFIL_DEFAULT = ( ('room', 'room'), ('accespoint', 'accesspoint'), ('uplink', 'uplink'), ('asso_machine', 'asso_machine'), ('nothing', 'nothing'), - ) + ) name = models.CharField(max_length=255, verbose_name=_("Name")) profil_default = models.CharField( max_length=32, @@ -620,25 +630,36 @@ class PortProfile(AclMixin, RevMixin, models.Model): default=False, help_text='Protect against rogue ra', verbose_name=_("Ra guard") - ) + ) loop_protect = models.BooleanField( default=False, help_text='Protect again loop', verbose_name=_("Loop Protect") - ) + ) class Meta: permissions = ( - ("view_port_profile", _("Can view a port profile object")), + ("view_port_profile", _("Can view a port profile object")), ) verbose_name = _("Port profile") verbose_name_plural = _("Port profiles") - security_parameters_fields = ['loop_protect', 'ra_guard', 'arp_protect', 'dhcpv6_snooping', 'dhcp_snooping', 'flow_control'] + security_parameters_fields = [ + 'loop_protect', + 'ra_guard', + 'arp_protect', + 'dhcpv6_snooping', + 'dhcp_snooping', + 'flow_control' + ] @cached_property def security_parameters_enabled(self): - return [parameter for parameter in self.security_parameters_fields if getattr(self, parameter)] + return [ + parameter + for parameter in self.security_parameters_fields + if getattr(self, parameter) + ] @cached_property def security_parameters_as_str(self): @@ -654,45 +675,55 @@ def ap_post_save(**_kwargs): regen('unifi-ap-names') regen("graph_topo") + @receiver(post_delete, sender=AccessPoint) def ap_post_delete(**_kwargs): """Regeneration des noms des bornes vers le controleur""" regen('unifi-ap-names') regen("graph_topo") + @receiver(post_delete, sender=Stack) def stack_post_delete(**_kwargs): """Vide les id des switches membres d'une stack supprimée""" Switch.objects.filter(stack=None).update(stack_member_id=None) + @receiver(post_save, sender=Port) def port_post_save(**_kwargs): regen("graph_topo") + @receiver(post_delete, sender=Port) def port_post_delete(**_kwargs): regen("graph_topo") + @receiver(post_save, sender=ModelSwitch) def modelswitch_post_save(**_kwargs): regen("graph_topo") + @receiver(post_delete, sender=ModelSwitch) def modelswitch_post_delete(**_kwargs): regen("graph_topo") + @receiver(post_save, sender=Building) def building_post_save(**_kwargs): regen("graph_topo") + @receiver(post_delete, sender=Building) def building_post_delete(**_kwargs): regen("graph_topo") + @receiver(post_save, sender=Switch) def switch_post_save(**_kwargs): regen("graph_topo") + @receiver(post_delete, sender=Switch) def switch_post_delete(**_kwargs): regen("graph_topo") diff --git a/topologie/templates/topologie/aff_port_profile.html b/topologie/templates/topologie/aff_port_profile.html index 12c8b365..65446236 100644 --- a/topologie/templates/topologie/aff_port_profile.html +++ b/topologie/templates/topologie/aff_port_profile.html @@ -29,9 +29,9 @@ with this program; if not, write to the Free Software Foundation, Inc., {% include "pagination.html" with list=port_profile_list %} {% endif %} -
{% trans "Name" %} - {% trans "VLAN untagged" %}{% trans "VLAN(s) tagged" %}
{% trans "RADIUS type" %}{% trans "RADIUS mode" %}{% trans "RADIUS type" %}
{% trans "speed" %}{% trans "Mac limit" %}{% trans "Flow control" %}
{% trans "dhcp snooping" %}{% trans "dhcpv6 snooping" %}{% trans "arp protect" %}
{% trans "ra guard" %}{% trans "loop protect" %}{% trans "Nom" %}{% trans "Default pour" %}{% trans "VLANs" %}{% trans "Réglages RADIUS" %}{% trans "Vitesse" %}{% trans "Mac address limit" %}{% trans "Sécurité" %}
{{port_profile.name}}{{port_profile.vlan_untagged}}{{port_profile.profil_default}} - {{port_profile.vlan_tagged.all|join:", "}} + Untagged : {{port_profile.vlan_untagged}} +
+ Tagged : {{port_profile.vlan_tagged.all|join:", "}}
{{port_profile.radius_type}}{{port_profile.radius_mode}} + Type : {{port_profile.radius_type}} + {% if port_profile.radius_type == "MAC-radius" %} +
+ Mode : {{port_profile.radius_mode}}
{{port_profile.speed}}{{port_profile.mac_limit}}{{port_profile.security_parameters_enabled|join:"
"}}
{% include 'buttons/history.html' with href='topologie:history' name='portprofile' id=port_profile.pk %} {% can_edit port_profile %} diff --git a/topologie/views.py b/topologie/views.py index eb86ce9c..4b21c651 100644 --- a/topologie/views.py +++ b/topologie/views.py @@ -97,7 +97,6 @@ from .forms import ( EditAccessPointForm, EditSwitchBayForm, EditBuildingForm, - NewPortProfileForm, EditPortProfileForm, ) @@ -135,7 +134,7 @@ def index(request): 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')): + if any(service_link.need_regen for service_link in Service_link.objects.filter(service__service_type='graph_topo')): make_machine_graph() for service_link in Service_link.objects.filter(service__service_type='graph_topo'): service_link.done_regen() @@ -967,7 +966,7 @@ def del_constructor_switch(request, constructor_switch, **_kwargs): @can_create(PortProfile) def new_port_profile(request): """Create a new port profile""" - port_profile = NewPortProfileForm(request.POST or None) + port_profile = EditPortProfileForm(request.POST or None) if port_profile.is_valid(): port_profile.save() messages.success(request, _("Port profile created")) From 7feba7fa4cf8a50a4490687e5bb0e55453b1e4dc Mon Sep 17 00:00:00 2001 From: chirac Date: Wed, 27 Jun 2018 20:28:54 +0000 Subject: [PATCH 017/171] =?UTF-8?q?Menu=20s=C3=A9par=C3=A9=20pour=20les=20?= =?UTF-8?q?profils=20de=20ports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- topologie/templates/topologie/index.html | 10 ----- .../topologie/index_portprofile.html | 44 +++++++++++++++++++ topologie/templates/topologie/sidebar.html | 6 ++- topologie/urls.py | 3 ++ topologie/views.py | 17 +++++-- 5 files changed, 66 insertions(+), 14 deletions(-) create mode 100644 topologie/templates/topologie/index_portprofile.html diff --git a/topologie/templates/topologie/index.html b/topologie/templates/topologie/index.html index 7902f4a3..7949f412 100644 --- a/topologie/templates/topologie/index.html +++ b/topologie/templates/topologie/index.html @@ -73,14 +73,4 @@ 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/templates/topologie/index_portprofile.html b/topologie/templates/topologie/index_portprofile.html new file mode 100644 index 00000000..a4287da6 --- /dev/null +++ b/topologie/templates/topologie/index_portprofile.html @@ -0,0 +1,44 @@ +{% 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 %}Switchs{% endblock %} + +{% block content %} + +

{% 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/templates/topologie/sidebar.html b/topologie/templates/topologie/sidebar.html index ce7b4114..c7ea6337 100644 --- a/topologie/templates/topologie/sidebar.html +++ b/topologie/templates/topologie/sidebar.html @@ -33,7 +33,11 @@ with this program; if not, write to the Free Software Foundation, Inc., Switchs - + + + Profil des ports switchs + + Bornes WiFi diff --git a/topologie/urls.py b/topologie/urls.py index be122191..c314c800 100644 --- a/topologie/urls.py +++ b/topologie/urls.py @@ -108,6 +108,9 @@ urlpatterns = [ url(r'^del_building/(?P[0-9]+)$', views.del_building, name='del-building'), + url(r'^index_port_profile/$', + views.index_port_profile, + name='index-port-profile'), url(r'^new_port_profile/$', views.new_port_profile, name='new-port-profile'), diff --git a/topologie/views.py b/topologie/views.py index 4b21c651..b7761e0c 100644 --- a/topologie/views.py +++ b/topologie/views.py @@ -128,11 +128,9 @@ def index(request): 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() @@ -144,7 +142,20 @@ def index(request): return render( request, 'topologie/index.html', - {'switch_list': switch_list, 'port_profile_list': port_profile_list} + {'switch_list': switch_list} + ) + + +@login_required +@can_view_all(PortProfile) +def index_port_profile(request): + pagination_number = GeneralOption.get_cached_value('pagination_number') + port_profile_list = PortProfile.objects.all().select_related('vlan_untagged') + port_profile_list = re2o_paginator(request, port_profile_list, pagination_number) + return render( + request, + 'topologie/index_portprofile.html', + {'port_profile_list': port_profile_list} ) From bac18a265de926d338b32b24850893444ae34540 Mon Sep 17 00:00:00 2001 From: chirac Date: Sat, 30 Jun 2018 15:29:00 +0000 Subject: [PATCH 018/171] =?UTF-8?q?Ajout=20et=20transfert=20des=20ancienne?= =?UTF-8?q?s=20donn=C3=A9es=20vers=20le=20nouveau=20syst=C3=A8me=20de=20pr?= =?UTF-8?q?ofil=20de=20ports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- topologie/forms.py | 7 +-- topologie/migrations/0061_portprofile.py | 2 +- .../migrations/0063_port_custom_profil.py | 21 +++++++ topologie/migrations/0064_createprofil.py | 57 +++++++++++++++++++ .../migrations/0065_auto_20180630_1703.py | 23 ++++++++ topologie/models.py | 32 +++++++---- topologie/templates/topologie/aff_port.html | 6 +- 7 files changed, 131 insertions(+), 17 deletions(-) create mode 100644 topologie/migrations/0063_port_custom_profil.py create mode 100644 topologie/migrations/0064_createprofil.py create mode 100644 topologie/migrations/0065_auto_20180630_1703.py diff --git a/topologie/forms.py b/topologie/forms.py index 1736a777..d0cd38e0 100644 --- a/topologie/forms.py +++ b/topologie/forms.py @@ -80,8 +80,8 @@ class EditPortForm(FormRevMixin, ModelForm): optimiser le temps de chargement avec select_related (vraiment lent sans)""" class Meta(PortForm.Meta): - fields = ['room', 'related', 'machine_interface', 'radius', - 'vlan_force', 'details'] + fields = ['room', 'related', 'machine_interface', 'custom_profil', + 'details'] def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) @@ -101,8 +101,7 @@ class AddPortForm(FormRevMixin, ModelForm): 'room', 'machine_interface', 'related', - 'radius', - 'vlan_force', + 'custom_profil', 'details' ] diff --git a/topologie/migrations/0061_portprofile.py b/topologie/migrations/0061_portprofile.py index d0e84021..7e130163 100644 --- a/topologie/migrations/0061_portprofile.py +++ b/topologie/migrations/0061_portprofile.py @@ -20,7 +20,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=255, verbose_name='Name')), - ('profil_default', models.CharField(blank=True, choices=[('room', 'room'), ('accespoint', 'accesspoint'), ('uplink', 'uplink'), ('asso_machine', 'asso_machine')], max_length=32, null=True, unique=True, verbose_name='profil default')), + ('profil_default', models.CharField(blank=True, choices=[('room', 'room'), ('nothing', 'nothing'), ('accespoint', 'accesspoint'), ('uplink', 'uplink'), ('asso_machine', 'asso_machine')], max_length=32, null=True, unique=True, verbose_name='profil default')), ('radius_type', models.CharField(choices=[('NO', 'NO'), ('802.1X', '802.1X'), ('MAC-radius', 'MAC-radius')], max_length=32, verbose_name='RADIUS type')), ('radius_mode', models.CharField(choices=[('STRICT', 'STRICT'), ('COMMON', 'COMMON')], default='COMMON', max_length=32, verbose_name='RADIUS mode')), ('speed', models.CharField(choices=[('10-half', '10-half'), ('100-half', '100-half'), ('10-full', '10-full'), ('100-full', '100-full'), ('1000-full', '1000-full'), ('auto', 'auto'), ('auto-10', 'auto-10'), ('auto-100', 'auto-100')], default='auto', help_text='Mode de transmission et vitesse du port', max_length=32, verbose_name='Speed')), diff --git a/topologie/migrations/0063_port_custom_profil.py b/topologie/migrations/0063_port_custom_profil.py new file mode 100644 index 00000000..15feebce --- /dev/null +++ b/topologie/migrations/0063_port_custom_profil.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-06-28 07:49 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('topologie', '0062_auto_20180627_0123'), + ] + + operations = [ + migrations.AddField( + model_name='port', + name='custom_profil', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='topologie.PortProfile'), + ), + ] diff --git a/topologie/migrations/0064_createprofil.py b/topologie/migrations/0064_createprofil.py new file mode 100644 index 00000000..189d1812 --- /dev/null +++ b/topologie/migrations/0064_createprofil.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-12-31 19:53 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('topologie', '0063_port_custom_profil'), + ] + + def transfer_profil(apps, schema_editor): + db_alias = schema_editor.connection.alias + port = apps.get_model("topologie", "Port") + profil = apps.get_model("topologie", "PortProfile") + vlan = apps.get_model("machines", "Vlan") + port_list = port.objects.using(db_alias).all() + profil_nothing = profil.objects.using(db_alias).create(name='nothing', profil_default='nothing', radius_type='NO') + profil_uplink = profil.objects.using(db_alias).create(name='uplink', profil_default='uplink', radius_type='NO') + profil_machine = profil.objects.using(db_alias).create(name='asso_machine', profil_default='asso_machine', radius_type='NO') + profil_room = profil.objects.using(db_alias).create(name='room', profil_default='room', radius_type='NO') + profil_borne = profil.objects.using(db_alias).create(name='accesspoint', profil_default='accesspoint', radius_type='NO') + for vlan_instance in vlan.objects.using(db_alias).all(): + if port.objects.using(db_alias).filter(vlan_force=vlan_instance): + custom_profil = profil.objects.using(db_alias).create(name='vlan-force-' + str(vlan_instance.vlan_id), radius_type='NO', vlan_untagged=vlan_instance) + port.objects.using(db_alias).filter(vlan_force=vlan_instance).update(custom_profil=custom_profil) + if port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').count() and port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').count(): + profil_room.radius_type = 'MAC-radius' + profil_room.radius_mode = 'STRICT' + common_profil = profil.objects.using(db_alias).create(name='mac-radius-common', radius_type='MAC-radius', radius_mode='COMMON') + no_rad_profil = profil.objects.using(db_alias).create(name='no-radius', radius_type='NO') + port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').update(custom_profil=common_profil) + port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').update(custom_profil=no_rad_profil) + elif port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').count() and port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').count(): + profil_room.radius_type = 'MAC-radius' + profil_room.radius_mode = 'COMMON' + strict_profil = profil.objects.using(db_alias).create(name='mac-radius-strict', radius_type='MAC-radius', radius_mode='STRICT') + no_rad_profil = profil.objects.using(db_alias).create(name='no-radius', radius_type='NO') + port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').update(custom_profil=strict_profil) + port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').update(custom_profil=no_rad_profil) + else: + strict_profil = profil.objects.using(db_alias).create(name='mac-radius-strict', radius_type='MAC-radius', radius_mode='STRICT') + common_profil = profil.objects.using(db_alias).create(name='mac-radius-common', radius_type='MAC-radius', radius_mode='COMMON') + port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').update(custom_profil=strict_profil) + port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').update(custom_profil=common_profil) + profil_room.save() + + + + def untransfer_profil(apps, schema_editor): + return + + operations = [ + migrations.RunPython(transfer_profil, untransfer_profil), + ] diff --git a/topologie/migrations/0065_auto_20180630_1703.py b/topologie/migrations/0065_auto_20180630_1703.py new file mode 100644 index 00000000..9fed2d83 --- /dev/null +++ b/topologie/migrations/0065_auto_20180630_1703.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-06-30 15:03 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('topologie', '0064_createprofil'), + ] + + operations = [ + migrations.RemoveField( + model_name='port', + name='radius', + ), + migrations.RemoveField( + model_name='port', + name='vlan_force', + ), + ] diff --git a/topologie/models.py b/topologie/models.py index 00648465..79f2cf67 100644 --- a/topologie/models.py +++ b/topologie/models.py @@ -370,12 +370,6 @@ class Port(AclMixin, RevMixin, models.Model): de forcer un port sur un vlan particulier. S'additionne à la politique RADIUS""" PRETTY_NAME = "Port de switch" - STATES = ( - ('NO', 'NO'), - ('STRICT', 'STRICT'), - ('BLOQ', 'BLOQ'), - ('COMMON', 'COMMON'), - ) switch = models.ForeignKey( 'Switch', @@ -401,13 +395,13 @@ class Port(AclMixin, RevMixin, models.Model): blank=True, related_name='related_port' ) - radius = models.CharField(max_length=32, choices=STATES, default='NO') - vlan_force = models.ForeignKey( - 'machines.Vlan', - on_delete=models.SET_NULL, + custom_profil = models.ForeignKey( + 'PortProfile', + on_delete=models.PROTECT, blank=True, null=True ) + details = models.CharField(max_length=255, blank=True) class Meta: @@ -416,6 +410,23 @@ class Port(AclMixin, RevMixin, models.Model): ("view_port", "Peut voir un objet port"), ) + @cached_property + def get_port_profil(self): + """Return the config profil for this port""" + if self.custom_profil: + return custom_profil + elif self.related: + return PortProfil.objects.get(profil_default='uplink') + elif self.machine_interface: + if isinstance(self.machine_interface.machine, AccessPoint): + return PortProfil.objects.get(profil_default='access_point') + else: + return PortProfil.objects.get(profil_default='asso_machine') + elif self.room: + return PortProfil.objects.get(profil_default='room') + else: + return PortProfil.objects.get(profil_default='nothing') + @classmethod def get_instance(cls, portid, *_args, **kwargs): return (cls.objects @@ -519,6 +530,7 @@ class PortProfile(AclMixin, RevMixin, models.Model): ('accespoint', 'accesspoint'), ('uplink', 'uplink'), ('asso_machine', 'asso_machine'), + ('nothing', 'nothing'), ) name = models.CharField(max_length=255, verbose_name=_("Name")) profil_default = models.CharField( diff --git a/topologie/templates/topologie/aff_port.html b/topologie/templates/topologie/aff_port.html index f01269a8..34130199 100644 --- a/topologie/templates/topologie/aff_port.html +++ b/topologie/templates/topologie/aff_port.html @@ -64,11 +64,13 @@ with this program; if not, write to the Free Software Foundation, Inc., {% acl_else %} {{ port.related }} + + {% acl_else %} + {{ port.related }} {% acl_end %} {% endif %}
{{ port.radius }}{% if not port.vlan_force %}Aucun{% else %}{{ port.vlan_force }}{% endif %}{% if not port.custom_profil %}Par défaut{% else %}{{port.custom_profil}}{% endif %} {{ port.details }} {% history_button port %} From ee79e21e353e4be00ca1cee45767ac959c20adef Mon Sep 17 00:00:00 2001 From: chirac Date: Sat, 30 Jun 2018 16:19:02 +0000 Subject: [PATCH 019/171] =?UTF-8?q?Finition,=20gestion=20du=20renvoie=20du?= =?UTF-8?q?=20profil=20par=20d=C3=A9faut=20du=20port?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- search/views.py | 4 +-- topologie/models.py | 30 ++++++++++++++------- topologie/templates/topologie/aff_port.html | 2 +- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/search/views.py b/search/views.py index 871515fa..0ae8470b 100644 --- a/search/views.py +++ b/search/views.py @@ -262,9 +262,9 @@ def search_single_word(word, filters, user, ) | Q( related__switch__interface__domain__name__icontains=word ) | Q( - radius__icontains=word + custom_profil__name__icontains=word ) | Q( - vlan_force__name__icontains=word + custom_profil__profil_default__icontains=word ) | Q( details__icontains=word ) diff --git a/topologie/models.py b/topologie/models.py index 79f2cf67..e4a79f74 100644 --- a/topologie/models.py +++ b/topologie/models.py @@ -413,19 +413,29 @@ class Port(AclMixin, RevMixin, models.Model): @cached_property def get_port_profil(self): """Return the config profil for this port""" - if self.custom_profil: - return custom_profil - elif self.related: - return PortProfil.objects.get(profil_default='uplink') - elif self.machine_interface: - if isinstance(self.machine_interface.machine, AccessPoint): - return PortProfil.objects.get(profil_default='access_point') + def profil_or_nothing(profil): + port_profil = PortProfile.objects.filter(profil_default=profil).first() + if port_profil: + return port_profil else: - return PortProfil.objects.get(profil_default='asso_machine') + nothing = PortProfile.objects.filter(profil_default='nothing').first() + if not nothing: + nothing = PortProfile.objects.create(profil_default='nothing', name='nothing', radius_type='NO') + return nothing + + if self.custom_profil: + return self.custom_profil + elif self.related: + return profil_or_nothing('uplink') + elif self.machine_interface: + if hasattr(self.machine_interface.machine, 'accesspoint'): + return profil_or_nothing('access_point') + else: + return profil_or_nothing('asso_machine') elif self.room: - return PortProfil.objects.get(profil_default='room') + return profil_or_nothing('room') else: - return PortProfil.objects.get(profil_default='nothing') + return profil_or_nothing('nothing') @classmethod def get_instance(cls, portid, *_args, **kwargs): diff --git a/topologie/templates/topologie/aff_port.html b/topologie/templates/topologie/aff_port.html index 34130199..85fe901e 100644 --- a/topologie/templates/topologie/aff_port.html +++ b/topologie/templates/topologie/aff_port.html @@ -70,7 +70,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% acl_end %} {% endif %} {% if not port.custom_profil %}Par défaut{% else %}{{port.custom_profil}}{% endif %}{% if not port.custom_profil %}Par défaut : {% endif %}{{port.get_port_profil}} {{ port.details }} {% history_button port %} From b66dcff8be735c5da6a214617018cca41d589835 Mon Sep 17 00:00:00 2001 From: chirac Date: Sat, 30 Jun 2018 16:36:14 +0000 Subject: [PATCH 020/171] =?UTF-8?q?Adapte=20freeradius=20pour=20le=20nouve?= =?UTF-8?q?au=20syst=C3=A8me=20de=20profil=20de=20ports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- freeradius_utils/auth.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/freeradius_utils/auth.py b/freeradius_utils/auth.py index fcf55cfa..4e9ad24e 100644 --- a/freeradius_utils/auth.py +++ b/freeradius_utils/auth.py @@ -359,23 +359,26 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, if not port: return (sw_name, "Chambre inconnue", u'Port inconnu', VLAN_OK) + # On récupère le profil du port + port_profil = port.get_port_profil + # Si un vlan a été précisé, on l'utilise pour VLAN_OK - if port.vlan_force: - DECISION_VLAN = int(port.vlan_force.vlan_id) + if port_profil.vlan_untagged: + DECISION_VLAN = int(port_profil.vlan_untagged.vlan_id) extra_log = u"Force sur vlan " + str(DECISION_VLAN) else: DECISION_VLAN = VLAN_OK - if port.radius == 'NO': + if port_profil.radius_type == 'NO': return (sw_name, "", u"Pas d'authentification sur ce port" + extra_log, DECISION_VLAN) - if port.radius == 'BLOQ': + if port_profil.radius_type == 'BLOQ': return (sw_name, port.room, u'Port desactive', VLAN_NOK) - if port.radius == 'STRICT': + if port_profil.radius_type == 'STRICT': room = port.room if not room: return (sw_name, "Inconnue", u'Chambre inconnue', VLAN_NOK) @@ -390,7 +393,7 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, return (sw_name, room, u'Chambre resident desactive', VLAN_NOK) # else: user OK, on passe à la verif MAC - if port.radius == 'COMMON' or port.radius == 'STRICT': + if port_profil.radius_type == 'COMMON' or port_profil.radius_type == 'STRICT': # Authentification par mac interface = (Interface.objects .filter(mac_address=mac_address) From 2a2a2850bd294581496676288d46b67c864b3efb Mon Sep 17 00:00:00 2001 From: chirac Date: Sat, 30 Jun 2018 17:04:15 +0000 Subject: [PATCH 021/171] =?UTF-8?q?Boolean=20direct=20pour=20d=C3=A9sactiv?= =?UTF-8?q?er=20un=20port=20+=20logo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- freeradius_utils/auth.py | 6 +-- topologie/forms.py | 3 +- .../migrations/0066_auto_20180630_1855.py | 25 +++++++++++ topologie/models.py | 6 ++- topologie/templates/topologie/aff_port.html | 45 +++++++++---------- topologie/templates/topologie/sidebar.html | 4 +- 6 files changed, 58 insertions(+), 31 deletions(-) create mode 100644 topologie/migrations/0066_auto_20180630_1855.py diff --git a/freeradius_utils/auth.py b/freeradius_utils/auth.py index 4e9ad24e..b318ab63 100644 --- a/freeradius_utils/auth.py +++ b/freeradius_utils/auth.py @@ -369,15 +369,15 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, else: DECISION_VLAN = VLAN_OK + if not port.state: + return (sw_name, port.room, u'Port desactive', VLAN_NOK) + if port_profil.radius_type == 'NO': return (sw_name, "", u"Pas d'authentification sur ce port" + extra_log, DECISION_VLAN) - if port_profil.radius_type == 'BLOQ': - return (sw_name, port.room, u'Port desactive', VLAN_NOK) - if port_profil.radius_type == 'STRICT': room = port.room if not room: diff --git a/topologie/forms.py b/topologie/forms.py index d0cd38e0..9ac3297f 100644 --- a/topologie/forms.py +++ b/topologie/forms.py @@ -81,7 +81,7 @@ class EditPortForm(FormRevMixin, ModelForm): lent sans)""" class Meta(PortForm.Meta): fields = ['room', 'related', 'machine_interface', 'custom_profil', - 'details'] + 'state', 'details'] def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) @@ -102,6 +102,7 @@ class AddPortForm(FormRevMixin, ModelForm): 'machine_interface', 'related', 'custom_profil', + 'state', 'details' ] diff --git a/topologie/migrations/0066_auto_20180630_1855.py b/topologie/migrations/0066_auto_20180630_1855.py new file mode 100644 index 00000000..b197f568 --- /dev/null +++ b/topologie/migrations/0066_auto_20180630_1855.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-06-30 16:55 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topologie', '0065_auto_20180630_1703'), + ] + + operations = [ + migrations.AddField( + model_name='port', + name='state', + field=models.BooleanField(default=True, help_text='Etat du port Actif', verbose_name='Etat du port Actif'), + ), + migrations.AlterField( + model_name='portprofile', + name='profil_default', + field=models.CharField(blank=True, choices=[('room', 'room'), ('accespoint', 'accesspoint'), ('uplink', 'uplink'), ('asso_machine', 'asso_machine'), ('nothing', 'nothing')], max_length=32, null=True, unique=True, verbose_name='profil default'), + ), + ] diff --git a/topologie/models.py b/topologie/models.py index e4a79f74..9fa30970 100644 --- a/topologie/models.py +++ b/topologie/models.py @@ -401,7 +401,11 @@ class Port(AclMixin, RevMixin, models.Model): blank=True, null=True ) - + state = models.BooleanField( + default=True, + help_text='Etat du port Actif', + verbose_name=_("Etat du port Actif") + ) details = models.CharField(max_length=255, blank=True) class Meta: diff --git a/topologie/templates/topologie/aff_port.html b/topologie/templates/topologie/aff_port.html index 85fe901e..46398f75 100644 --- a/topologie/templates/topologie/aff_port.html +++ b/topologie/templates/topologie/aff_port.html @@ -28,30 +28,26 @@ with this program; if not, write to the Free Software Foundation, Inc.,
- - - - - - - - - - - - {% for port in port_list %} - - - - + + + + + + + + + + {% for port in port_list %} + + + + + diff --git a/topologie/templates/topologie/aff_port_profile.html b/topologie/templates/topologie/aff_port_profile.html index 7e044c0a..577ac2a5 100644 --- a/topologie/templates/topologie/aff_port_profile.html +++ b/topologie/templates/topologie/aff_port_profile.html @@ -1,3 +1,25 @@ +{% 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 © 2018 Gabriel Détraz + +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 i18n %} @@ -7,45 +29,51 @@ {% include "pagination.html" with list=port_profile_list %} {% endif %} + +
{% include "buttons/sort.html" with prefix='port' col='port' text='Port' %}{% include "buttons/sort.html" with prefix='port' col='room' text='Room' %}{% include "buttons/sort.html" with prefix='port' col='interface' text='Interface machine' %}{% include "buttons/sort.html" with prefix='port' col='related' text='Related' %}{% include "buttons/sort.html" with prefix='port' col='radius' text='Radius' %}{% include "buttons/sort.html" with prefix='port' col='vlan' text='Vlan forcé' %}Détails
{{ port.port }} - {% if port.room %}{{ port.room }}{% endif %} - - {% if port.machine_interface %} - {% can_view port.machine_interface.machine.user %} - - {{ port.machine_interface }} - - {% acl_else %} + {% include "buttons/sort.html" with prefix='port' col='port' text='Port' %}{% include "buttons/sort.html" with prefix='port' col='room' text='Room' %}{% include "buttons/sort.html" with prefix='port' col='interface' text='Interface machine' %}{% include "buttons/sort.html" with prefix='port' col='related' text='Related' %}Etat du portProfil du portDétails
{{ port.port }} + {% if port.room %}{{ port.room }}{% endif %} + + {% if port.machine_interface %} + {% can_view port.machine_interface.machine.user %} + {{ port.machine_interface }} {% acl_end %} {% endif %} @@ -70,6 +66,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% acl_end %} {% endif %} {% if port.state %} Actif{% else %}Désactivé{% endif %} {% if not port.custom_profil %}Par défaut : {% endif %}{{port.get_port_profil}} {{ port.details }} diff --git a/topologie/templates/topologie/sidebar.html b/topologie/templates/topologie/sidebar.html index c7ea6337..8652690e 100644 --- a/topologie/templates/topologie/sidebar.html +++ b/topologie/templates/topologie/sidebar.html @@ -34,8 +34,8 @@ with this program; if not, write to the Free Software Foundation, Inc., Switchs - - Profil des ports switchs + + Config des ports switchs From 4270cd32bc6e0d0008303471d405b123427db4d9 Mon Sep 17 00:00:00 2001 From: chirac Date: Sat, 30 Jun 2018 22:17:24 +0000 Subject: [PATCH 022/171] Fix langue et 802.X radius + divers --- freeradius_utils/auth.py | 39 +++++++- search/views.py | 4 +- topologie/forms.py | 6 +- topologie/migrations/0064_createprofil.py | 80 ++++++++-------- .../migrations/0067_auto_20180701_0016.py | 75 +++++++++++++++ topologie/models.py | 37 ++++---- topologie/templates/topologie/aff_port.html | 3 - .../templates/topologie/aff_port_profile.html | 94 ++++++++++++------- topologie/templates/topologie/index.html | 1 - .../topologie/index_portprofile.html | 13 ++- topologie/templates/topologie/sidebar.html | 2 +- topologie/views.py | 8 +- 12 files changed, 245 insertions(+), 117 deletions(-) create mode 100644 topologie/migrations/0067_auto_20180701_0016.py diff --git a/freeradius_utils/auth.py b/freeradius_utils/auth.py index b318ab63..8450777b 100644 --- a/freeradius_utils/auth.py +++ b/freeradius_utils/auth.py @@ -355,30 +355,47 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, port=port_number ) .first()) + # Si le port est inconnu, on place sur le vlan defaut + # Aucune information particulière ne permet de déterminer quelle + # politique à appliquer sur ce port if not port: return (sw_name, "Chambre inconnue", u'Port inconnu', VLAN_OK) # On récupère le profil du port port_profil = port.get_port_profil - # Si un vlan a été précisé, on l'utilise pour VLAN_OK + # Si un vlan a été précisé dans la config du port, + # on l'utilise pour VLAN_OK if port_profil.vlan_untagged: DECISION_VLAN = int(port_profil.vlan_untagged.vlan_id) extra_log = u"Force sur vlan " + str(DECISION_VLAN) else: DECISION_VLAN = VLAN_OK + # Si le port est désactivé, on rejette sur le vlan de déconnexion if not port.state: - return (sw_name, port.room, u'Port desactive', VLAN_NOK) + return (sw_name, port.room, u'Port desactivé', VLAN_NOK) + # Si radius est désactivé, on laisse passer if port_profil.radius_type == 'NO': return (sw_name, "", u"Pas d'authentification sur ce port" + extra_log, DECISION_VLAN) - if port_profil.radius_type == 'STRICT': + # Si le 802.1X est activé sur ce port, cela veut dire que la personne a été accept précédemment + # Par conséquent, on laisse passer sur le bon vlan + if nas_type.port_access_mode == '802.1X' and port_profil.radius_type == '802.1X': + room = port.room or "Chambre/local inconnu" + return (sw_name, room, u'Acceptation authentification 802.1X', DECISION_VLAN) + + # Sinon, cela veut dire qu'on fait de l'auth radius par mac + # Si le port est en mode strict, on vérifie que tous les users + # rattachés à ce port sont bien à jour de cotisation. Sinon on rejette (anti squattage) + # Il n'est pas possible de se connecter sur une prise strict sans adhérent à jour de cotis + # dedans + if port_profil.radius_mode == 'STRICT': room = port.room if not room: return (sw_name, "Inconnue", u'Chambre inconnue', VLAN_NOK) @@ -393,7 +410,8 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, return (sw_name, room, u'Chambre resident desactive', VLAN_NOK) # else: user OK, on passe à la verif MAC - if port_profil.radius_type == 'COMMON' or port_profil.radius_type == 'STRICT': + # Si on fait de l'auth par mac, on cherche l'interface via sa mac dans la bdd + if port_profil.radius_mode == 'COMMON' or port_profil.radius_mode == 'STRICT': # Authentification par mac interface = (Interface.objects .filter(mac_address=mac_address) @@ -402,15 +420,19 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, .first()) if not interface: room = port.room - # On essaye de register la mac + # On essaye de register la mac, si l'autocapture a été activée + # Sinon on rejette sur vlan_nok if not nas_type.autocapture_mac: return (sw_name, "", u'Machine inconnue', VLAN_NOK) + # On ne peut autocapturer que si on connait la chambre et donc l'user correspondant elif not room: return (sw_name, "Inconnue", u'Chambre et machine inconnues', VLAN_NOK) else: + # Si la chambre est vide (local club, prises en libre services) + # Impossible d'autocapturer if not room_user: room_user = User.objects.filter( Q(club__room=port.room) | Q(adherent__room=port.room) @@ -421,6 +443,8 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, u'Machine et propriétaire de la chambre ' 'inconnus', VLAN_NOK) + # Si il y a plus d'un user dans la chambre, impossible de savoir à qui + # Ajouter la machine elif room_user.count() > 1: return (sw_name, room, @@ -428,11 +452,13 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, 'dans la chambre/local -> ajout de mac ' 'automatique impossible', VLAN_NOK) + # Si l'adhérent de la chambre n'est pas à jour de cotis, pas d'autocapture elif not room_user.first().has_access(): return (sw_name, room, u'Machine inconnue et adhérent non cotisant', VLAN_NOK) + # Sinon on capture et on laisse passer sur le bon vlan else: result, reason = (room_user .first() @@ -452,6 +478,9 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, reason + str(mac_address) ), VLAN_NOK) + # L'interface a été trouvée, on vérifie qu'elle est active, sinon on reject + # Si elle n'a pas d'ipv4, on lui en met une + # Enfin on laisse passer sur le vlan pertinent else: room = port.room if not interface.is_active: diff --git a/search/views.py b/search/views.py index 0ae8470b..73333834 100644 --- a/search/views.py +++ b/search/views.py @@ -262,9 +262,9 @@ def search_single_word(word, filters, user, ) | Q( related__switch__interface__domain__name__icontains=word ) | Q( - custom_profil__name__icontains=word + custom_profile__name__icontains=word ) | Q( - custom_profil__profil_default__icontains=word + custom_profile__profil_default__icontains=word ) | Q( details__icontains=word ) diff --git a/topologie/forms.py b/topologie/forms.py index 9ac3297f..86b5c541 100644 --- a/topologie/forms.py +++ b/topologie/forms.py @@ -80,8 +80,8 @@ class EditPortForm(FormRevMixin, ModelForm): optimiser le temps de chargement avec select_related (vraiment lent sans)""" class Meta(PortForm.Meta): - fields = ['room', 'related', 'machine_interface', 'custom_profil', - 'state', 'details'] + fields = ['room', 'related', 'machine_interface', 'custom_profile', + 'state', 'details'] def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) @@ -101,7 +101,7 @@ class AddPortForm(FormRevMixin, ModelForm): 'room', 'machine_interface', 'related', - 'custom_profil', + 'custom_profile', 'state', 'details' ] diff --git a/topologie/migrations/0064_createprofil.py b/topologie/migrations/0064_createprofil.py index 189d1812..2f165386 100644 --- a/topologie/migrations/0064_createprofil.py +++ b/topologie/migrations/0064_createprofil.py @@ -5,53 +5,49 @@ from __future__ import unicode_literals from django.db import migrations +def transfer_profil(apps, schema_editor): + db_alias = schema_editor.connection.alias + port = apps.get_model("topologie", "Port") + profil = apps.get_model("topologie", "PortProfile") + vlan = apps.get_model("machines", "Vlan") + port_list = port.objects.using(db_alias).all() + profil_nothing = profil.objects.using(db_alias).create(name='nothing', profil_default='nothing', radius_type='NO') + profil_uplink = profil.objects.using(db_alias).create(name='uplink', profil_default='uplink', radius_type='NO') + profil_machine = profil.objects.using(db_alias).create(name='asso_machine', profil_default='asso_machine', radius_type='NO') + profil_room = profil.objects.using(db_alias).create(name='room', profil_default='room', radius_type='NO') + profil_borne = profil.objects.using(db_alias).create(name='accesspoint', profil_default='accesspoint', radius_type='NO') + for vlan_instance in vlan.objects.using(db_alias).all(): + if port.objects.using(db_alias).filter(vlan_force=vlan_instance): + custom_profil = profil.objects.using(db_alias).create(name='vlan-force-' + str(vlan_instance.vlan_id), radius_type='NO', vlan_untagged=vlan_instance) + port.objects.using(db_alias).filter(vlan_force=vlan_instance).update(custom_profil=custom_profil) + if port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').count() and port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').count(): + profil_room.radius_type = 'MAC-radius' + profil_room.radius_mode = 'STRICT' + common_profil = profil.objects.using(db_alias).create(name='mac-radius-common', radius_type='MAC-radius', radius_mode='COMMON') + no_rad_profil = profil.objects.using(db_alias).create(name='no-radius', radius_type='NO') + port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').update(custom_profil=common_profil) + port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').update(custom_profil=no_rad_profil) + elif port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').count() and port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').count(): + profil_room.radius_type = 'MAC-radius' + profil_room.radius_mode = 'COMMON' + strict_profil = profil.objects.using(db_alias).create(name='mac-radius-strict', radius_type='MAC-radius', radius_mode='STRICT') + no_rad_profil = profil.objects.using(db_alias).create(name='no-radius', radius_type='NO') + port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').update(custom_profil=strict_profil) + port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').update(custom_profil=no_rad_profil) + else: + strict_profil = profil.objects.using(db_alias).create(name='mac-radius-strict', radius_type='MAC-radius', radius_mode='STRICT') + common_profil = profil.objects.using(db_alias).create(name='mac-radius-common', radius_type='MAC-radius', radius_mode='COMMON') + port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').update(custom_profil=strict_profil) + port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').update(custom_profil=common_profil) + profil_room.save() + + class Migration(migrations.Migration): dependencies = [ ('topologie', '0063_port_custom_profil'), ] - def transfer_profil(apps, schema_editor): - db_alias = schema_editor.connection.alias - port = apps.get_model("topologie", "Port") - profil = apps.get_model("topologie", "PortProfile") - vlan = apps.get_model("machines", "Vlan") - port_list = port.objects.using(db_alias).all() - profil_nothing = profil.objects.using(db_alias).create(name='nothing', profil_default='nothing', radius_type='NO') - profil_uplink = profil.objects.using(db_alias).create(name='uplink', profil_default='uplink', radius_type='NO') - profil_machine = profil.objects.using(db_alias).create(name='asso_machine', profil_default='asso_machine', radius_type='NO') - profil_room = profil.objects.using(db_alias).create(name='room', profil_default='room', radius_type='NO') - profil_borne = profil.objects.using(db_alias).create(name='accesspoint', profil_default='accesspoint', radius_type='NO') - for vlan_instance in vlan.objects.using(db_alias).all(): - if port.objects.using(db_alias).filter(vlan_force=vlan_instance): - custom_profil = profil.objects.using(db_alias).create(name='vlan-force-' + str(vlan_instance.vlan_id), radius_type='NO', vlan_untagged=vlan_instance) - port.objects.using(db_alias).filter(vlan_force=vlan_instance).update(custom_profil=custom_profil) - if port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').count() and port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').count(): - profil_room.radius_type = 'MAC-radius' - profil_room.radius_mode = 'STRICT' - common_profil = profil.objects.using(db_alias).create(name='mac-radius-common', radius_type='MAC-radius', radius_mode='COMMON') - no_rad_profil = profil.objects.using(db_alias).create(name='no-radius', radius_type='NO') - port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').update(custom_profil=common_profil) - port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').update(custom_profil=no_rad_profil) - elif port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').count() and port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').count(): - profil_room.radius_type = 'MAC-radius' - profil_room.radius_mode = 'COMMON' - strict_profil = profil.objects.using(db_alias).create(name='mac-radius-strict', radius_type='MAC-radius', radius_mode='STRICT') - no_rad_profil = profil.objects.using(db_alias).create(name='no-radius', radius_type='NO') - port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').update(custom_profil=strict_profil) - port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').update(custom_profil=no_rad_profil) - else: - strict_profil = profil.objects.using(db_alias).create(name='mac-radius-strict', radius_type='MAC-radius', radius_mode='STRICT') - common_profil = profil.objects.using(db_alias).create(name='mac-radius-common', radius_type='MAC-radius', radius_mode='COMMON') - port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').update(custom_profil=strict_profil) - port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').update(custom_profil=common_profil) - profil_room.save() - - - - def untransfer_profil(apps, schema_editor): - return - operations = [ - migrations.RunPython(transfer_profil, untransfer_profil), + migrations.RunPython(transfer_profil), ] diff --git a/topologie/migrations/0067_auto_20180701_0016.py b/topologie/migrations/0067_auto_20180701_0016.py new file mode 100644 index 00000000..578ee7d6 --- /dev/null +++ b/topologie/migrations/0067_auto_20180701_0016.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-06-30 22:16 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topologie', '0066_auto_20180630_1855'), + ] + + operations = [ + migrations.RenameField( + model_name='port', + old_name='custom_profil', + new_name='custom_profile', + ), + migrations.AlterField( + model_name='port', + name='state', + field=models.BooleanField(default=True, help_text='Port state Active', verbose_name='Port State Active'), + ), + migrations.AlterField( + model_name='portprofile', + name='arp_protect', + field=models.BooleanField(default=False, help_text='Check if ip is dhcp assigned', verbose_name='Arp protect'), + ), + migrations.AlterField( + model_name='portprofile', + name='dhcp_snooping', + field=models.BooleanField(default=False, help_text='Protect against rogue dhcp', verbose_name='Dhcp snooping'), + ), + migrations.AlterField( + model_name='portprofile', + name='dhcpv6_snooping', + field=models.BooleanField(default=False, help_text='Protect against rogue dhcpv6', verbose_name='Dhcpv6 snooping'), + ), + migrations.AlterField( + model_name='portprofile', + name='flow_control', + field=models.BooleanField(default=False, help_text='Flow control', verbose_name='Flow control'), + ), + migrations.AlterField( + model_name='portprofile', + name='loop_protect', + field=models.BooleanField(default=False, help_text='Protect again loop', verbose_name='Loop Protect'), + ), + migrations.AlterField( + model_name='portprofile', + name='mac_limit', + field=models.IntegerField(blank=True, help_text='Limit of mac-address on this port', null=True, verbose_name='Mac limit'), + ), + migrations.AlterField( + model_name='portprofile', + name='ra_guard', + field=models.BooleanField(default=False, help_text='Protect against rogue ra', verbose_name='Ra guard'), + ), + migrations.AlterField( + model_name='portprofile', + name='radius_mode', + field=models.CharField(choices=[('STRICT', 'STRICT'), ('COMMON', 'COMMON')], default='COMMON', help_text='In case of mac-auth : mode common or strict on this port', 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')], help_text='Type of radius auth : inactive, mac-address or 802.1X', max_length=32, verbose_name='RADIUS type'), + ), + migrations.AlterField( + model_name='portprofile', + name='speed', + field=models.CharField(choices=[('10-half', '10-half'), ('100-half', '100-half'), ('10-full', '10-full'), ('100-full', '100-full'), ('1000-full', '1000-full'), ('auto', 'auto'), ('auto-10', 'auto-10'), ('auto-100', 'auto-100')], default='auto', help_text='Port speed limit', max_length=32, verbose_name='Speed'), + ), + ] diff --git a/topologie/models.py b/topologie/models.py index 9fa30970..029aebc8 100644 --- a/topologie/models.py +++ b/topologie/models.py @@ -395,7 +395,7 @@ class Port(AclMixin, RevMixin, models.Model): blank=True, related_name='related_port' ) - custom_profil = models.ForeignKey( + custom_profile = models.ForeignKey( 'PortProfile', on_delete=models.PROTECT, blank=True, @@ -403,8 +403,8 @@ class Port(AclMixin, RevMixin, models.Model): ) state = models.BooleanField( default=True, - help_text='Etat du port Actif', - verbose_name=_("Etat du port Actif") + help_text='Port state Active', + verbose_name=_("Port State Active") ) details = models.CharField(max_length=255, blank=True) @@ -416,7 +416,8 @@ class Port(AclMixin, RevMixin, models.Model): @cached_property def get_port_profil(self): - """Return the config profil for this port""" + """Return the config profil for this port + :returns: the profile of self (port)""" def profil_or_nothing(profil): port_profil = PortProfile.objects.filter(profil_default=profil).first() if port_profil: @@ -427,8 +428,8 @@ class Port(AclMixin, RevMixin, models.Model): nothing = PortProfile.objects.create(profil_default='nothing', name='nothing', radius_type='NO') return nothing - if self.custom_profil: - return self.custom_profil + if self.custom_profile: + return self.custom_profile elif self.related: return profil_or_nothing('uplink') elif self.machine_interface: @@ -572,57 +573,57 @@ class PortProfile(AclMixin, RevMixin, models.Model): radius_type = models.CharField( max_length=32, choices=TYPES, - help_text="Choix du type d'authentification radius : non actif, mac ou 802.1X", + help_text="Type of radius auth : inactive, mac-address or 802.1X", verbose_name=_("RADIUS type") ) radius_mode = models.CharField( max_length=32, choices=MODES, default='COMMON', - help_text="En cas d'auth par mac, auth common ou strcit sur le port", + help_text="In case of mac-auth : mode common or strict on this port", verbose_name=_("RADIUS mode") ) speed = models.CharField( max_length=32, choices=SPEED, default='auto', - help_text='Mode de transmission et vitesse du port', + help_text='Port speed limit', verbose_name=_("Speed") ) mac_limit = models.IntegerField( null=True, blank=True, - help_text='Limit du nombre de mac sur le port', + help_text='Limit of mac-address on this port', verbose_name=_("Mac limit") ) flow_control = models.BooleanField( default=False, - help_text='Gestion des débits', + help_text='Flow control', verbose_name=_("Flow control") ) dhcp_snooping = models.BooleanField( default=False, - help_text='Protection dhcp pirate', + help_text='Protect against rogue dhcp', verbose_name=_("Dhcp snooping") ) dhcpv6_snooping = models.BooleanField( default=False, - help_text='Protection dhcpv6 pirate', + help_text='Protect against rogue dhcpv6', verbose_name=_("Dhcpv6 snooping") ) arp_protect = models.BooleanField( default=False, - help_text='Verification assignation de l\'IP par dhcp', + help_text='Check if ip is dhcp assigned', verbose_name=_("Arp protect") ) ra_guard = models.BooleanField( default=False, - help_text='Protection contre ra pirate', + help_text='Protect against rogue ra', verbose_name=_("Ra guard") ) loop_protect = models.BooleanField( default=False, - help_text='Protection contre les boucles', + help_text='Protect again loop', verbose_name=_("Loop Protect") ) @@ -639,6 +640,10 @@ class PortProfile(AclMixin, RevMixin, models.Model): def security_parameters_enabled(self): return [parameter for parameter in self.security_parameters_fields if getattr(self, parameter)] + @cached_property + def security_parameters_as_str(self): + return ','.join(self.security_parameters_enabled) + def __str__(self): return self.name diff --git a/topologie/templates/topologie/aff_port.html b/topologie/templates/topologie/aff_port.html index 46398f75..ec02bcaa 100644 --- a/topologie/templates/topologie/aff_port.html +++ b/topologie/templates/topologie/aff_port.html @@ -60,9 +60,6 @@ with this program; if not, write to the Free Software Foundation, Inc., {% acl_else %} {{ port.related }} - - {% acl_else %} - {{ port.related }} {% acl_end %} {% endif %}
- - - - - - - - - - + + + + + + + + + + {% for port_profile in port_profile_list %} - - - {% endif %} - - - - + + + {% endif %} + + + + {% endfor %}
{% trans "Nom" %}{% trans "Default pour" %}{% trans "VLANs" %}{% trans "Réglages RADIUS" %}{% trans "Vitesse" %}{% trans "Mac address limit" %}{% trans "Sécurité" %}
{% trans "Name" %}{% trans "Default for" %}{% trans "VLANs" %}{% trans "RADIUS settings" %}{% trans "Speed" %}{% trans "Mac address limit" %}{% trans "Security" %}
{{port_profile.name}} {{port_profile.profil_default}} - Untagged : {{port_profile.vlan_untagged}} -
- Tagged : {{port_profile.vlan_tagged.all|join:", "}} -
- Type : {{port_profile.radius_type}} - {% if port_profile.radius_type == "MAC-radius" %} -
- Mode : {{port_profile.radius_mode}}
{{port_profile.speed}}{{port_profile.mac_limit}}{{port_profile.security_parameters_enabled|join:"
"}}
- {% 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.vlan_untagged %} + Untagged : {{port_profile.vlan_untagged}} +
+ {% endif %} + {% if port_profile.vlan_untagged %} + Tagged : {{port_profile.vlan_tagged.all|join:", "}} + {% endif %} +
+ Type : {{port_profile.radius_type}} + {% if port_profile.radius_type == "MAC-radius" %} +
+ Mode : {{port_profile.radius_mode}}
{{port_profile.speed}}{{port_profile.mac_limit}}{{port_profile.security_parameters_enabled|join:"
"}}
+ {% 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 %} +
diff --git a/topologie/templates/topologie/index.html b/topologie/templates/topologie/index.html index 7949f412..f596e6a5 100644 --- a/topologie/templates/topologie/index.html +++ b/topologie/templates/topologie/index.html @@ -25,7 +25,6 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} {% load acl %} -{% load i18n %} {% block title %}Switchs{% endblock %} diff --git a/topologie/templates/topologie/index_portprofile.html b/topologie/templates/topologie/index_portprofile.html index a4287da6..f95415c8 100644 --- a/topologie/templates/topologie/index_portprofile.html +++ b/topologie/templates/topologie/index_portprofile.html @@ -4,9 +4,8 @@ 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 +Copyright © 2018 Gabriel Détraz + 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 @@ -36,9 +35,9 @@ with this program; if not, write to the Free Software Foundation, Inc., {% trans " Add a port profile" %}
{% acl_end %} - {% include "topologie/aff_port_profile.html" with port_profile_list=port_profile_list %} -
-
-
+{% include "topologie/aff_port_profile.html" with port_profile_list=port_profile_list %} +
+
+
{% endblock %} diff --git a/topologie/templates/topologie/sidebar.html b/topologie/templates/topologie/sidebar.html index 8652690e..04ee5202 100644 --- a/topologie/templates/topologie/sidebar.html +++ b/topologie/templates/topologie/sidebar.html @@ -33,7 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc., Switchs - + Config des ports switchs diff --git a/topologie/views.py b/topologie/views.py index b7761e0c..e5453982 100644 --- a/topologie/views.py +++ b/topologie/views.py @@ -1014,11 +1014,11 @@ def del_port_profile(request, port_profile, **_kwargs): if request.method == 'POST': try: port_profile.delete() - messages.success(request, _("The port profile was successfully" - " deleted")) + messages.success(request, + _("The port profile was successfully deleted")) except ProtectedError: - messages.success(request, _("Impossible to delete the port" - " profile")) + messages.success(request, + _("Impossible to delete the port profile")) return redirect(reverse('topologie:index')) return form( {'objet': port_profile, 'objet_name': _("Port profile")}, From 3d692cbda8af48580c28953e81f3e760bfbcf995 Mon Sep 17 00:00:00 2001 From: chirac Date: Sat, 30 Jun 2018 22:23:00 +0000 Subject: [PATCH 023/171] Petit bug affichage vlans --- topologie/templates/topologie/aff_port_profile.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topologie/templates/topologie/aff_port_profile.html b/topologie/templates/topologie/aff_port_profile.html index 577ac2a5..12c8b365 100644 --- a/topologie/templates/topologie/aff_port_profile.html +++ b/topologie/templates/topologie/aff_port_profile.html @@ -52,7 +52,7 @@ with this program; if not, write to the Free Software Foundation, Inc., Untagged : {{port_profile.vlan_untagged}}
{% endif %} - {% if port_profile.vlan_untagged %} + {% if port_profile.vlan_tagged.all %} Tagged : {{port_profile.vlan_tagged.all|join:", "}} {% endif %}
+ @@ -49,7 +49,7 @@ with this program; if not, write to the Free Software Foundation, Inc., + @@ -36,6 +37,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% for role in role_list %} + + {% for role in role_list %} + + + + + + + {% endfor %} +
{% trans "Name" %} {% trans "Default for" %}{{port_profile.profil_default}} {% if port_profile.vlan_untagged %} - Untagged : {{port_profile.vlan_untagged}} + Untagged : {{port_profile.vlan_untagged}}
{% endif %} {% if port_profile.vlan_tagged.all %} diff --git a/topologie/views.py b/topologie/views.py index e5453982..a6c6ab69 100644 --- a/topologie/views.py +++ b/topologie/views.py @@ -42,11 +42,7 @@ from django.contrib.auth.decorators import login_required from django.db import IntegrityError from django.db.models import ProtectedError, Prefetch from django.core.exceptions import ValidationError -from django.contrib.staticfiles.storage import staticfiles_storage -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 @@ -105,8 +101,7 @@ from subprocess import ( PIPE ) -from os.path import isfile -from os import remove +from os.path import isfile @login_required @@ -128,13 +123,17 @@ def index(request): SortTable.TOPOLOGIE_INDEX ) - pagination_number = GeneralOption.get_cached_value('pagination_number') switch_list = re2o_paginator(request, switch_list, pagination_number) - if any(service_link.need_regen for service_link in Service_link.objects.filter(service__service_type='graph_topo')): + if any( + service_link.need_regen + for service_link in Service_link.objects.filter( + service__service_type='graph_topo') + ): make_machine_graph() - for service_link in Service_link.objects.filter(service__service_type='graph_topo'): + for service_link in Service_link.objects.filter( + service__service_type='graph_topo'): service_link.done_regen() if not isfile("/var/www/re2o/media/images/switchs.png"): @@ -150,8 +149,10 @@ def index(request): @can_view_all(PortProfile) def index_port_profile(request): pagination_number = GeneralOption.get_cached_value('pagination_number') - port_profile_list = PortProfile.objects.all().select_related('vlan_untagged') - port_profile_list = re2o_paginator(request, port_profile_list, pagination_number) + port_profile_list = PortProfile.objects.all().select_related( + 'vlan_untagged') + port_profile_list = re2o_paginator( + request, port_profile_list, pagination_number) return render( request, 'topologie/index_portprofile.html', @@ -460,7 +461,7 @@ def new_switch(request): ) domain = DomainForm( request.POST or None, - ) + ) if switch.is_valid() and interface.is_valid(): user = AssoOption.get_cached_value('utilisateur_asso') if not user: @@ -530,7 +531,7 @@ def create_ports(request, switchid): return redirect(reverse( 'topologie:index-port', kwargs={'switchid': switchid} - )) + )) return form( {'id_switch': switchid, 'topoform': port_form}, 'topologie/switch.html', @@ -548,16 +549,16 @@ def edit_switch(request, switch, switchid): request.POST or None, instance=switch, user=request.user - ) + ) interface_form = EditInterfaceForm( request.POST or None, instance=switch.interface_set.first(), user=request.user - ) + ) domain_form = DomainForm( request.POST or None, instance=switch.interface_set.first().domain - ) + ) if switch_form.is_valid() and interface_form.is_valid(): new_switch_obj = switch_form.save(commit=False) new_interface_obj = interface_form.save(commit=False) @@ -601,7 +602,7 @@ def new_ap(request): ) domain = DomainForm( request.POST or None, - ) + ) if ap.is_valid() and interface.is_valid(): user = AssoOption.get_cached_value('utilisateur_asso') if not user: @@ -656,7 +657,7 @@ def edit_ap(request, ap, **_kwargs): domain_form = DomainForm( request.POST or None, instance=ap.interface_set.first().domain - ) + ) if ap_form.is_valid() and interface_form.is_valid(): user = AssoOption.get_cached_value('utilisateur_asso') if not user: @@ -970,7 +971,7 @@ def del_constructor_switch(request, constructor_switch, **_kwargs): return form({ 'objet': constructor_switch, 'objet_name': 'Constructeur de switch' - }, 'topologie/delete.html', request) + }, 'topologie/delete.html', request) @login_required @@ -993,7 +994,8 @@ def new_port_profile(request): @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) + port_profile = EditPortProfileForm( + request.POST or None, instance=port_profile) if port_profile.is_valid(): if port_profile.changed_data: port_profile.save() @@ -1006,7 +1008,6 @@ def edit_port_profile(request, port_profile, **_kwargs): ) - @login_required @can_delete(PortProfile) def del_port_profile(request, port_profile, **_kwargs): @@ -1014,25 +1015,26 @@ def del_port_profile(request, port_profile, **_kwargs): if request.method == 'POST': try: port_profile.delete() - messages.success(request, - _("The port profile was successfully deleted")) + messages.success(request, + _("The port profile was successfully deleted")) except ProtectedError: - messages.success(request, - _("Impossible to delete the port profile")) + 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 + {'objet': port_profile, 'objet_name': _("Port profile")}, + 'topologie/delete.html', + request ) + def make_machine_graph(): """ Create the graph of switchs, machines and access points. """ dico = { 'subs': [], - 'links' : [], + 'links': [], 'alone': [], 'colors': { 'head': "#7f0505", # Color parameters for the graph @@ -1041,23 +1043,23 @@ def make_machine_graph(): 'border_bornes': "#02078e", 'head_bornes': "#25771c", 'head_server': "#1c3777" - } } + } missing = list(Switch.objects.all()) detected = [] for building in Building.objects.all(): # Visit all buildings dico['subs'].append( { - 'bat_id': building.id, - 'bat_name': building, - 'switchs': [], - 'bornes': [], - 'machines': [] + 'bat_id': building.id, + 'bat_name': building, + 'switchs': [], + 'bornes': [], + 'machines': [] } ) # Visit all switchs in this building - for switch in Switch.objects.filter(switchbay__building=building): + for switch in Switch.objects.filter(switchbay__building=building): dico['subs'][-1]['switchs'].append({ 'name': switch.main_interface().domain.name, 'nombre': switch.number, @@ -1067,7 +1069,7 @@ def make_machine_graph(): 'ports': [] }) # visit all ports of this switch and add the switchs linked to it - for port in switch.ports.filter(related__isnull=False): + for port in switch.ports.filter(related__isnull=False): dico['subs'][-1]['switchs'][-1]['ports'].append({ 'numero': port.port, 'related': port.related.switch.main_interface().domain.name @@ -1085,50 +1087,58 @@ def make_machine_graph(): dico['subs'][-1]['machines'].append({ 'name': server.short_name, 'switch': server.switch()[0].main_interface().domain.name, - 'port': Port.objects.filter(machine_interface__machine=server)[0].port + 'port': Port.objects.filter( + machine_interface__machine=server + )[0].port }) # While the list of forgotten ones is not empty while missing: if missing[0].ports.count(): # The switch is not empty - links, new_detected = recursive_switchs(missing[0], None, [missing[0]]) + links, new_detected = recursive_switchs( + missing[0], None, [missing[0]]) for link in links: dico['links'].append(link) # Update the lists of missings and already detected switchs - missing=[i for i in missing if i not in new_detected] + missing = [i for i in missing if i not in new_detected] detected += new_detected - else: # If the switch have no ports, don't explore it and hop to the next one + # If the switch have no ports, don't explore it and hop to the next one + else: del missing[0] # Switchs that are not connected or not in a building - for switch in Switch.objects.filter(switchbay__isnull=True).exclude(ports__related__isnull=False): + for switch in Switch.objects.filter( + switchbay__isnull=True).exclude(ports__related__isnull=False): dico['alone'].append({ 'id': switch.id, 'name': switch.main_interface().domain.name - }) + }) + # generate the dot file + dot_data = generate_dot(dico, 'topologie/graph_switch.dot') - dot_data=generate_dot(dico,'topologie/graph_switch.dot') # generate the dot file - - f = tempfile.NamedTemporaryFile(mode='w+', encoding='utf-8', delete=False) # Create a temporary file to store the dot data + # Create a temporary file to store the dot data + f = tempfile.NamedTemporaryFile(mode='w+', encoding='utf-8', delete=False) with f: - f.write(dot_data) + f.write(dot_data) unflatten = Popen( # unflatten the graph to make it look better - ["unflatten","-l", "3", f.name], + ["unflatten", "-l", "3", f.name], stdout=PIPE ) - image = Popen( # pipe the result of the first command into the second + Popen( # pipe the result of the first command into the second ["dot", "-Tpng", "-o", MEDIA_ROOT + "/images/switchs.png"], stdin=unflatten.stdout, stdout=PIPE ) -def generate_dot(data,template): + +def generate_dot(data, template): """create the dot file :param data: dictionary passed to the template :param template: path to the dot template :return: all the lines of the dot file""" t = loader.get_template(template) - if not isinstance(t, Template) and not (hasattr(t, 'template') and isinstance(t.template, Template)): + if not isinstance(t, Template) and \ + not (hasattr(t, 'template') and isinstance(t.template, Template)): raise Exception("Le template par défaut de Django n'est pas utilisé." "Cela peut mener à des erreurs de rendu." "Vérifiez les paramètres") @@ -1136,27 +1146,40 @@ def generate_dot(data,template): dot = t.render(c) return(dot) + def recursive_switchs(switch_start, switch_before, detected): """Visit the switch and travel to the switchs linked to it. :param switch_start: the switch to begin the visit on - :param switch_before: the switch that you come from. None if switch_start is the first one - :param detected: list of all switchs already visited. None if switch_start is the first one - :return: A list of all the links found and a list of all the switchs visited""" + :param switch_before: the switch that you come from. + None if switch_start is the first one + :param detected: list of all switchs already visited. + None if switch_start is the first one + :return: A list of all the links found and a list of + all the switchs visited + """ detected.append(switch_start) - links_return=[] # list of dictionaries of the links to be detected - for port in switch_start.ports.filter(related__isnull=False): # create links to every switchs below - if port.related.switch != switch_before and port.related.switch != port.switch and port.related.switch not in detected: # Not the switch that we come from, not the current switch + links_return = [] # list of dictionaries of the links to be detected + # create links to every switchs below + for port in switch_start.ports.filter(related__isnull=False): + # Not the switch that we come from, not the current switch + if port.related.switch != switch_before \ + and port.related.switch != port.switch \ + and port.related.switch not in detected: links = { # Dictionary of a link - 'depart':switch_start.id, - 'arrive':port.related.switch.id + 'depart': switch_start.id, + 'arrive': port.related.switch.id } links_return.append(links) # Add current and below levels links - for port in switch_start.ports.filter(related__isnull=False): # go down on every related switchs - if port.related.switch not in detected: # The switch at the end of this link has not been visited - links_down, detected = recursive_switchs(port.related.switch, switch_start, detected) # explore it and get the results - for link in links_down: # Add the non empty links to the current list + # go down on every related switchs + for port in switch_start.ports.filter(related__isnull=False): + # The switch at the end of this link has not been visited + if port.related.switch not in detected: + # explore it and get the results + links_down, detected = recursive_switchs( + port.related.switch, switch_start, detected) + # Add the non empty links to the current list + for link in links_down: if link: - links_return.append(link) + links_return.append(link) return (links_return, detected) - From 6c4877b52adf8a0984e3957ae511fe62d6f1cada Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Thu, 2 Aug 2018 20:23:52 +0200 Subject: [PATCH 025/171] Squash des migrations de port_profile --- topologie/migrations/0061_portprofile.py | 78 ++++++++++++++++--- .../migrations/0062_auto_20180627_0123.py | 25 ------ .../migrations/0063_port_custom_profil.py | 21 ----- topologie/migrations/0064_createprofil.py | 53 ------------- .../migrations/0065_auto_20180630_1703.py | 23 ------ .../migrations/0066_auto_20180630_1855.py | 25 ------ .../migrations/0067_auto_20180701_0016.py | 75 ------------------ 7 files changed, 67 insertions(+), 233 deletions(-) delete mode 100644 topologie/migrations/0062_auto_20180627_0123.py delete mode 100644 topologie/migrations/0063_port_custom_profil.py delete mode 100644 topologie/migrations/0064_createprofil.py delete mode 100644 topologie/migrations/0065_auto_20180630_1703.py delete mode 100644 topologie/migrations/0066_auto_20180630_1855.py delete mode 100644 topologie/migrations/0067_auto_20180701_0016.py diff --git a/topologie/migrations/0061_portprofile.py b/topologie/migrations/0061_portprofile.py index 7e130163..88e2d9ab 100644 --- a/topologie/migrations/0061_portprofile.py +++ b/topologie/migrations/0061_portprofile.py @@ -7,6 +7,43 @@ import django.db.models.deletion import re2o.mixins +def transfer_profil(apps, schema_editor): + db_alias = schema_editor.connection.alias + port = apps.get_model("topologie", "Port") + profil = apps.get_model("topologie", "PortProfile") + vlan = apps.get_model("machines", "Vlan") + port_list = port.objects.using(db_alias).all() + profil_nothing = profil.objects.using(db_alias).create(name='nothing', profil_default='nothing', radius_type='NO') + profil_uplink = profil.objects.using(db_alias).create(name='uplink', profil_default='uplink', radius_type='NO') + profil_machine = profil.objects.using(db_alias).create(name='asso_machine', profil_default='asso_machine', radius_type='NO') + profil_room = profil.objects.using(db_alias).create(name='room', profil_default='room', radius_type='NO') + profil_borne = profil.objects.using(db_alias).create(name='accesspoint', profil_default='accesspoint', radius_type='NO') + for vlan_instance in vlan.objects.using(db_alias).all(): + if port.objects.using(db_alias).filter(vlan_force=vlan_instance): + custom_profile = profil.objects.using(db_alias).create(name='vlan-force-' + str(vlan_instance.vlan_id), radius_type='NO', vlan_untagged=vlan_instance) + port.objects.using(db_alias).filter(vlan_force=vlan_instance).update(custom_profile=custom_profile) + if port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').count() and port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').count(): + profil_room.radius_type = 'MAC-radius' + profil_room.radius_mode = 'STRICT' + common_profil = profil.objects.using(db_alias).create(name='mac-radius-common', radius_type='MAC-radius', radius_mode='COMMON') + no_rad_profil = profil.objects.using(db_alias).create(name='no-radius', radius_type='NO') + port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').update(custom_profile=common_profil) + port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').update(custom_profile=no_rad_profil) + elif port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').count() and port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').count(): + profil_room.radius_type = 'MAC-radius' + profil_room.radius_mode = 'COMMON' + strict_profil = profil.objects.using(db_alias).create(name='mac-radius-strict', radius_type='MAC-radius', radius_mode='STRICT') + no_rad_profil = profil.objects.using(db_alias).create(name='no-radius', radius_type='NO') + port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').update(custom_profile=strict_profil) + port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').update(custom_profile=no_rad_profil) + else: + strict_profil = profil.objects.using(db_alias).create(name='mac-radius-strict', radius_type='MAC-radius', radius_mode='STRICT') + common_profil = profil.objects.using(db_alias).create(name='mac-radius-common', radius_type='MAC-radius', radius_mode='COMMON') + port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').update(custom_profile=strict_profil) + port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').update(custom_profile=common_profil) + profil_room.save() + + class Migration(migrations.Migration): dependencies = [ @@ -20,17 +57,17 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=255, verbose_name='Name')), - ('profil_default', models.CharField(blank=True, choices=[('room', 'room'), ('nothing', 'nothing'), ('accespoint', 'accesspoint'), ('uplink', 'uplink'), ('asso_machine', 'asso_machine')], max_length=32, null=True, unique=True, verbose_name='profil default')), - ('radius_type', models.CharField(choices=[('NO', 'NO'), ('802.1X', '802.1X'), ('MAC-radius', 'MAC-radius')], max_length=32, verbose_name='RADIUS type')), - ('radius_mode', models.CharField(choices=[('STRICT', 'STRICT'), ('COMMON', 'COMMON')], default='COMMON', max_length=32, verbose_name='RADIUS mode')), - ('speed', models.CharField(choices=[('10-half', '10-half'), ('100-half', '100-half'), ('10-full', '10-full'), ('100-full', '100-full'), ('1000-full', '1000-full'), ('auto', 'auto'), ('auto-10', 'auto-10'), ('auto-100', 'auto-100')], default='auto', help_text='Mode de transmission et vitesse du port', max_length=32, verbose_name='Speed')), - ('mac_limit', models.IntegerField(blank=True, help_text='Limit du nombre de mac sur le port', null=True, verbose_name='Mac limit')), - ('flow_control', models.BooleanField(default=False, help_text='Gestion des débits', verbose_name='Flow control')), - ('dhcp_snooping', models.BooleanField(default=False, help_text='Protection dhcp pirate', verbose_name='Dhcp snooping')), - ('dhcpv6_snooping', models.BooleanField(default=False, help_text='Protection dhcpv6 pirate', verbose_name='Dhcpv6 snooping')), - ('arp_protect', models.BooleanField(default=False, help_text="Verification assignation de l'IP par dhcp", verbose_name='Arp protect')), - ('ra_guard', models.BooleanField(default=False, help_text='Protection contre ra pirate', verbose_name='Ra guard')), - ('loop_protect', models.BooleanField(default=False, help_text='Protection contre les boucles', verbose_name='Loop Protect')), + ('profil_default', models.CharField(blank=True, choices=[('room', 'room'), ('accespoint', 'accesspoint'), ('uplink', 'uplink'), ('asso_machine', 'asso_machine'), ('nothing', 'nothing')], max_length=32, null=True, unique=True, verbose_name='profil default')), + ('radius_type', models.CharField(choices=[('NO', 'NO'), ('802.1X', '802.1X'), ('MAC-radius', 'MAC-radius')], help_text='Type of radius auth : inactive, mac-address or 802.1X', max_length=32, verbose_name='RADIUS type')), + ('radius_mode', models.CharField(choices=[('STRICT', 'STRICT'), ('COMMON', 'COMMON')], default='COMMON', help_text='In case of mac-auth : mode common or strict on this port', max_length=32, verbose_name='RADIUS mode')), + ('speed', models.CharField(choices=[('10-half', '10-half'), ('100-half', '100-half'), ('10-full', '10-full'), ('100-full', '100-full'), ('1000-full', '1000-full'), ('auto', 'auto'), ('auto-10', 'auto-10'), ('auto-100', 'auto-100')], default='auto', help_text='Port speed limit', max_length=32, verbose_name='Speed')), + ('mac_limit', models.IntegerField(blank=True, help_text='Limit of mac-address on this port', null=True, verbose_name='Mac limit')), + ('flow_control', models.BooleanField(default=False, help_text='Flow control', verbose_name='Flow control')), + ('dhcp_snooping', models.BooleanField(default=False, help_text='Protect against rogue dhcp', verbose_name='Dhcp snooping')), + ('dhcpv6_snooping', models.BooleanField(default=False, help_text='Protect against rogue dhcpv6', verbose_name='Dhcpv6 snooping')), + ('arp_protect', models.BooleanField(default=False, help_text='Check if ip is dhcp assigned', verbose_name='Arp protect')), + ('ra_guard', models.BooleanField(default=False, help_text='Protect against rogue ra', verbose_name='Ra guard')), + ('loop_protect', models.BooleanField(default=False, help_text='Protect again loop', verbose_name='Loop Protect')), ('vlan_tagged', models.ManyToManyField(blank=True, related_name='vlan_tagged', to='machines.Vlan', verbose_name='VLAN(s) tagged')), ('vlan_untagged', 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')), ], @@ -41,4 +78,23 @@ class Migration(migrations.Migration): }, bases=(re2o.mixins.AclMixin, re2o.mixins.RevMixin, models.Model), ), + migrations.AddField( + model_name='port', + name='custom_profile', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='topologie.PortProfile'), + ), + migrations.RunPython(transfer_profil), + migrations.RemoveField( + model_name='port', + name='radius', + ), + migrations.RemoveField( + model_name='port', + name='vlan_force', + ), + migrations.AddField( + model_name='port', + name='state', + field=models.BooleanField(default=True, help_text='Port state Active', verbose_name='Port State Active'), + ), ] diff --git a/topologie/migrations/0062_auto_20180627_0123.py b/topologie/migrations/0062_auto_20180627_0123.py deleted file mode 100644 index b8135de8..00000000 --- a/topologie/migrations/0062_auto_20180627_0123.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10.7 on 2018-06-26 23:23 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('topologie', '0061_portprofile'), - ] - - operations = [ - migrations.AlterField( - model_name='portprofile', - name='radius_mode', - field=models.CharField(choices=[('STRICT', 'STRICT'), ('COMMON', 'COMMON')], default='COMMON', help_text="En cas d'auth par mac, auth common ou strcit sur le port", 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')], help_text="Choix du type d'authentification radius : non actif, mac ou 802.1X", max_length=32, verbose_name='RADIUS type'), - ), - ] diff --git a/topologie/migrations/0063_port_custom_profil.py b/topologie/migrations/0063_port_custom_profil.py deleted file mode 100644 index 15feebce..00000000 --- a/topologie/migrations/0063_port_custom_profil.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10.7 on 2018-06-28 07:49 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('topologie', '0062_auto_20180627_0123'), - ] - - operations = [ - migrations.AddField( - model_name='port', - name='custom_profil', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='topologie.PortProfile'), - ), - ] diff --git a/topologie/migrations/0064_createprofil.py b/topologie/migrations/0064_createprofil.py deleted file mode 100644 index 2f165386..00000000 --- a/topologie/migrations/0064_createprofil.py +++ /dev/null @@ -1,53 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10.7 on 2017-12-31 19:53 -from __future__ import unicode_literals - -from django.db import migrations - - -def transfer_profil(apps, schema_editor): - db_alias = schema_editor.connection.alias - port = apps.get_model("topologie", "Port") - profil = apps.get_model("topologie", "PortProfile") - vlan = apps.get_model("machines", "Vlan") - port_list = port.objects.using(db_alias).all() - profil_nothing = profil.objects.using(db_alias).create(name='nothing', profil_default='nothing', radius_type='NO') - profil_uplink = profil.objects.using(db_alias).create(name='uplink', profil_default='uplink', radius_type='NO') - profil_machine = profil.objects.using(db_alias).create(name='asso_machine', profil_default='asso_machine', radius_type='NO') - profil_room = profil.objects.using(db_alias).create(name='room', profil_default='room', radius_type='NO') - profil_borne = profil.objects.using(db_alias).create(name='accesspoint', profil_default='accesspoint', radius_type='NO') - for vlan_instance in vlan.objects.using(db_alias).all(): - if port.objects.using(db_alias).filter(vlan_force=vlan_instance): - custom_profil = profil.objects.using(db_alias).create(name='vlan-force-' + str(vlan_instance.vlan_id), radius_type='NO', vlan_untagged=vlan_instance) - port.objects.using(db_alias).filter(vlan_force=vlan_instance).update(custom_profil=custom_profil) - if port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').count() and port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').count(): - profil_room.radius_type = 'MAC-radius' - profil_room.radius_mode = 'STRICT' - common_profil = profil.objects.using(db_alias).create(name='mac-radius-common', radius_type='MAC-radius', radius_mode='COMMON') - no_rad_profil = profil.objects.using(db_alias).create(name='no-radius', radius_type='NO') - port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').update(custom_profil=common_profil) - port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').update(custom_profil=no_rad_profil) - elif port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').count() and port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').count(): - profil_room.radius_type = 'MAC-radius' - profil_room.radius_mode = 'COMMON' - strict_profil = profil.objects.using(db_alias).create(name='mac-radius-strict', radius_type='MAC-radius', radius_mode='STRICT') - no_rad_profil = profil.objects.using(db_alias).create(name='no-radius', radius_type='NO') - port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').update(custom_profil=strict_profil) - port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').update(custom_profil=no_rad_profil) - else: - strict_profil = profil.objects.using(db_alias).create(name='mac-radius-strict', radius_type='MAC-radius', radius_mode='STRICT') - common_profil = profil.objects.using(db_alias).create(name='mac-radius-common', radius_type='MAC-radius', radius_mode='COMMON') - port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').update(custom_profil=strict_profil) - port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').update(custom_profil=common_profil) - profil_room.save() - - -class Migration(migrations.Migration): - - dependencies = [ - ('topologie', '0063_port_custom_profil'), - ] - - operations = [ - migrations.RunPython(transfer_profil), - ] diff --git a/topologie/migrations/0065_auto_20180630_1703.py b/topologie/migrations/0065_auto_20180630_1703.py deleted file mode 100644 index 9fed2d83..00000000 --- a/topologie/migrations/0065_auto_20180630_1703.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10.7 on 2018-06-30 15:03 -from __future__ import unicode_literals - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('topologie', '0064_createprofil'), - ] - - operations = [ - migrations.RemoveField( - model_name='port', - name='radius', - ), - migrations.RemoveField( - model_name='port', - name='vlan_force', - ), - ] diff --git a/topologie/migrations/0066_auto_20180630_1855.py b/topologie/migrations/0066_auto_20180630_1855.py deleted file mode 100644 index b197f568..00000000 --- a/topologie/migrations/0066_auto_20180630_1855.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10.7 on 2018-06-30 16:55 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('topologie', '0065_auto_20180630_1703'), - ] - - operations = [ - migrations.AddField( - model_name='port', - name='state', - field=models.BooleanField(default=True, help_text='Etat du port Actif', verbose_name='Etat du port Actif'), - ), - migrations.AlterField( - model_name='portprofile', - name='profil_default', - field=models.CharField(blank=True, choices=[('room', 'room'), ('accespoint', 'accesspoint'), ('uplink', 'uplink'), ('asso_machine', 'asso_machine'), ('nothing', 'nothing')], max_length=32, null=True, unique=True, verbose_name='profil default'), - ), - ] diff --git a/topologie/migrations/0067_auto_20180701_0016.py b/topologie/migrations/0067_auto_20180701_0016.py deleted file mode 100644 index 578ee7d6..00000000 --- a/topologie/migrations/0067_auto_20180701_0016.py +++ /dev/null @@ -1,75 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10.7 on 2018-06-30 22:16 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('topologie', '0066_auto_20180630_1855'), - ] - - operations = [ - migrations.RenameField( - model_name='port', - old_name='custom_profil', - new_name='custom_profile', - ), - migrations.AlterField( - model_name='port', - name='state', - field=models.BooleanField(default=True, help_text='Port state Active', verbose_name='Port State Active'), - ), - migrations.AlterField( - model_name='portprofile', - name='arp_protect', - field=models.BooleanField(default=False, help_text='Check if ip is dhcp assigned', verbose_name='Arp protect'), - ), - migrations.AlterField( - model_name='portprofile', - name='dhcp_snooping', - field=models.BooleanField(default=False, help_text='Protect against rogue dhcp', verbose_name='Dhcp snooping'), - ), - migrations.AlterField( - model_name='portprofile', - name='dhcpv6_snooping', - field=models.BooleanField(default=False, help_text='Protect against rogue dhcpv6', verbose_name='Dhcpv6 snooping'), - ), - migrations.AlterField( - model_name='portprofile', - name='flow_control', - field=models.BooleanField(default=False, help_text='Flow control', verbose_name='Flow control'), - ), - migrations.AlterField( - model_name='portprofile', - name='loop_protect', - field=models.BooleanField(default=False, help_text='Protect again loop', verbose_name='Loop Protect'), - ), - migrations.AlterField( - model_name='portprofile', - name='mac_limit', - field=models.IntegerField(blank=True, help_text='Limit of mac-address on this port', null=True, verbose_name='Mac limit'), - ), - migrations.AlterField( - model_name='portprofile', - name='ra_guard', - field=models.BooleanField(default=False, help_text='Protect against rogue ra', verbose_name='Ra guard'), - ), - migrations.AlterField( - model_name='portprofile', - name='radius_mode', - field=models.CharField(choices=[('STRICT', 'STRICT'), ('COMMON', 'COMMON')], default='COMMON', help_text='In case of mac-auth : mode common or strict on this port', 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')], help_text='Type of radius auth : inactive, mac-address or 802.1X', max_length=32, verbose_name='RADIUS type'), - ), - migrations.AlterField( - model_name='portprofile', - name='speed', - field=models.CharField(choices=[('10-half', '10-half'), ('100-half', '100-half'), ('10-full', '10-full'), ('100-full', '100-full'), ('1000-full', '1000-full'), ('auto', 'auto'), ('auto-10', 'auto-10'), ('auto-100', 'auto-100')], default='auto', help_text='Port speed limit', max_length=32, verbose_name='Speed'), - ), - ] From b356954f6c5b99f1049f00b208da945db92040cb Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Thu, 2 Aug 2018 20:47:05 +0200 Subject: [PATCH 026/171] Fix de l'historique pour l'affichage des port_profile --- .../templates/topologie/aff_port_profile.html | 107 +++++++++--------- 1 file changed, 54 insertions(+), 53 deletions(-) diff --git a/topologie/templates/topologie/aff_port_profile.html b/topologie/templates/topologie/aff_port_profile.html index 65446236..4ba59ff5 100644 --- a/topologie/templates/topologie/aff_port_profile.html +++ b/topologie/templates/topologie/aff_port_profile.html @@ -22,64 +22,65 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load acl %} {% load i18n %} +{% load logs_extra %}
-{% if port_profile_list.paginator %} -{% include "pagination.html" with list=port_profile_list %} -{% endif %} + {% if port_profile_list.paginator %} + {% include "pagination.html" with list=port_profile_list %} + {% endif %} - - - - - - - - - - - - - - {% for port_profile in port_profile_list %} - - - - - - {% endif %} - - - - - - {% endfor %} -
{% trans "Name" %}{% trans "Default for" %}{% trans "VLANs" %}{% trans "RADIUS settings" %}{% trans "Speed" %}{% trans "Mac address limit" %}{% trans "Security" %}
{{port_profile.name}}{{port_profile.profil_default}} - {% if port_profile.vlan_untagged %} - Untagged : {{port_profile.vlan_untagged}} -
- {% endif %} - {% if port_profile.vlan_tagged.all %} - Tagged : {{port_profile.vlan_tagged.all|join:", "}} - {% endif %} -
- Type : {{port_profile.radius_type}} - {% if port_profile.radius_type == "MAC-radius" %} -
- Mode : {{port_profile.radius_mode}}
{{port_profile.speed}}{{port_profile.mac_limit}}{{port_profile.security_parameters_enabled|join:"
"}}
- {% 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 %} -
+ + + + + + + + + + + + + + {% for port_profile in port_profile_list %} + + + + + + {% endif %} + + + + + + {% endfor %} +
{% trans "Name" %}{% trans "Default for" %}{% trans "VLANs" %}{% trans "RADIUS settings" %}{% trans "Speed" %}{% trans "Mac address limit" %}{% trans "Security" %}
{{port_profile.name}}{{port_profile.profil_default}} + {% if port_profile.vlan_untagged %} + Untagged : {{port_profile.vlan_untagged}} +
+ {% endif %} + {% if port_profile.vlan_tagged.all %} + Tagged : {{port_profile.vlan_tagged.all|join:", "}} + {% endif %} +
+ Type : {{port_profile.radius_type}} + {% if port_profile.radius_type == "MAC-radius" %} +
+ Mode : {{port_profile.radius_mode}}
{{port_profile.speed}}{{port_profile.mac_limit}}{{port_profile.security_parameters_enabled|join:"
"}}
+ {% history_button port_profile %} + {% 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 %} + {% if port_profile_list.paginator %} + {% include "pagination.html" with list=port_profile_list %} + {% endif %}
From 5dab70ccee3b1b0d8c692b0d477eca8588e3ec49 Mon Sep 17 00:00:00 2001 From: Charlie Jacomme Date: Sat, 23 Jun 2018 16:39:03 +0200 Subject: [PATCH 027/171] Add machines.models.Role --- machines/admin.py | 7 ++ machines/forms.py | 35 ++++++++++ machines/models.py | 20 ++++++ machines/templates/machines/aff_role.html | 49 +++++++++++++ machines/templates/machines/index_role.html | 41 +++++++++++ machines/templates/machines/machine.html | 6 ++ machines/templates/machines/sidebar.html | 6 ++ machines/urls.py | 8 ++- machines/views.py | 77 +++++++++++++++++++++ re2o/templatetags/acl.py | 1 + 10 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 machines/templates/machines/aff_role.html create mode 100644 machines/templates/machines/index_role.html diff --git a/machines/admin.py b/machines/admin.py index 26d7a6a3..0c83e9ab 100644 --- a/machines/admin.py +++ b/machines/admin.py @@ -42,6 +42,7 @@ from .models import ( SshFp, Nas, Service, + Role, OuverturePort, Ipv6List, OuverturePortList, @@ -146,6 +147,11 @@ class ServiceAdmin(VersionAdmin): """ Admin view of a ServiceAdmin object """ list_display = ('service_type', 'min_time_regen', 'regular_time_regen') +class RoleAdmin(VersionAdmin): + """ Admin view of a RoleAdmin object """ + list_display = ('role_type') + + admin.site.register(Machine, MachineAdmin) admin.site.register(MachineType, MachineTypeAdmin) @@ -162,6 +168,7 @@ admin.site.register(IpList, IpListAdmin) admin.site.register(Interface, InterfaceAdmin) admin.site.register(Domain, DomainAdmin) admin.site.register(Service, ServiceAdmin) +admin.site.register(Role, RoleAdmin) admin.site.register(Vlan, VlanAdmin) admin.site.register(Ipv6List, Ipv6ListAdmin) admin.site.register(Nas, NasAdmin) diff --git a/machines/forms.py b/machines/forms.py index 23c2aa39..c8584d30 100644 --- a/machines/forms.py +++ b/machines/forms.py @@ -53,6 +53,7 @@ from .models import ( Txt, DName, Ns, + Role, Service, Vlan, Srv, @@ -497,6 +498,40 @@ class DelNasForm(FormRevMixin, Form): self.fields['nas'].queryset = Nas.objects.all() +class RoleForm(FormRevMixin, ModelForm): + """Ajout et edition d'un role""" + class Meta: + model = Role + fields = '__all__' + + def __init__(self, *args, **kwargs): + prefix = kwargs.pop('prefix', self.Meta.model.__name__) + super(RoleForm, self).__init__(*args, prefix=prefix, **kwargs) + self.fields['servers'].queryset = (Interface.objects.all() + .select_related( + 'domain__extension' + )) + + +class DelRoleForm(FormRevMixin, Form): + """Suppression d'un ou plusieurs service""" + role = forms.ModelMultipleChoiceField( + queryset=Role.objects.none(), + label="Roles actuels", + widget=forms.CheckboxSelectMultiple + ) + + def __init__(self, *args, **kwargs): + instances = kwargs.pop('instances', None) + super(DelRoleForm, self).__init__(*args, **kwargs) + if instances: + self.fields['role'].queryset = instances + else: + self.fields['role'].queryset = role.objects.all() + + + + class ServiceForm(FormRevMixin, ModelForm): """Ajout et edition d'une classe de service : dns, dhcp, etc""" class Meta: diff --git a/machines/models.py b/machines/models.py index 7be76e74..4b3ee891 100644 --- a/machines/models.py +++ b/machines/models.py @@ -1441,6 +1441,26 @@ class IpList(RevMixin, AclMixin, models.Model): return self.ipv4 + +class Role(RevMixin, AclMixin, models.Model): + """ Definition d'un role (routeur principal, routeur de backkup)""" + """ Sert à la génération automatique de la conf des serveurs""" + PRETTY_NAME = "Roles des serveurs" + + role_type = models.CharField(max_length=255, unique=True) + servers = models.ManyToManyField('Interface') + + class Meta: + permissions = ( + ("view_role", "Peut voir un objet service"), + ) + + def save(self, *args, **kwargs): + super(Role, self).save(*args, **kwargs) + + def __str__(self): + return str(self.role_type) + class Service(RevMixin, AclMixin, models.Model): """ Definition d'un service (dhcp, dns, etc)""" PRETTY_NAME = "Services à générer (dhcp, dns, etc)" diff --git a/machines/templates/machines/aff_role.html b/machines/templates/machines/aff_role.html new file mode 100644 index 00000000..f914cd24 --- /dev/null +++ b/machines/templates/machines/aff_role.html @@ -0,0 +1,49 @@ +{% 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 %} + + + + + + + + + + + {% for role in role_list %} + + + + + + {% endfor %} +
Nom du roleServeurs inclus
{{ role.role_type }}{% for serv in role.servers.all %}{{ serv }}, {% endfor %} + {% can_edit role %} + {% include 'buttons/edit.html' with href='machines:edit-role' id=role.id %} + {% acl_end %} + {% include 'buttons/history.html' with href='machines:history' name='role' id=role.id %} +
+ diff --git a/machines/templates/machines/index_role.html b/machines/templates/machines/index_role.html new file mode 100644 index 00000000..93a99577 --- /dev/null +++ b/machines/templates/machines/index_role.html @@ -0,0 +1,41 @@ +{% extends "machines/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 %} + +{% block title %}Machines{% endblock %} + +{% block content %} +

Liste des roles

+ {% can_create Role %} + Ajouter un role + {% acl_end %} + Supprimer un ou plusieurs role + {% include "machines/aff_role.html" with role_list=role_list %} +
+
+{% endblock %} + diff --git a/machines/templates/machines/machine.html b/machines/templates/machines/machine.html index 7ec4212a..cd00ba0c 100644 --- a/machines/templates/machines/machine.html +++ b/machines/templates/machines/machine.html @@ -71,6 +71,8 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endif %} {% if sshfpform %} {% bootstrap_form_errors sshfpform %} +{% if roleform %} + {% bootstrap_form_errors roleform %} {% endif %} {% if vlanform %} {% bootstrap_form_errors vlanform %} @@ -148,6 +150,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,

Service

{% massive_bootstrap_form serviceform 'servers' %} {% endif %} + {% if roleform %} +

Role

+ {% massive_bootstrap_form roleform 'servers' %} + {% endif %} {% if vlanform %}

Vlan

{% bootstrap_form vlanform %} diff --git a/machines/templates/machines/sidebar.html b/machines/templates/machines/sidebar.html index 5a0f975d..68031f29 100644 --- a/machines/templates/machines/sidebar.html +++ b/machines/templates/machines/sidebar.html @@ -68,6 +68,12 @@ with this program; if not, write to the Free Software Foundation, Inc., Services (dhcp, dns...) {% acl_end %} + {% can_view_all Role %} + + + Roles des serveurs + + {% acl_end %} {% can_view_all OuverturePortList %} diff --git a/machines/urls.py b/machines/urls.py index ce0a7a78..8c670308 100644 --- a/machines/urls.py +++ b/machines/urls.py @@ -21,7 +21,7 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. """machines.urls -The defined URLs for the Cotisations app +The defined URLs for the Machines app """ from __future__ import unicode_literals @@ -125,6 +125,12 @@ urlpatterns = [ name='edit-service'), url(r'^del_service/$', views.del_service, name='del-service'), url(r'^index_service/$', views.index_service, name='index-service'), + url(r'^add_role/$', views.add_role, name='add-role'), + url(r'^edit_role/(?P[0-9]+)$', + views.edit_role, + name='edit-role'), + url(r'^del_role/$', views.del_role, name='del-role'), + url(r'^index_role/$', views.index_role, name='index-role'), url(r'^add_vlan/$', views.add_vlan, name='add-vlan'), url(r'^edit_vlan/(?P[0-9]+)$', views.edit_vlan, name='edit-vlan'), url(r'^del_vlan/$', views.del_vlan, name='del-vlan'), diff --git a/machines/views.py b/machines/views.py index 398b9250..134560ba 100644 --- a/machines/views.py +++ b/machines/views.py @@ -101,6 +101,8 @@ from .forms import ( DelMxForm, VlanForm, DelVlanForm, + RoleForm, + DelRoleForm, ServiceForm, DelServiceForm, SshFpForm, @@ -122,6 +124,7 @@ from .models import ( Mx, Ns, Domain, + Role, Service, Service_link, Vlan, @@ -1141,6 +1144,65 @@ def del_alias(request, interface, interfaceid): ) +@login_required +@can_create(Role) +def add_role(request): + """ View used to add a Role object """ + role = RoleForm(request.POST or None) + if role.is_valid(): + role.save() + messages.success(request, "Cet enregistrement role a été ajouté") + return redirect(reverse('machines:index-role')) + return form( + {'roleform': role, 'action_name': 'Créer'}, + 'machines/machine.html', + request + ) + + +@login_required +@can_edit(Role) +def edit_role(request, role_instance, **_kwargs): + """ View used to edit a Role object """ + role = RoleForm(request.POST or None, instance=role_instance) + if role.is_valid(): + if role.changed_data: + role.save() + messages.success(request, "Role modifié") + return redirect(reverse('machines:index-role')) + return form( + {'roleform': role, 'action_name': 'Editer'}, + 'machines/machine.html', + request + ) + + +@login_required +@can_delete_set(Role) +def del_role(request, instances): + """ View used to delete a Service object """ + role = DelRoleForm(request.POST or None, instances=instances) + if role.is_valid(): + role_dels = role.cleaned_data['role'] + for role_del in role_dels: + try: + role_del.delete() + messages.success(request, "Le role a été supprimée") + except ProtectedError: + messages.error( + request, + ("Erreur le role suivant %s ne peut être supprimé" + % role_del) + ) + return redirect(reverse('machines:index-role')) + return form( + {'roleform': role, 'action_name': 'Supprimer'}, + 'machines/machine.html', + request + ) + + + @login_required @can_create(Service) def add_service(request): @@ -1481,6 +1543,21 @@ def index_ipv6(request, interface, interfaceid): ) +@login_required +@can_view_all(Role) +def index_role(request): + """ View used to display the list of existing roles """ + role_list = (Role.objects + .prefetch_related( + 'servers__domain__extension' + ).all()) + return render( + request, + 'machines/index_role.html', + {'role_list': role_list} + ) + + @login_required @can_view_all(Service) def index_service(request): diff --git a/re2o/templatetags/acl.py b/re2o/templatetags/acl.py index 9a439f88..fe13c5ac 100644 --- a/re2o/templatetags/acl.py +++ b/re2o/templatetags/acl.py @@ -79,6 +79,7 @@ from django.contrib.contenttypes.models import ContentType register = template.Library() + def get_model(model_name): """Retrieve the model object from its name""" splitted = model_name.split('.') From b70f6fd97a6ec158969db033fffbdf107d9f9bc3 Mon Sep 17 00:00:00 2001 From: Charlie Jacomme Date: Sat, 23 Jun 2018 16:47:24 +0200 Subject: [PATCH 028/171] migration for Role --- machines/migrations/0083_role.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 machines/migrations/0083_role.py diff --git a/machines/migrations/0083_role.py b/machines/migrations/0083_role.py new file mode 100644 index 00000000..7609a5ac --- /dev/null +++ b/machines/migrations/0083_role.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-06-23 14:07 +from __future__ import unicode_literals + +from django.db import migrations, models +import re2o.mixins + + +class Migration(migrations.Migration): + + dependencies = [ + ('machines', '0082_auto_20180621_1524'), + ] + + operations = [ + migrations.CreateModel( + name='Role', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('role_type', models.CharField(max_length=255, unique=True)), + ('servers', models.ManyToManyField(to='machines.Interface')), + ], + options={ + 'permissions': (('view_role', 'Peut voir un objet service'),), + }, + bases=(re2o.mixins.RevMixin, re2o.mixins.AclMixin, models.Model), + ), + ] From 793cf302ad168bb1e6bde93a30cd00ceeb0ad8f7 Mon Sep 17 00:00:00 2001 From: chirac Date: Sat, 23 Jun 2018 16:51:15 +0200 Subject: [PATCH 029/171] Fix bug admin --- machines/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/machines/admin.py b/machines/admin.py index 0c83e9ab..af721ff9 100644 --- a/machines/admin.py +++ b/machines/admin.py @@ -149,7 +149,7 @@ class ServiceAdmin(VersionAdmin): class RoleAdmin(VersionAdmin): """ Admin view of a RoleAdmin object """ - list_display = ('role_type') + pass From 35eb94540490a3f9b1ca5b358ec0aae7f96f5572 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Wed, 11 Jul 2018 18:49:27 +0200 Subject: [PATCH 030/171] =?UTF-8?q?Notion=20de=20role=20sp=C3=A9cifique=20?= =?UTF-8?q?pour=20retrouver=20le=20bon=20role?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- machines/models.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/machines/models.py b/machines/models.py index 4b3ee891..4c528c45 100644 --- a/machines/models.py +++ b/machines/models.py @@ -1447,8 +1447,31 @@ class Role(RevMixin, AclMixin, models.Model): """ Sert à la génération automatique de la conf des serveurs""" PRETTY_NAME = "Roles des serveurs" + ROLE = ( + ('dhcp-server', 'dhcp-server'), + ('switch-conf-server', 'switch-conf-server'), + ('dns-recursif-server', 'dns-recursif-server'), + ('ntp-server', 'ntp-server'), + ('radius-server', 'radius-server'), + ('ntp-server', 'ntp-server'), + ('log-server', 'log-server'), + ('ldap-master-server', 'ldap-master-server'), + ('ldap-backup-server', 'ldap-backup-server'), + ('smtp-server', 'smtp-server'), + ('postgresql-server', 'postgresql-server'), + ('mysql-server', 'mysql-server'), + ('sql-client', 'sql-client'), + ('gateway', 'gateway'), + ) + role_type = models.CharField(max_length=255, unique=True) servers = models.ManyToManyField('Interface') + specific_role = models.CharField( + choices=ROLE, + null=True, + blank=True, + max_length=32, + ) class Meta: permissions = ( From 5dc59035f564b8b9f2d8641d9e15d25d48d8a475 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Wed, 11 Jul 2018 19:46:13 +0200 Subject: [PATCH 031/171] =?UTF-8?q?Cr=C3=A9e=20sp=C3=A9cific=20role,=20l'u?= =?UTF-8?q?tilise=20pour=20get=20l'ip=20du=20serveur=20des=20config=20swit?= =?UTF-8?q?chs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/serializers.py | 44 +++++++++++++++++++ .../migrations/0094_role_specific_role.py | 20 +++++++++ machines/models.py | 13 ++++++ machines/templates/machines/aff_role.html | 2 + 4 files changed, 79 insertions(+) create mode 100644 machines/migrations/0094_role_specific_role.py diff --git a/api/serializers.py b/api/serializers.py index 398f2b19..ffb3aaba 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -622,6 +622,50 @@ class ServiceRegenSerializer(NamespacedHMSerializer): 'api_url': {'view_name': 'serviceregen-detail'} } +# Switches et ports + +class InterfaceVlanSerializer(NamespacedHMSerializer): + domain = serializers.CharField(read_only=True) + ipv4 = serializers.CharField(read_only=True) + ipv6 = Ipv6ListSerializer(read_only=True, many=True) + vlan_id = serializers.IntegerField(source='type.ip_type.vlan.vlan_id', read_only=True) + + class Meta: + model = machines.Interface + fields = ('ipv4', 'ipv6', 'domain', 'vlan_id') + +class InterfaceRoleSerializer(NamespacedHMSerializer): + interface = InterfaceVlanSerializer(source='machine.interface_set', read_only=True, many=True) + + class Meta: + model = machines.Interface + fields = ('interface',) + + +class RoleSerializer(NamespacedHMSerializer): + """Serialize `machines.models.OuverturePort` objects. + """ + servers = InterfaceRoleSerializer(read_only=True, many=True) + + class Meta: + model = machines.Role + fields = ('role_type', 'servers', 'specific_role') + + +class VlanPortSerializer(NamespacedHMSerializer): + class Meta: + model = machines.Vlan + fields = ('vlan_id', 'name') + + +class ProfilSerializer(NamespacedHMSerializer): + vlan_untagged = VlanSerializer(read_only=True) + vlan_tagged = VlanPortSerializer(read_only=True, many=True) + + class Meta: + model = topologie.PortProfile + fields = ('name', 'profil_default', 'vlan_untagged', 'vlan_tagged', 'radius_type', 'radius_mode', 'speed', 'mac_limit', 'flow_control', 'dhcp_snooping', 'dhcpv6_snooping', 'arp_protect', 'ra_guard', 'loop_protect', 'vlan_untagged', 'vlan_tagged') + # LOCAL EMAILS diff --git a/machines/migrations/0094_role_specific_role.py b/machines/migrations/0094_role_specific_role.py new file mode 100644 index 00000000..73cade7b --- /dev/null +++ b/machines/migrations/0094_role_specific_role.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-07-11 16:49 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('machines', '0093_merge_20180710_0226'), + ] + + operations = [ + migrations.AddField( + model_name='role', + name='specific_role', + field=models.CharField(blank=True, choices=[('dhcp-server', 'dhcp-server'), ('switch-conf-server', 'switch-conf-server'), ('dns-recursif-server', 'dns-recursif-server'), ('ntp-server', 'ntp-server'), ('radius-server', 'radius-server'), ('ntp-server', 'ntp-server'), ('log-server', 'log-server'), ('ldap-master-server', 'ldap-master-server'), ('ldap-backup-server', 'ldap-backup-server'), ('smtp-server', 'smtp-server'), ('postgresql-server', 'postgresql-server'), ('mysql-server', 'mysql-server'), ('sql-client', 'sql-client'), ('gateway', 'gateway')], max_length=32, null=True), + ), + ] diff --git a/machines/models.py b/machines/models.py index 4c528c45..2f401301 100644 --- a/machines/models.py +++ b/machines/models.py @@ -1478,6 +1478,19 @@ class Role(RevMixin, AclMixin, models.Model): ("view_role", "Peut voir un objet service"), ) + @classmethod + def get_instance(cls, machineid, *_args, **_kwargs): + """Get the Machine instance with machineid. + :param userid: The id + :return: The user + """ + return cls.objects.get(pk=machineid) + + @classmethod + def interface_for_roletype(cls, roletype): + """Return interfaces for a roletype""" + return Interface.objects.filter(role=cls.objects.filter(specific_role=roletype)) + def save(self, *args, **kwargs): super(Role, self).save(*args, **kwargs) diff --git a/machines/templates/machines/aff_role.html b/machines/templates/machines/aff_role.html index f914cd24..691cc0c2 100644 --- a/machines/templates/machines/aff_role.html +++ b/machines/templates/machines/aff_role.html @@ -28,6 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
Nom du roleRole spécifique Serveurs inclus
{{ role.role_type }}{{ role.specific_role }} {% for serv in role.servers.all %}{{ serv }}, {% endfor %} {% can_edit role %} From ed91cdcfe507aa6579ce34f4f54c3909f3c69c33 Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Thu, 12 Jul 2018 16:24:45 +0200 Subject: [PATCH 032/171] Recolle les fixations ensemble --- CHANGELOG.md | 8 ++++++++ machines/migrations/{0083_role.py => 0084_role.py} | 2 +- ...4_role_specific_role.py => 0085_role_specific_role.py} | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) rename machines/migrations/{0083_role.py => 0084_role.py} (93%) rename machines/migrations/{0094_role_specific_role.py => 0085_role_specific_role.py} (94%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b9020e1..dcf03168 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -120,3 +120,11 @@ Don't forget to run migrations, several settings previously in the `preferences` in their own Payment models. To have a closer look on how the payments works, please go to the wiki. + +## MR xxx: Add role models + +Adds the Role model. +You need to ensure that your database character set is utf-8. +```sql +ALTER DATABASE re2o CHARACTER SET utf8; +``` diff --git a/machines/migrations/0083_role.py b/machines/migrations/0084_role.py similarity index 93% rename from machines/migrations/0083_role.py rename to machines/migrations/0084_role.py index 7609a5ac..bb113813 100644 --- a/machines/migrations/0083_role.py +++ b/machines/migrations/0084_role.py @@ -9,7 +9,7 @@ import re2o.mixins class Migration(migrations.Migration): dependencies = [ - ('machines', '0082_auto_20180621_1524'), + ('machines', '0083_remove_duplicate_rights'), ] operations = [ diff --git a/machines/migrations/0094_role_specific_role.py b/machines/migrations/0085_role_specific_role.py similarity index 94% rename from machines/migrations/0094_role_specific_role.py rename to machines/migrations/0085_role_specific_role.py index 73cade7b..d861f45b 100644 --- a/machines/migrations/0094_role_specific_role.py +++ b/machines/migrations/0085_role_specific_role.py @@ -8,7 +8,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('machines', '0093_merge_20180710_0226'), + ('machines', '0084_role'), ] operations = [ From 0b86dc1ec498d22f776542401fe1b9b0d8778bce Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Tue, 17 Jul 2018 21:26:42 +0200 Subject: [PATCH 033/171] TODO : offrir des cours d'anglais au cr@ns. --- CHANGELOG.md | 2 +- machines/forms.py | 11 ++- machines/models.py | 97 ++++++++++++----------- machines/templates/machines/aff_role.html | 46 ++++++----- machines/views.py | 70 ++++++++-------- 5 files changed, 118 insertions(+), 108 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dcf03168..f390ba09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -121,7 +121,7 @@ in their own Payment models. To have a closer look on how the payments works, please go to the wiki. -## MR xxx: Add role models +## MR 182: Add role models Adds the Role model. You need to ensure that your database character set is utf-8. diff --git a/machines/forms.py b/machines/forms.py index c8584d30..ecffcc71 100644 --- a/machines/forms.py +++ b/machines/forms.py @@ -37,6 +37,7 @@ from __future__ import unicode_literals from django.forms import ModelForm, Form from django import forms +from django.utils.translation import ugettext_lazy as _l from re2o.field_permissions import FieldPermissionFormMixin from re2o.mixins import FormRevMixin @@ -499,7 +500,7 @@ class DelNasForm(FormRevMixin, Form): class RoleForm(FormRevMixin, ModelForm): - """Ajout et edition d'un role""" + """Add and edit role.""" class Meta: model = Role fields = '__all__' @@ -514,10 +515,10 @@ class RoleForm(FormRevMixin, ModelForm): class DelRoleForm(FormRevMixin, Form): - """Suppression d'un ou plusieurs service""" + """Deletion of one or several roles.""" role = forms.ModelMultipleChoiceField( queryset=Role.objects.none(), - label="Roles actuels", + label=_l("Current roles"), widget=forms.CheckboxSelectMultiple ) @@ -527,9 +528,7 @@ class DelRoleForm(FormRevMixin, Form): if instances: self.fields['role'].queryset = instances else: - self.fields['role'].queryset = role.objects.all() - - + self.fields['role'].queryset = Role.objects.all() class ServiceForm(FormRevMixin, ModelForm): diff --git a/machines/models.py b/machines/models.py index 2f401301..6ce84adb 100644 --- a/machines/models.py +++ b/machines/models.py @@ -42,6 +42,7 @@ from django.forms import ValidationError from django.utils.functional import cached_property from django.utils import timezone from django.core.validators import MaxValueValidator +from django.utils.translation import ugettext_lazy as _l from macaddress.fields import MACAddressField @@ -158,7 +159,7 @@ class Machine(RevMixin, FieldPermissionModelMixin, models.Model): user_request, *args, **kwargs - )[0]): + )[0]): return False, (u"Vous ne pouvez pas éditer une machine " "d'un autre user que vous sans droit") return True, None @@ -176,7 +177,7 @@ class Machine(RevMixin, FieldPermissionModelMixin, models.Model): user_request, *args, **kwargs - )[0]): + )[0]): return False, (u"Vous ne pouvez pas éditer une machine " "d'un autre user que vous sans droit") return True, None @@ -338,10 +339,10 @@ class IpType(RevMixin, AclMixin, models.Model): return else: for ipv6 in Ipv6List.objects.filter( - interface__in=Interface.objects.filter( - type__in=MachineType.objects.filter(ip_type=self) - ) - ): + interface__in=Interface.objects.filter( + type__in=MachineType.objects.filter(ip_type=self) + ) + ): ipv6.check_and_replace_prefix(prefix=self.prefix_v6) def clean(self): @@ -713,7 +714,7 @@ class Srv(RevMixin, AclMixin, models.Model): choices=( (TCP, 'TCP'), (UDP, 'UDP'), - ), + ), default=TCP, ) extension = models.ForeignKey('Extension', on_delete=models.PROTECT) @@ -1047,7 +1048,7 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): user_request, *args, **kwargs - )[0]): + )[0]): return False, (u"Vous ne pouvez pas éditer une machine " "d'un autre user que vous sans droit") return True, None @@ -1064,7 +1065,7 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): user_request, *args, **kwargs - )[0]): + )[0]): return False, (u"Vous ne pouvez pas éditer une machine " "d'un autre user que vous sans droit") return True, None @@ -1165,7 +1166,7 @@ class Ipv6List(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): user_request, *args, **kwargs - )[0]): + )[0]): return False, (u"Vous ne pouvez pas éditer une machine " "d'un autre user que vous sans droit") return True, None @@ -1182,7 +1183,7 @@ class Ipv6List(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): user_request, *args, **kwargs - )[0]): + )[0]): return False, (u"Vous ne pouvez pas éditer une machine " "d'un autre user que vous sans droit") return True, None @@ -1358,11 +1359,11 @@ class Domain(RevMixin, AclMixin, models.Model): return False, (u"Vous ne pouvez pas ajouter un alias à une " "machine d'un autre user que vous sans droit") if Domain.objects.filter( - cname__in=Domain.objects.filter( - interface_parent__in=(interface.machine.user - .user_interfaces()) - ) - ).count() >= max_lambdauser_aliases: + cname__in=Domain.objects.filter( + interface_parent__in=(interface.machine.user + .user_interfaces()) + ) + ).count() >= max_lambdauser_aliases: return False, (u"Vous avez atteint le maximum d'alias " "autorisés que vous pouvez créer vous même " "(%s) " % max_lambdauser_aliases) @@ -1441,27 +1442,25 @@ class IpList(RevMixin, AclMixin, models.Model): return self.ipv4 - class Role(RevMixin, AclMixin, models.Model): - """ Definition d'un role (routeur principal, routeur de backkup)""" - """ Sert à la génération automatique de la conf des serveurs""" - PRETTY_NAME = "Roles des serveurs" + """Define the role of a machine. + Allow automated generation of the server configuration. + """ ROLE = ( - ('dhcp-server', 'dhcp-server'), - ('switch-conf-server', 'switch-conf-server'), - ('dns-recursif-server', 'dns-recursif-server'), - ('ntp-server', 'ntp-server'), - ('radius-server', 'radius-server'), - ('ntp-server', 'ntp-server'), - ('log-server', 'log-server'), - ('ldap-master-server', 'ldap-master-server'), - ('ldap-backup-server', 'ldap-backup-server'), - ('smtp-server', 'smtp-server'), - ('postgresql-server', 'postgresql-server'), - ('mysql-server', 'mysql-server'), - ('sql-client', 'sql-client'), - ('gateway', 'gateway'), + ('dhcp-server', _l('DHCP server')), + ('switch-conf-server', _l('Switches configuration server')), + ('dns-recursif-server', _l('Recursive DNS server')), + ('ntp-server', _l('NTP server')), + ('radius-server', _l('Radius server')), + ('log-server', _l('Log server')), + ('ldap-master-server', _l('LDAP master server')), + ('ldap-backup-server', _l('LDAP backup server')), + ('smtp-server', _l('SMTP server')), + ('postgresql-server', _l('postgreSQL server')), + ('mysql-server', _l('mySQL server')), + ('sql-client', _l('SQL client')), + ('gateway', _l('Gatewaw')), ) role_type = models.CharField(max_length=255, unique=True) @@ -1475,21 +1474,28 @@ class Role(RevMixin, AclMixin, models.Model): class Meta: permissions = ( - ("view_role", "Peut voir un objet service"), + ("view_role", _l("Can view a role.")), ) + verbose_name = _l("Server role") @classmethod - def get_instance(cls, machineid, *_args, **_kwargs): - """Get the Machine instance with machineid. - :param userid: The id - :return: The user + def get_instance(cls, roleid, *_args, **_kwargs): + """Get the Role instance with roleid. + + Args: + roleid: The id + + Returns: + The role. """ - return cls.objects.get(pk=machineid) + return cls.objects.get(pk=roleid) @classmethod def interface_for_roletype(cls, roletype): """Return interfaces for a roletype""" - return Interface.objects.filter(role=cls.objects.filter(specific_role=roletype)) + return Interface.objects.filter( + role=cls.objects.filter(specific_role=roletype) + ) def save(self, *args, **kwargs): super(Role, self).save(*args, **kwargs) @@ -1497,6 +1503,7 @@ class Role(RevMixin, AclMixin, models.Model): def __str__(self): return str(self.role_type) + class Service(RevMixin, AclMixin, models.Model): """ Definition d'un service (dhcp, dns, etc)""" PRETTY_NAME = "Services à générer (dhcp, dns, etc)" @@ -1527,8 +1534,8 @@ class Service(RevMixin, AclMixin, models.Model): """ Django ne peut créer lui meme les relations manytomany avec table intermediaire explicite""" for serv in servers.exclude( - pk__in=Interface.objects.filter(service=self) - ): + pk__in=Interface.objects.filter(service=self) + ): link = Service_link(service=self, server=serv) link.save() Service_link.objects.filter(service=self).exclude(server__in=servers)\ @@ -1686,7 +1693,7 @@ class OuverturePort(RevMixin, AclMixin, models.Model): choices=( (TCP, 'TCP'), (UDP, 'UDP'), - ), + ), default=TCP, ) io = models.CharField( @@ -1694,7 +1701,7 @@ class OuverturePort(RevMixin, AclMixin, models.Model): choices=( (IN, 'IN'), (OUT, 'OUT'), - ), + ), default=OUT, ) diff --git a/machines/templates/machines/aff_role.html b/machines/templates/machines/aff_role.html index 691cc0c2..f83a4adb 100644 --- a/machines/templates/machines/aff_role.html +++ b/machines/templates/machines/aff_role.html @@ -23,29 +23,31 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load acl %} +{% load i18n %} - - - - - - - - - - - {% for role in role_list %} + +
Nom du roleRole spécifiqueServeurs inclus
+ - - - - + + + + + - {% endfor %} -
{{ role.role_type }}{{ role.specific_role }}{% for serv in role.servers.all %}{{ serv }}, {% endfor %} - {% can_edit role %} - {% include 'buttons/edit.html' with href='machines:edit-role' id=role.id %} - {% acl_end %} - {% include 'buttons/history.html' with href='machines:history' name='role' id=role.id %} - {% trans "Role name" %}{% trans "Specific role" %}{% trans "Servers" %}
+
{{ role.role_type }}{{ role.specific_role }}{% for serv in role.servers.all %}{{ serv }}, {% endfor %} + {% can_edit role %} + {% include 'buttons/edit.html' with href='machines:edit-role' id=role.id %} + {% acl_end %} + {% include 'buttons/history.html' with href='machines:history' name='role' id=role.id %} +
diff --git a/machines/views.py b/machines/views.py index 134560ba..975cac08 100644 --- a/machines/views.py +++ b/machines/views.py @@ -40,6 +40,7 @@ from django.contrib.auth.decorators import login_required, permission_required from django.db.models import ProtectedError, F from django.forms import modelformset_factory from django.views.decorators.csrf import csrf_exempt +from django.utils.translation import ugettext as _ from rest_framework.renderers import JSONRenderer @@ -181,14 +182,14 @@ def generate_ipv4_engine(is_type_tt): """ return ( 'new Bloodhound( {{' - 'datumTokenizer: Bloodhound.tokenizers.obj.whitespace( "value" ),' - 'queryTokenizer: Bloodhound.tokenizers.whitespace,' - 'local: choices_ipv4[ $( "#{type_id}" ).val() ],' - 'identify: function( obj ) {{ return obj.key; }}' + 'datumTokenizer: Bloodhound.tokenizers.obj.whitespace( "value" ),' + 'queryTokenizer: Bloodhound.tokenizers.whitespace,' + 'local: choices_ipv4[ $( "#{type_id}" ).val() ],' + 'identify: function( obj ) {{ return obj.key; }}' '}} )' - ).format( - type_id=f_type_id(is_type_tt) - ) + ).format( + type_id=f_type_id(is_type_tt) + ) def generate_ipv4_match_func(is_type_tt): @@ -196,17 +197,17 @@ def generate_ipv4_match_func(is_type_tt): """ return ( 'function(q, sync) {{' - 'if (q === "") {{' - 'var first = choices_ipv4[$("#{type_id}").val()].slice(0, 5);' - 'first = first.map( function (obj) {{ return obj.key; }} );' - 'sync(engine_ipv4.get(first));' - '}} else {{' - 'engine_ipv4.search(q, sync);' - '}}' + 'if (q === "") {{' + 'var first = choices_ipv4[$("#{type_id}").val()].slice(0, 5);' + 'first = first.map( function (obj) {{ return obj.key; }} );' + 'sync(engine_ipv4.get(first));' + '}} else {{' + 'engine_ipv4.search(q, sync);' '}}' - ).format( - type_id=f_type_id(is_type_tt) - ) + '}}' + ).format( + type_id=f_type_id(is_type_tt) + ) def generate_ipv4_mbf_param(form_obj, is_type_tt): @@ -1168,10 +1169,10 @@ def edit_role(request, role_instance, **_kwargs): if role.is_valid(): if role.changed_data: role.save() - messages.success(request, "Role modifié") + messages.success(request, _("Role updated")) return redirect(reverse('machines:index-role')) return form( - {'roleform': role, 'action_name': 'Editer'}, + {'roleform': role, 'action_name': _('Edit')}, 'machines/machine.html', request ) @@ -1187,22 +1188,22 @@ def del_role(request, instances): for role_del in role_dels: try: role_del.delete() - messages.success(request, "Le role a été supprimée") + messages.success(request, _("The role has been deleted.")) except ProtectedError: messages.error( request, - ("Erreur le role suivant %s ne peut être supprimé" - % role_del) + (_("Error: The following role cannot be deleted: %(role)") + % {'role': role_del} + ) ) return redirect(reverse('machines:index-role')) return form( - {'roleform': role, 'action_name': 'Supprimer'}, + {'roleform': role, 'action_name': _('Delete')}, 'machines/machine.html', request ) - @login_required @can_create(Service) def add_service(request): @@ -1548,9 +1549,9 @@ def index_ipv6(request, interface, interfaceid): def index_role(request): """ View used to display the list of existing roles """ role_list = (Role.objects - .prefetch_related( - 'servers__domain__extension' - ).all()) + .prefetch_related( + 'servers__domain__extension' + ).all()) return render( request, 'machines/index_role.html', @@ -1647,12 +1648,12 @@ def add_portlist(request): """ View used to add a port policy """ port_list = EditOuverturePortListForm(request.POST or None) port_formset = modelformset_factory( - OuverturePort, - fields=('begin', 'end', 'protocole', 'io'), - extra=0, - can_delete=True, - min_num=1, - validate_min=True, + OuverturePort, + fields=('begin', 'end', 'protocole', 'io'), + extra=0, + can_delete=True, + min_num=1, + validate_min=True, )(request.POST or None, queryset=OuverturePort.objects.none()) if port_list.is_valid() and port_formset.is_valid(): pl = port_list.save() @@ -1699,11 +1700,12 @@ def configure_ports(request, interface_instance, **_kwargs): ) -## Framework Rest +# Framework Rest class JSONResponse(HttpResponse): """ Class to build a JSON response. Used for API """ + def __init__(self, data, **kwargs): content = JSONRenderer().render(data) kwargs['content_type'] = 'application/json' From d7af7d64a20e0c6ad3320fc201bab525bb7dac09 Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Tue, 17 Jul 2018 21:42:03 +0200 Subject: [PATCH 034/171] Une migration de Role pour les gouverner toutes. --- machines/migrations/0084_role.py | 5 ++--- .../migrations/0085_role_specific_role.py | 20 ------------------- 2 files changed, 2 insertions(+), 23 deletions(-) delete mode 100644 machines/migrations/0085_role_specific_role.py diff --git a/machines/migrations/0084_role.py b/machines/migrations/0084_role.py index bb113813..49343809 100644 --- a/machines/migrations/0084_role.py +++ b/machines/migrations/0084_role.py @@ -19,10 +19,9 @@ class Migration(migrations.Migration): ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('role_type', models.CharField(max_length=255, unique=True)), ('servers', models.ManyToManyField(to='machines.Interface')), + ('specific_role', models.CharField(blank=True, choices=[('dhcp-server', 'DHCP server'), ('switch-conf-server', 'Switches configuration server'), ('dns-recursif-server', 'Recursive DNS server'), ('ntp-server', 'NTP server'), ('radius-server', 'Radius server'), ('log-server', 'Log server'), ('ldap-master-server', 'LDAP master server'), ('ldap-backup-server', 'LDAP backup server'), ('smtp-server', 'SMTP server'), ('postgresql-server', 'postgreSQL server'), ('mysql-server', 'mySQL server'), ('sql-client', 'SQL client'), ('gateway', 'Gatewaw')], max_length=32, null=True)) ], - options={ - 'permissions': (('view_role', 'Peut voir un objet service'),), - }, + options={'permissions': (('view_role', 'Can view a role.'),), 'verbose_name': 'Server role'}, bases=(re2o.mixins.RevMixin, re2o.mixins.AclMixin, models.Model), ), ] diff --git a/machines/migrations/0085_role_specific_role.py b/machines/migrations/0085_role_specific_role.py deleted file mode 100644 index d861f45b..00000000 --- a/machines/migrations/0085_role_specific_role.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10.7 on 2018-07-11 16:49 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('machines', '0084_role'), - ] - - operations = [ - migrations.AddField( - model_name='role', - name='specific_role', - field=models.CharField(blank=True, choices=[('dhcp-server', 'dhcp-server'), ('switch-conf-server', 'switch-conf-server'), ('dns-recursif-server', 'dns-recursif-server'), ('ntp-server', 'ntp-server'), ('radius-server', 'radius-server'), ('ntp-server', 'ntp-server'), ('log-server', 'log-server'), ('ldap-master-server', 'ldap-master-server'), ('ldap-backup-server', 'ldap-backup-server'), ('smtp-server', 'smtp-server'), ('postgresql-server', 'postgresql-server'), ('mysql-server', 'mysql-server'), ('sql-client', 'sql-client'), ('gateway', 'gateway')], max_length=32, null=True), - ), - ] From 5e5bd89885f40114b8a8badbba601780ef4eb06a Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Tue, 17 Jul 2018 21:47:20 +0200 Subject: [PATCH 035/171] =?UTF-8?q?Anglais=20=C3=A9pisode=202=20:=20L'atta?= =?UTF-8?q?que=20des=20templates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- machines/templates/machines/index_role.html | 7 ++++--- machines/templates/machines/sidebar.html | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/machines/templates/machines/index_role.html b/machines/templates/machines/index_role.html index 93a99577..86c36a09 100644 --- a/machines/templates/machines/index_role.html +++ b/machines/templates/machines/index_role.html @@ -25,15 +25,16 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} {% load acl %} +{% load i18n %} {% block title %}Machines{% endblock %} {% block content %} -

Liste des roles

+

{% trans "Roles list" %}

{% can_create Role %} - Ajouter un role + {% trans "Add role"%} {% acl_end %} - Supprimer un ou plusieurs role + {% trans "Delete one or several roles" %} {% include "machines/aff_role.html" with role_list=role_list %}

diff --git a/machines/templates/machines/sidebar.html b/machines/templates/machines/sidebar.html index 68031f29..75badb6b 100644 --- a/machines/templates/machines/sidebar.html +++ b/machines/templates/machines/sidebar.html @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load acl %} +{% load i18n %} {% block sidebar %} {% can_view_all Machine %} @@ -71,7 +72,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% can_view_all Role %} - Roles des serveurs + {% trans "Server roles" %} {% acl_end %} {% can_view_all OuverturePortList %} From 7166318e19048cbf6b38c7e82ec2b7c5827338a9 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Thu, 12 Jul 2018 00:11:55 +0200 Subject: [PATCH 036/171] Repare le get_instance de role --- api/serializers.py | 7 ++++++- machines/models.py | 7 +++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/api/serializers.py b/api/serializers.py index ffb3aaba..db3f1b7b 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -338,10 +338,15 @@ class OptionalMachineSerializer(NamespacedHMSerializer): class OptionalTopologieSerializer(NamespacedHMSerializer): """Serialize `preferences.models.OptionalTopologie` objects. """ + switchs_management_interface_ip= serializers.CharField(read_only=True) + class Meta: model = preferences.OptionalTopologie fields = ('radius_general_policy', 'vlan_decision_ok', - 'vlan_decision_nok') + 'vlan_decision_nok', 'switchs_ip_type', 'switchs_web_management', + 'switchs_web_management_ssl', 'switchs_rest_management', + 'switchs_management_utils', 'switchs_management_interface_ip', + 'provision_switchs_enabled') class GeneralOptionSerializer(NamespacedHMSerializer): diff --git a/machines/models.py b/machines/models.py index 6ce84adb..e981bf10 100644 --- a/machines/models.py +++ b/machines/models.py @@ -1497,6 +1497,13 @@ class Role(RevMixin, AclMixin, models.Model): role=cls.objects.filter(specific_role=roletype) ) + @classmethod + def all_interfaces_for_roletype(cls, roletype): + """Return all interfaces for a roletype""" + return Interface.objects.filter( + machine__interface__role=cls.objects.filter(specific_role=roletype) + ) + def save(self, *args, **kwargs): super(Role, self).save(*args, **kwargs) From 4679bbe604b69b4984e815bc57c5ebeeae0e9705 Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Tue, 17 Jul 2018 22:27:58 +0200 Subject: [PATCH 037/171] Retire des modifications qui viendrons avec les switchs. --- api/serializers.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/api/serializers.py b/api/serializers.py index db3f1b7b..cdcf41f7 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -338,15 +338,11 @@ class OptionalMachineSerializer(NamespacedHMSerializer): class OptionalTopologieSerializer(NamespacedHMSerializer): """Serialize `preferences.models.OptionalTopologie` objects. """ - switchs_management_interface_ip= serializers.CharField(read_only=True) class Meta: model = preferences.OptionalTopologie fields = ('radius_general_policy', 'vlan_decision_ok', - 'vlan_decision_nok', 'switchs_ip_type', 'switchs_web_management', - 'switchs_web_management_ssl', 'switchs_rest_management', - 'switchs_management_utils', 'switchs_management_interface_ip', - 'provision_switchs_enabled') + 'vlan_decision_nok') class GeneralOptionSerializer(NamespacedHMSerializer): From 6202ddd0fac9f3906a956f1a8618e11c6aeb50a8 Mon Sep 17 00:00:00 2001 From: chirac Date: Sun, 29 Jul 2018 17:21:58 +0200 Subject: [PATCH 038/171] =?UTF-8?q?D=C3=A9plac=C3=A9=20dans=20une=20MR=20u?= =?UTF-8?q?lt=C3=A9rieure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/serializers.py | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/api/serializers.py b/api/serializers.py index cdcf41f7..839a1047 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -623,41 +623,6 @@ class ServiceRegenSerializer(NamespacedHMSerializer): 'api_url': {'view_name': 'serviceregen-detail'} } -# Switches et ports - -class InterfaceVlanSerializer(NamespacedHMSerializer): - domain = serializers.CharField(read_only=True) - ipv4 = serializers.CharField(read_only=True) - ipv6 = Ipv6ListSerializer(read_only=True, many=True) - vlan_id = serializers.IntegerField(source='type.ip_type.vlan.vlan_id', read_only=True) - - class Meta: - model = machines.Interface - fields = ('ipv4', 'ipv6', 'domain', 'vlan_id') - -class InterfaceRoleSerializer(NamespacedHMSerializer): - interface = InterfaceVlanSerializer(source='machine.interface_set', read_only=True, many=True) - - class Meta: - model = machines.Interface - fields = ('interface',) - - -class RoleSerializer(NamespacedHMSerializer): - """Serialize `machines.models.OuverturePort` objects. - """ - servers = InterfaceRoleSerializer(read_only=True, many=True) - - class Meta: - model = machines.Role - fields = ('role_type', 'servers', 'specific_role') - - -class VlanPortSerializer(NamespacedHMSerializer): - class Meta: - model = machines.Vlan - fields = ('vlan_id', 'name') - class ProfilSerializer(NamespacedHMSerializer): vlan_untagged = VlanSerializer(read_only=True) From 22662a2b2292f30d5b364d7f77a706d0554bddc1 Mon Sep 17 00:00:00 2001 From: Maxime Bombar Date: Fri, 3 Aug 2018 10:24:22 +0200 Subject: [PATCH 039/171] [preferences/EditAssoOptionForm] commits c1f55e797 and 78b950c39 removed references to payment. This form was forgotten. --- preferences/forms.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/preferences/forms.py b/preferences/forms.py index 99910f9c..02463103 100644 --- a/preferences/forms.py +++ b/preferences/forms.py @@ -151,24 +151,6 @@ class EditAssoOptionForm(ModelForm): self.fields['utilisateur_asso'].label = 'Compte utilisé pour\ faire les modifications depuis /admin' - def clean(self): - cleaned_data = super().clean() - payment = cleaned_data.get('payment') - - if payment == 'NONE': - return cleaned_data - - if not cleaned_data.get('payment_id', ''): - msg = forms.ValidationError("Vous devez spécifier un identifiant \ - de paiement.") - self.add_error('payment_id', msg) - if not cleaned_data.get('payment_pass', ''): - msg = forms.ValidationError("Vous devez spécifier un mot de passe \ - de paiement.") - self.add_error('payment_pass', msg) - - return cleaned_data - class EditMailMessageOptionForm(ModelForm): """Formulaire d'edition des messages de bienvenue personnalisés""" From d46ce4a1517df51d465082762ea3613735ee36e8 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Fri, 3 Aug 2018 18:08:33 +0200 Subject: [PATCH 040/171] Placement des machines sur un vlan en fonction de leur classe type --- freeradius_utils/auth.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/freeradius_utils/auth.py b/freeradius_utils/auth.py index d743495f..023448d1 100644 --- a/freeradius_utils/auth.py +++ b/freeradius_utils/auth.py @@ -63,6 +63,7 @@ from preferences.models import OptionalTopologie options, created = OptionalTopologie.objects.get_or_create() VLAN_NOK = options.vlan_decision_nok.vlan_id VLAN_OK = options.vlan_decision_ok.vlan_id +RADIUS_POLICY = options.radius_general_policy #: Serveur radius de test (pas la prod) TEST_SERVER = bool(os.getenv('DBG_FREERADIUS', False)) @@ -460,13 +461,16 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, VLAN_NOK) # Sinon on capture et on laisse passer sur le bon vlan else: - result, reason = (room_user + interface, reason = (room_user .first() .autoregister_machine( mac_address, nas_type )) - if result: + if interface: + ## Si on choisi de placer les machines sur le vlan correspondant à leur type : + if RADIUS_POLICY == 'MACHINE': + DECISION_VLAN = interface.type.ip_type.vlan.vlan_id return (sw_name, room, u'Access Ok, Capture de la mac: ' + extra_log, @@ -488,7 +492,10 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, room, u'Machine non active / adherent non cotisant', VLAN_NOK) - elif not interface.ipv4: + ## Si on choisi de placer les machines sur le vlan correspondant à leur type : + if RADIUS_POLICY == 'MACHINE': + DECISION_VLAN = interface.type.ip_type.vlan.vlan_id + if not interface.ipv4: interface.assign_ipv4() return (sw_name, room, From a4007b985f6dc9887814e6cf29d6570d5b28daa8 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Sat, 4 Aug 2018 01:15:34 +0200 Subject: [PATCH 041/171] =?UTF-8?q?L'autocreation=20renvoie=20l'interface?= =?UTF-8?q?=20cr=C3=A9e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- users/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/users/models.py b/users/models.py index bdc37142..695a2053 100755 --- a/users/models.py +++ b/users/models.py @@ -652,7 +652,7 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, self.notif_auto_newmachine(interface_cible) except Exception as error: return False, error - return True, "Ok" + return interface_cible, "Ok" def notif_auto_newmachine(self, interface): """Notification mail lorsque une machine est automatiquement From 5757a0c03d0410f0f92d6c2d336075acd305d782 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Fri, 13 Jul 2018 09:56:15 +0200 Subject: [PATCH 042/171] Serialisation des ouvertures de ports en sortie par subnet --- api/serializers.py | 24 ++++++++++++++++++++++++ api/urls.py | 2 ++ api/views.py | 6 ++++++ 3 files changed, 32 insertions(+) diff --git a/api/serializers.py b/api/serializers.py index 398f2b19..efbe5186 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -638,6 +638,30 @@ class LocalEmailUsersSerializer(NamespacedHMSerializer): 'email_address') +#Firewall + +class FirewallPortListSerializer(serializers.ModelSerializer): + class Meta: + model = machines.OuverturePort + fields = ('begin', 'end', 'protocole', 'io') + +class FirewallOuverturePortListSerializer(serializers.ModelSerializer): + tcp_ports_in = FirewallPortListSerializer(many=True, read_only=True) + udp_ports_in = FirewallPortListSerializer(many=True, read_only=True) + tcp_ports_out = FirewallPortListSerializer(many=True, read_only=True) + udp_ports_out = FirewallPortListSerializer(many=True, read_only=True) + + class Meta: + model = machines.OuverturePortList + fields = ('tcp_ports_in', 'udp_ports_in', 'tcp_ports_out', 'udp_ports_out') + +class SubnetPortsOpenSerializer(serializers.ModelSerializer): + ouverture_ports = FirewallOuverturePortListSerializer(read_only=True) + + class Meta: + model = machines.IpType + fields = ('type', 'domaine_ip_start', 'domaine_ip_stop', 'prefix_v6', 'ouverture_ports') + # DHCP diff --git a/api/urls.py b/api/urls.py index 7ee36073..37580db2 100644 --- a/api/urls.py +++ b/api/urls.py @@ -100,6 +100,8 @@ router.register_viewset(r'services/regen', views.ServiceRegenViewSet, base_name= router.register_view(r'dhcp/hostmacip', views.HostMacIpView), # LOCAL EMAILS router.register_view(r'localemail/users', views.LocalEmailUsersView), +# Firewall +router.register_view(r'firewall/subnet-ports', views.SubnetPortsOpenView), # DNS router.register_view(r'dns/zones', views.DNSZonesView), # MAILING diff --git a/api/views.py b/api/views.py index ef083edf..715a31ac 100644 --- a/api/views.py +++ b/api/views.py @@ -532,6 +532,12 @@ class HostMacIpView(generics.ListAPIView): serializer_class = serializers.HostMacIpSerializer +#Firewall + +class SubnetPortsOpenView(generics.ListAPIView): + queryset = machines.IpType.objects.all() + serializer_class = serializers.SubnetPortsOpenSerializer + # DNS From a7ea8a151881869506f6231ae5adfecb56af0da0 Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Fri, 3 Aug 2018 00:21:23 +0200 Subject: [PATCH 043/171] =?UTF-8?q?R=C3=A9pare=20de=20petits=20soucis=20de?= =?UTF-8?q?=20templates=20+=20problemes=20de=20rebase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/serializers.py | 9 --------- machines/migrations/{0084_role.py => 0086_role.py} | 2 +- machines/templates/machines/aff_role.html | 3 ++- machines/templates/machines/machine.html | 1 + 4 files changed, 4 insertions(+), 11 deletions(-) rename machines/migrations/{0084_role.py => 0086_role.py} (96%) diff --git a/api/serializers.py b/api/serializers.py index 839a1047..3a5f7f90 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -624,15 +624,6 @@ class ServiceRegenSerializer(NamespacedHMSerializer): } -class ProfilSerializer(NamespacedHMSerializer): - vlan_untagged = VlanSerializer(read_only=True) - vlan_tagged = VlanPortSerializer(read_only=True, many=True) - - class Meta: - model = topologie.PortProfile - fields = ('name', 'profil_default', 'vlan_untagged', 'vlan_tagged', 'radius_type', 'radius_mode', 'speed', 'mac_limit', 'flow_control', 'dhcp_snooping', 'dhcpv6_snooping', 'arp_protect', 'ra_guard', 'loop_protect', 'vlan_untagged', 'vlan_tagged') - - # LOCAL EMAILS diff --git a/machines/migrations/0084_role.py b/machines/migrations/0086_role.py similarity index 96% rename from machines/migrations/0084_role.py rename to machines/migrations/0086_role.py index 49343809..a23de26f 100644 --- a/machines/migrations/0084_role.py +++ b/machines/migrations/0086_role.py @@ -9,7 +9,7 @@ import re2o.mixins class Migration(migrations.Migration): dependencies = [ - ('machines', '0083_remove_duplicate_rights'), + ('machines', '0085_sshfingerprint'), ] operations = [ diff --git a/machines/templates/machines/aff_role.html b/machines/templates/machines/aff_role.html index f83a4adb..519e8fd6 100644 --- a/machines/templates/machines/aff_role.html +++ b/machines/templates/machines/aff_role.html @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load acl %} {% load i18n %} +{% load logs_extra %} @@ -45,7 +46,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% can_edit role %} {% include 'buttons/edit.html' with href='machines:edit-role' id=role.id %} {% acl_end %} - {% include 'buttons/history.html' with href='machines:history' name='role' id=role.id %} + {% history_button role %} {% endfor %} diff --git a/machines/templates/machines/machine.html b/machines/templates/machines/machine.html index cd00ba0c..d6c0f522 100644 --- a/machines/templates/machines/machine.html +++ b/machines/templates/machines/machine.html @@ -71,6 +71,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endif %} {% if sshfpform %} {% bootstrap_form_errors sshfpform %} +{% endif %} {% if roleform %} {% bootstrap_form_errors roleform %} {% endif %} From 029a83bd33276b52d7059cafa7fe1103fcd011e8 Mon Sep 17 00:00:00 2001 From: Charlie Jacomme Date: Mon, 25 Jun 2018 17:15:48 +0200 Subject: [PATCH 044/171] Ajout dnssec reverse bool for iptypes --- machines/forms.py | 4 +++- machines/models.py | 8 ++++++++ machines/templates/machines/aff_iptype.html | 9 +++++---- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/machines/forms.py b/machines/forms.py index ecffcc71..ff529295 100644 --- a/machines/forms.py +++ b/machines/forms.py @@ -218,7 +218,8 @@ class IpTypeForm(FormRevMixin, ModelForm): class Meta: model = IpType fields = ['type', 'extension', 'need_infra', 'domaine_ip_start', - 'domaine_ip_stop', 'prefix_v6', 'vlan', 'ouverture_ports'] + 'domaine_ip_stop', 'dnssec_reverse_v4', 'prefix_v6', + 'dnssec_reverse_v6', 'vlan', 'ouverture_ports'] def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) @@ -231,6 +232,7 @@ class EditIpTypeForm(IpTypeForm): synchroniser les objets iplist""" class Meta(IpTypeForm.Meta): fields = ['extension', 'type', 'need_infra', 'prefix_v6', 'vlan', + 'dnssec_reverse_v4', 'dnssec_reverse_v6', 'ouverture_ports'] diff --git a/machines/models.py b/machines/models.py index e981bf10..7a211fe1 100644 --- a/machines/models.py +++ b/machines/models.py @@ -256,11 +256,19 @@ class IpType(RevMixin, AclMixin, models.Model): need_infra = models.BooleanField(default=False) domaine_ip_start = models.GenericIPAddressField(protocol='IPv4') domaine_ip_stop = models.GenericIPAddressField(protocol='IPv4') + dnssec_reverse_v4 = models.BooleanField( + default=False, + help_text="Activer DNSSEC sur le reverse DNS IPv4", + ) prefix_v6 = models.GenericIPAddressField( protocol='IPv6', null=True, blank=True ) + dnssec_reverse_v6 = models.BooleanField( + default=False, + help_text="Activer DNSSEC sur le reverse DNS IPv6", + ) vlan = models.ForeignKey( 'Vlan', on_delete=models.PROTECT, diff --git a/machines/templates/machines/aff_iptype.html b/machines/templates/machines/aff_iptype.html index fa2a2767..bee4669b 100644 --- a/machines/templates/machines/aff_iptype.html +++ b/machines/templates/machines/aff_iptype.html @@ -34,11 +34,11 @@ with this program; if not, write to the Free Software Foundation, Inc., - + + - {% for type in iptype_list %} @@ -46,8 +46,9 @@ with this program; if not, write to the Free Software Foundation, Inc., - - + + + - {% for txt in txt_list %} + {% for txt in text_list %} From cd5d0f562c4e32184c263556fc57806103a7865e Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Sat, 4 Aug 2018 08:37:01 +0200 Subject: [PATCH 047/171] Simplification et fusion des migrations --- ...9_auto_20180625_1700.py => 0087_dnssec.py} | 8 ++--- .../migrations/0090_auto_20180625_1706.py | 33 ------------------- 2 files changed, 4 insertions(+), 37 deletions(-) rename machines/migrations/{0089_auto_20180625_1700.py => 0087_dnssec.py} (78%) delete mode 100644 machines/migrations/0090_auto_20180625_1706.py diff --git a/machines/migrations/0089_auto_20180625_1700.py b/machines/migrations/0087_dnssec.py similarity index 78% rename from machines/migrations/0089_auto_20180625_1700.py rename to machines/migrations/0087_dnssec.py index dbaee1a8..cc2a25ec 100644 --- a/machines/migrations/0089_auto_20180625_1700.py +++ b/machines/migrations/0087_dnssec.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.10.7 on 2018-06-25 15:00 +# Generated by Django 1.10.7 on 2018-06-25 15:06 from __future__ import unicode_literals from django.db import migrations, models @@ -8,17 +8,17 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('machines', '0088_dname'), + ('machines', '0086_role'), ] operations = [ migrations.AddField( - model_name='extension', + model_name='iptype', name='dnssec_reverse_v4', field=models.BooleanField(default=False, help_text='Activer DNSSEC sur le reverse DNS IPv4'), ), migrations.AddField( - model_name='extension', + model_name='iptype', name='dnssec_reverse_v6', field=models.BooleanField(default=False, help_text='Activer DNSSEC sur le reverse DNS IPv6'), ), diff --git a/machines/migrations/0090_auto_20180625_1706.py b/machines/migrations/0090_auto_20180625_1706.py deleted file mode 100644 index 9c5e523c..00000000 --- a/machines/migrations/0090_auto_20180625_1706.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10.7 on 2018-06-25 15:06 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('machines', '0089_auto_20180625_1700'), - ] - - operations = [ - migrations.RemoveField( - model_name='extension', - name='dnssec_reverse_v4', - ), - migrations.RemoveField( - model_name='extension', - name='dnssec_reverse_v6', - ), - migrations.AddField( - model_name='iptype', - name='dnssec_reverse_v4', - field=models.BooleanField(default=False, help_text='Activer DNSSEC sur le reverse DNS IPv4'), - ), - migrations.AddField( - model_name='iptype', - name='dnssec_reverse_v6', - field=models.BooleanField(default=False, help_text='Activer DNSSEC sur le reverse DNS IPv6'), - ), - ] From 1a46f3110f7e937f84087e66032f2e3d6cf7a228 Mon Sep 17 00:00:00 2001 From: Charlie Jacomme Date: Sat, 14 Jul 2018 10:59:17 +0200 Subject: [PATCH 048/171] Serializers for reverse DNS --- api/serializers.py | 18 ++++++++++++++++++ api/urls.py | 1 + api/views.py | 9 +++++++++ machines/models.py | 11 +++++++++++ 4 files changed, 39 insertions(+) diff --git a/api/serializers.py b/api/serializers.py index 23a2b15e..3761907f 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -817,6 +817,24 @@ class DNSZonesSerializer(serializers.ModelSerializer): 'aaaa_records', 'cname_records', 'sshfp_records') +class DNSReverseZonesSerializer(serializers.ModelSerializer): + """Serialize the data about DNS Zones. + """ + soa = SOARecordSerializer(source='extension.soa') + extension = serializers.CharField(source='extension.name', read_only=True) + cidrs = serializers.CharField(source='ip_set_cidrs_as_str', read_only=True) + ns_records = NSRecordSerializer(many=True, source='extension.ns_set') + mx_records = MXRecordSerializer(many=True, source='extension.mx_set') + txt_records = TXTRecordSerializer(many=True, source='extension.txt_set') + ptr_records = ARecordSerializer(many=True, source='get_associated_ptr_records') + ptr_v6_records = AAAARecordSerializer(many=True, source='get_associated_ptr_v6_records') + + + class Meta: + model = machines.IpType + fields = ('type', 'extension', 'soa', 'ns_records', 'mx_records', + 'txt_records', 'ptr_records', 'ptr_v6_records', 'cidrs') + # MAILING diff --git a/api/urls.py b/api/urls.py index 37580db2..0c0c0e6c 100644 --- a/api/urls.py +++ b/api/urls.py @@ -104,6 +104,7 @@ router.register_view(r'localemail/users', views.LocalEmailUsersView), router.register_view(r'firewall/subnet-ports', views.SubnetPortsOpenView), # DNS router.register_view(r'dns/zones', views.DNSZonesView), +router.register_view(r'dns/reverse-zones', views.DNSReverseZonesView), # MAILING router.register_view(r'mailing/standard', views.StandardMailingView), router.register_view(r'mailing/club', views.ClubMailingView), diff --git a/api/views.py b/api/views.py index 715a31ac..9a3a772b 100644 --- a/api/views.py +++ b/api/views.py @@ -555,6 +555,15 @@ class DNSZonesView(generics.ListAPIView): .all()) serializer_class = serializers.DNSZonesSerializer +class DNSReverseZonesView(generics.ListAPIView): + """Exposes the detailed information about each extension (hostnames, + IPs, DNS records, etc.) in order to build the DNS zone files. + """ + queryset = (machines.IpType.objects.all()) + serializer_class = serializers.DNSReverseZonesSerializer + + + # MAILING diff --git a/machines/models.py b/machines/models.py index 7a211fe1..1cfee990 100644 --- a/machines/models.py +++ b/machines/models.py @@ -353,6 +353,17 @@ class IpType(RevMixin, AclMixin, models.Model): ): ipv6.check_and_replace_prefix(prefix=self.prefix_v6) + def get_associated_ptr_records(self): + from re2o.utils import all_active_assigned_interfaces + return (all_active_assigned_interfaces() + .filter(type__ip_type=self) + .filter(ipv4__isnull=False)) + + def get_associated_ptr_v6_records(self): + from re2o.utils import all_active_interfaces + return (all_active_interfaces(full=True) + .filter(type__ip_type=self)) + def clean(self): """ Nettoyage. Vérifie : - Que ip_stop est après ip_start From 1cb0fb275be6bb646430a11c0de7b48c1b382164 Mon Sep 17 00:00:00 2001 From: Charlie Jacomme Date: Mon, 16 Jul 2018 10:53:44 +0200 Subject: [PATCH 049/171] serializers for dns reverse, cidr is list --- api/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/serializers.py b/api/serializers.py index 3761907f..09e39de7 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -822,7 +822,7 @@ class DNSReverseZonesSerializer(serializers.ModelSerializer): """ soa = SOARecordSerializer(source='extension.soa') extension = serializers.CharField(source='extension.name', read_only=True) - cidrs = serializers.CharField(source='ip_set_cidrs_as_str', read_only=True) + cidrs = serializers.ListField(child=serializers.CharField(), source='ip_set_cidrs_as_str', read_only=True) ns_records = NSRecordSerializer(many=True, source='extension.ns_set') mx_records = MXRecordSerializer(many=True, source='extension.mx_set') txt_records = TXTRecordSerializer(many=True, source='extension.txt_set') From 6aed9383344cd693363853ef8da79eee3d0994af Mon Sep 17 00:00:00 2001 From: Charlie Jacomme Date: Mon, 16 Jul 2018 20:09:02 +0200 Subject: [PATCH 050/171] serializer prefix_v6 for dns reverse --- api/serializers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/serializers.py b/api/serializers.py index 09e39de7..4621d5bb 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -833,7 +833,8 @@ class DNSReverseZonesSerializer(serializers.ModelSerializer): class Meta: model = machines.IpType fields = ('type', 'extension', 'soa', 'ns_records', 'mx_records', - 'txt_records', 'ptr_records', 'ptr_v6_records', 'cidrs') + 'txt_records', 'ptr_records', 'ptr_v6_records', 'cidrs', + 'prefix_v6') # MAILING From 4329641899d93e6f3d599a6ec64089d89a75df33 Mon Sep 17 00:00:00 2001 From: Charlie Jacomme Date: Mon, 16 Jul 2018 20:52:39 +0200 Subject: [PATCH 051/171] Ading prefix_v6_length field to IpType --- machines/forms.py | 7 ++-- .../0088_iptype_prefix_v6_length.py | 21 +++++++++++ machines/models.py | 36 ++++++++++++++++++- machines/templates/machines/aff_iptype.html | 2 +- 4 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 machines/migrations/0088_iptype_prefix_v6_length.py diff --git a/machines/forms.py b/machines/forms.py index ff529295..49150349 100644 --- a/machines/forms.py +++ b/machines/forms.py @@ -219,7 +219,8 @@ class IpTypeForm(FormRevMixin, ModelForm): model = IpType fields = ['type', 'extension', 'need_infra', 'domaine_ip_start', 'domaine_ip_stop', 'dnssec_reverse_v4', 'prefix_v6', - 'dnssec_reverse_v6', 'vlan', 'ouverture_ports'] + 'prefix_v6_length','dnssec_reverse_v6', 'vlan', + 'ouverture_ports'] def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) @@ -231,8 +232,8 @@ class EditIpTypeForm(IpTypeForm): """Edition d'un iptype. Pas d'edition du rangev4 possible, car il faudrait synchroniser les objets iplist""" class Meta(IpTypeForm.Meta): - fields = ['extension', 'type', 'need_infra', 'prefix_v6', 'vlan', - 'dnssec_reverse_v4', 'dnssec_reverse_v6', + fields = ['extension', 'type', 'need_infra', 'prefix_v6', 'prefix_v6_length', + 'vlan', 'dnssec_reverse_v4', 'dnssec_reverse_v6', 'ouverture_ports'] diff --git a/machines/migrations/0088_iptype_prefix_v6_length.py b/machines/migrations/0088_iptype_prefix_v6_length.py new file mode 100644 index 00000000..e061167c --- /dev/null +++ b/machines/migrations/0088_iptype_prefix_v6_length.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-07-16 18:46 +from __future__ import unicode_literals + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('machines', '0087_dnssec'), + ] + + operations = [ + migrations.AddField( + model_name='iptype', + name='prefix_v6_length', + field=models.IntegerField(default=64, validators=[django.core.validators.MaxValueValidator(128), django.core.validators.MinValueValidator(0)]), + ), + ] diff --git a/machines/models.py b/machines/models.py index 1cfee990..2f5a6a62 100644 --- a/machines/models.py +++ b/machines/models.py @@ -41,8 +41,8 @@ from django.dispatch import receiver from django.forms import ValidationError from django.utils.functional import cached_property from django.utils import timezone -from django.core.validators import MaxValueValidator from django.utils.translation import ugettext_lazy as _l +from django.core.validators import MaxValueValidator, MinValueValidator from macaddress.fields import MACAddressField @@ -265,6 +265,13 @@ class IpType(RevMixin, AclMixin, models.Model): null=True, blank=True ) + prefix_v6_length = models.IntegerField( + default=64, + validators=[ + MaxValueValidator(128), + MinValueValidator(0) + ] + ) dnssec_reverse_v6 = models.BooleanField( default=False, help_text="Activer DNSSEC sur le reverse DNS IPv6", @@ -302,6 +309,33 @@ class IpType(RevMixin, AclMixin, models.Model): """ Renvoie une liste des ip en string""" return [str(x) for x in self.ip_set] + @cached_property + def ip_set_full_info(self): + """Iter sur les range cidr, et renvoie network, broacast , etc""" + return [ + { + 'network': str(ip_set.network), + 'netmask': str(ip_set.netmask), + 'netmask_cidr': str(ip_set.prefixlen), + 'broadcast': str(ip_set.broadcast), + 'vlan': str(self.vlan), + 'vlan_id': self.vlan.vlan_id + } for ip_set in self.ip_set.iter_cidrs() + ] + + @cached_property + def ip6_set_full_info(self): + if self.prefix_v6: + return { + 'network' : str(self.prefix_v6), + 'netmask' : 'ffff:ffff:ffff:ffff::', + 'netmask_cidr' : str(self.prefix_v6_length), + 'vlan': str(self.vlan), + 'vlan_id': self.vlan.vlan_id + } + else: + return None + def ip_objects(self): """ Renvoie tous les objets ipv4 relié à ce type""" return IpList.objects.filter(ip_type=self) diff --git a/machines/templates/machines/aff_iptype.html b/machines/templates/machines/aff_iptype.html index bee4669b..7d4de6c5 100644 --- a/machines/templates/machines/aff_iptype.html +++ b/machines/templates/machines/aff_iptype.html @@ -47,7 +47,7 @@ with this program; if not, write to the Free Software Foundation, Inc., - + From b140e26f0a55a3d11c8bb76275ce065e0862fe38 Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Sat, 4 Aug 2018 09:54:17 +0200 Subject: [PATCH 052/171] =?UTF-8?q?Suppression=20des=20migrations=20pr?= =?UTF-8?q?=C3=A9c=C3=A9demment=20fusionn=C3=A9es.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0062_auto_20180627_0123.py | 25 ------- .../migrations/0063_port_custom_profil.py | 21 ------ topologie/migrations/0064_createprofil.py | 53 ------------- .../migrations/0065_auto_20180630_1703.py | 23 ------ .../migrations/0066_auto_20180630_1855.py | 25 ------- .../migrations/0067_auto_20180701_0016.py | 75 ------------------- 6 files changed, 222 deletions(-) delete mode 100644 topologie/migrations/0062_auto_20180627_0123.py delete mode 100644 topologie/migrations/0063_port_custom_profil.py delete mode 100644 topologie/migrations/0064_createprofil.py delete mode 100644 topologie/migrations/0065_auto_20180630_1703.py delete mode 100644 topologie/migrations/0066_auto_20180630_1855.py delete mode 100644 topologie/migrations/0067_auto_20180701_0016.py diff --git a/topologie/migrations/0062_auto_20180627_0123.py b/topologie/migrations/0062_auto_20180627_0123.py deleted file mode 100644 index b8135de8..00000000 --- a/topologie/migrations/0062_auto_20180627_0123.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10.7 on 2018-06-26 23:23 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('topologie', '0061_portprofile'), - ] - - operations = [ - migrations.AlterField( - model_name='portprofile', - name='radius_mode', - field=models.CharField(choices=[('STRICT', 'STRICT'), ('COMMON', 'COMMON')], default='COMMON', help_text="En cas d'auth par mac, auth common ou strcit sur le port", 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')], help_text="Choix du type d'authentification radius : non actif, mac ou 802.1X", max_length=32, verbose_name='RADIUS type'), - ), - ] diff --git a/topologie/migrations/0063_port_custom_profil.py b/topologie/migrations/0063_port_custom_profil.py deleted file mode 100644 index 15feebce..00000000 --- a/topologie/migrations/0063_port_custom_profil.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10.7 on 2018-06-28 07:49 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('topologie', '0062_auto_20180627_0123'), - ] - - operations = [ - migrations.AddField( - model_name='port', - name='custom_profil', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='topologie.PortProfile'), - ), - ] diff --git a/topologie/migrations/0064_createprofil.py b/topologie/migrations/0064_createprofil.py deleted file mode 100644 index 2f165386..00000000 --- a/topologie/migrations/0064_createprofil.py +++ /dev/null @@ -1,53 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10.7 on 2017-12-31 19:53 -from __future__ import unicode_literals - -from django.db import migrations - - -def transfer_profil(apps, schema_editor): - db_alias = schema_editor.connection.alias - port = apps.get_model("topologie", "Port") - profil = apps.get_model("topologie", "PortProfile") - vlan = apps.get_model("machines", "Vlan") - port_list = port.objects.using(db_alias).all() - profil_nothing = profil.objects.using(db_alias).create(name='nothing', profil_default='nothing', radius_type='NO') - profil_uplink = profil.objects.using(db_alias).create(name='uplink', profil_default='uplink', radius_type='NO') - profil_machine = profil.objects.using(db_alias).create(name='asso_machine', profil_default='asso_machine', radius_type='NO') - profil_room = profil.objects.using(db_alias).create(name='room', profil_default='room', radius_type='NO') - profil_borne = profil.objects.using(db_alias).create(name='accesspoint', profil_default='accesspoint', radius_type='NO') - for vlan_instance in vlan.objects.using(db_alias).all(): - if port.objects.using(db_alias).filter(vlan_force=vlan_instance): - custom_profil = profil.objects.using(db_alias).create(name='vlan-force-' + str(vlan_instance.vlan_id), radius_type='NO', vlan_untagged=vlan_instance) - port.objects.using(db_alias).filter(vlan_force=vlan_instance).update(custom_profil=custom_profil) - if port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').count() and port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').count(): - profil_room.radius_type = 'MAC-radius' - profil_room.radius_mode = 'STRICT' - common_profil = profil.objects.using(db_alias).create(name='mac-radius-common', radius_type='MAC-radius', radius_mode='COMMON') - no_rad_profil = profil.objects.using(db_alias).create(name='no-radius', radius_type='NO') - port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').update(custom_profil=common_profil) - port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').update(custom_profil=no_rad_profil) - elif port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').count() and port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').count(): - profil_room.radius_type = 'MAC-radius' - profil_room.radius_mode = 'COMMON' - strict_profil = profil.objects.using(db_alias).create(name='mac-radius-strict', radius_type='MAC-radius', radius_mode='STRICT') - no_rad_profil = profil.objects.using(db_alias).create(name='no-radius', radius_type='NO') - port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').update(custom_profil=strict_profil) - port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').update(custom_profil=no_rad_profil) - else: - strict_profil = profil.objects.using(db_alias).create(name='mac-radius-strict', radius_type='MAC-radius', radius_mode='STRICT') - common_profil = profil.objects.using(db_alias).create(name='mac-radius-common', radius_type='MAC-radius', radius_mode='COMMON') - port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').update(custom_profil=strict_profil) - port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').update(custom_profil=common_profil) - profil_room.save() - - -class Migration(migrations.Migration): - - dependencies = [ - ('topologie', '0063_port_custom_profil'), - ] - - operations = [ - migrations.RunPython(transfer_profil), - ] diff --git a/topologie/migrations/0065_auto_20180630_1703.py b/topologie/migrations/0065_auto_20180630_1703.py deleted file mode 100644 index 9fed2d83..00000000 --- a/topologie/migrations/0065_auto_20180630_1703.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10.7 on 2018-06-30 15:03 -from __future__ import unicode_literals - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('topologie', '0064_createprofil'), - ] - - operations = [ - migrations.RemoveField( - model_name='port', - name='radius', - ), - migrations.RemoveField( - model_name='port', - name='vlan_force', - ), - ] diff --git a/topologie/migrations/0066_auto_20180630_1855.py b/topologie/migrations/0066_auto_20180630_1855.py deleted file mode 100644 index b197f568..00000000 --- a/topologie/migrations/0066_auto_20180630_1855.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10.7 on 2018-06-30 16:55 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('topologie', '0065_auto_20180630_1703'), - ] - - operations = [ - migrations.AddField( - model_name='port', - name='state', - field=models.BooleanField(default=True, help_text='Etat du port Actif', verbose_name='Etat du port Actif'), - ), - migrations.AlterField( - model_name='portprofile', - name='profil_default', - field=models.CharField(blank=True, choices=[('room', 'room'), ('accespoint', 'accesspoint'), ('uplink', 'uplink'), ('asso_machine', 'asso_machine'), ('nothing', 'nothing')], max_length=32, null=True, unique=True, verbose_name='profil default'), - ), - ] diff --git a/topologie/migrations/0067_auto_20180701_0016.py b/topologie/migrations/0067_auto_20180701_0016.py deleted file mode 100644 index 578ee7d6..00000000 --- a/topologie/migrations/0067_auto_20180701_0016.py +++ /dev/null @@ -1,75 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10.7 on 2018-06-30 22:16 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('topologie', '0066_auto_20180630_1855'), - ] - - operations = [ - migrations.RenameField( - model_name='port', - old_name='custom_profil', - new_name='custom_profile', - ), - migrations.AlterField( - model_name='port', - name='state', - field=models.BooleanField(default=True, help_text='Port state Active', verbose_name='Port State Active'), - ), - migrations.AlterField( - model_name='portprofile', - name='arp_protect', - field=models.BooleanField(default=False, help_text='Check if ip is dhcp assigned', verbose_name='Arp protect'), - ), - migrations.AlterField( - model_name='portprofile', - name='dhcp_snooping', - field=models.BooleanField(default=False, help_text='Protect against rogue dhcp', verbose_name='Dhcp snooping'), - ), - migrations.AlterField( - model_name='portprofile', - name='dhcpv6_snooping', - field=models.BooleanField(default=False, help_text='Protect against rogue dhcpv6', verbose_name='Dhcpv6 snooping'), - ), - migrations.AlterField( - model_name='portprofile', - name='flow_control', - field=models.BooleanField(default=False, help_text='Flow control', verbose_name='Flow control'), - ), - migrations.AlterField( - model_name='portprofile', - name='loop_protect', - field=models.BooleanField(default=False, help_text='Protect again loop', verbose_name='Loop Protect'), - ), - migrations.AlterField( - model_name='portprofile', - name='mac_limit', - field=models.IntegerField(blank=True, help_text='Limit of mac-address on this port', null=True, verbose_name='Mac limit'), - ), - migrations.AlterField( - model_name='portprofile', - name='ra_guard', - field=models.BooleanField(default=False, help_text='Protect against rogue ra', verbose_name='Ra guard'), - ), - migrations.AlterField( - model_name='portprofile', - name='radius_mode', - field=models.CharField(choices=[('STRICT', 'STRICT'), ('COMMON', 'COMMON')], default='COMMON', help_text='In case of mac-auth : mode common or strict on this port', 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')], help_text='Type of radius auth : inactive, mac-address or 802.1X', max_length=32, verbose_name='RADIUS type'), - ), - migrations.AlterField( - model_name='portprofile', - name='speed', - field=models.CharField(choices=[('10-half', '10-half'), ('100-half', '100-half'), ('10-full', '10-full'), ('100-full', '100-full'), ('1000-full', '1000-full'), ('auto', 'auto'), ('auto-10', 'auto-10'), ('auto-100', 'auto-100')], default='auto', help_text='Port speed limit', max_length=32, verbose_name='Speed'), - ), - ] From 470b02be940391242dd2d4a6e161b66306b2a2ba Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Sat, 4 Aug 2018 10:53:34 +0200 Subject: [PATCH 053/171] =?UTF-8?q?R=C3=A9pare=20l'API=20pour=20les=20Port?= =?UTF-8?q?s=20en=20ajoutant=20la=20s=C3=A9rialisation=20des=20PortProfile?= =?UTF-8?q?.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/serializers.py | 16 ++++++++++++++-- api/urls.py | 3 ++- api/views.py | 8 +++++++- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/api/serializers.py b/api/serializers.py index 23a2b15e..d4e50702 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -470,10 +470,10 @@ class SwitchPortSerializer(NamespacedHMSerializer): class Meta: model = topologie.Port fields = ('switch', 'port', 'room', 'machine_interface', 'related', - 'radius', 'vlan_force', 'details', 'api_url') + 'custom_profile', 'state', 'details', 'api_url') extra_kwargs = { 'related': {'view_name': 'switchport-detail'}, - 'api_url': {'view_name': 'switchport-detail'} + 'api_url': {'view_name': 'switchport-detail'}, } @@ -485,6 +485,18 @@ class RoomSerializer(NamespacedHMSerializer): fields = ('name', 'details', 'api_url') +class PortProfileSerializer(NamespacedHMSerializer): + vlan_untagged = VlanSerializer(read_only=True) + + class Meta: + model = topologie.PortProfile + fields = ('name', 'profil_default', 'vlan_untagged', 'vlan_tagged', + 'radius_type', 'radius_mode', 'speed', 'mac_limit', + 'flow_control', 'dhcp_snooping', 'dhcpv6_snooping', + 'arp_protect', 'ra_guard', 'loop_protect', 'vlan_untagged', + 'vlan_tagged') + + # USERS diff --git a/api/urls.py b/api/urls.py index 37580db2..15a19d59 100644 --- a/api/urls.py +++ b/api/urls.py @@ -81,8 +81,9 @@ router.register_viewset(r'topologie/modelswitch', views.ModelSwitchViewSet) router.register_viewset(r'topologie/constructorswitch', views.ConstructorSwitchViewSet) router.register_viewset(r'topologie/switchbay', views.SwitchBayViewSet) router.register_viewset(r'topologie/building', views.BuildingViewSet) -router.register_viewset(r'topologie/switchport', views.SwitchPortViewSet, base_name='switchport') +router.register(r'topologie/switchport', views.SwitchPortViewSet, base_name='switchport') router.register_viewset(r'topologie/room', views.RoomViewSet) +router.register(r'topologie/portprofile', views.PortProfileViewSet) # USERS router.register_viewset(r'users/user', views.UserViewSet) router.register_viewset(r'users/club', views.ClubViewSet) diff --git a/api/views.py b/api/views.py index 715a31ac..f84903e6 100644 --- a/api/views.py +++ b/api/views.py @@ -403,6 +403,12 @@ class RoomViewSet(viewsets.ReadOnlyModelViewSet): serializer_class = serializers.RoomSerializer +class PortProfileViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `topologie.models.PortProfile` objects. + """ + queryset = topologie.PortProfile.objects.all() + serializer_class = serializers.PortProfileSerializer + # USER @@ -542,7 +548,7 @@ class SubnetPortsOpenView(generics.ListAPIView): class DNSZonesView(generics.ListAPIView): - """Exposes the detailed information about each extension (hostnames, + """Exposes the detailed information about each extension (hostnames, IPs, DNS records, etc.) in order to build the DNS zone files. """ queryset = (machines.Extension.objects From fbb2c5972239fdbc60ec8bafae04efb890df3194 Mon Sep 17 00:00:00 2001 From: Charlie Jacomme Date: Sat, 4 Aug 2018 16:58:42 +0200 Subject: [PATCH 054/171] Support old hashes, md5/crypt --- re2o/login.py | 115 ++++++++++++++++++++++++++++++++++++++++++++++- re2o/settings.py | 2 + 2 files changed, 116 insertions(+), 1 deletion(-) diff --git a/re2o/login.py b/re2o/login.py index b867e836..4f4150e2 100644 --- a/re2o/login.py +++ b/re2o/login.py @@ -33,8 +33,10 @@ import binascii import os from base64 import encodestring from base64 import decodestring +from base64 import b64encode +from base64 import b64decode from collections import OrderedDict - +import crypt from django.contrib.auth import hashers @@ -72,6 +74,117 @@ def checkPassword(challenge_password, password): return valid_password +def hash_password_salt(hashed_password): + """ Extract the salt from a given hashed password """ + if hashed_password.upper().startswith('{CRYPT}'): + hashed_password = hashed_password[7:] + if hashed_password.startswith('$'): + return '$'.join(hashed_password.split('$')[:-1]) + else: + return hashed_password[:2] + elif hashed_password.upper().startswith('{SSHA}'): + try: + digest = b64decode(hashed_password[6:]) + except TypeError as error: + raise ValueError("b64 error for `hashed_password` : %s" % error) + if len(digest) < 20: + raise ValueError("`hashed_password` too short") + return digest[20:] + elif hashed_password.upper().startswith('{SMD5}'): + try: + digest = b64decode(hashed_password[7:]) + except TypeError as error: + raise ValueError("b64 error for `hashed_password` : %s" % error) + if len(digest) < 16: + raise ValueError("`hashed_password` too short") + return digest[16:] + else: + raise ValueError("`hashed_password` should start with '{SSHA}' or '{CRYPT}' or '{SMD5}'") + + + +class CryptPasswordHasher(hashers.BasePasswordHasher): + """ + Crypt password hashing to allow for LDAP auth compatibility + We do not encode, this should bot be used ! + """ + + algorithm = "{crypt}" + + def encode(self, password, salt): + pass + + def verify(self, password, encoded): + """ + Check password against encoded using SSHA algorithm + """ + assert encoded.startswith(self.algorithm) + salt = hash_password_salt(challenge_password) + return crypt.crypt(password.encode(), salt) == challenge.encode() + + def safe_summary(self, encoded): + """ + Provides a safe summary of the password + """ + assert encoded.startswith(self.algorithm) + hash_str = encoded[7:] + hash_str = binascii.hexlify(decodestring(hash_str.encode())).decode() + return OrderedDict([ + ('algorithm', self.algorithm), + ('iterations', 0), + ('salt', hashers.mask_hash(hash_str[2*DIGEST_LEN:], show=2)), + ('hash', hashers.mask_hash(hash_str[:2*DIGEST_LEN])), + ]) + + def harden_runtime(self, password, encoded): + """ + Method implemented to shut up BasePasswordHasher warning + + As we are not using multiple iterations the method is pretty useless + """ + pass + +class MD5PasswordHasher(hashers.BasePasswordHasher): + """ + MD5 password hashing to allow for LDAP auth compatibility + We do not encode, this should bot be used ! + """ + + algorithm = "{SMD5}" + + def encode(self, password, salt): + pass + + def verify(self, password, encoded): + """ + Check password against encoded using SSHA algorithm + """ + assert encoded.startswith(self.algorithm) + salt = hash_password_salt(encoded) + return b64encode(hashlib.md5(password.encode() + salt).digest() + salt) == encoded.encode() + + def safe_summary(self, encoded): + """ + Provides a safe summary of the password + """ + assert encoded.startswith(self.algorithm) + hash_str = encoded[7:] + hash_str = binascii.hexlify(decodestring(hash_str.encode())).decode() + return OrderedDict([ + ('algorithm', self.algorithm), + ('iterations', 0), + ('salt', hashers.mask_hash(hash_str[2*DIGEST_LEN:], show=2)), + ('hash', hashers.mask_hash(hash_str[:2*DIGEST_LEN])), + ]) + + def harden_runtime(self, password, encoded): + """ + Method implemented to shut up BasePasswordHasher warning + + As we are not using multiple iterations the method is pretty useless + """ + pass + class SSHAPasswordHasher(hashers.BasePasswordHasher): """ SSHA password hashing to allow for LDAP auth compatibility diff --git a/re2o/settings.py b/re2o/settings.py index 71bd266f..b68e4997 100644 --- a/re2o/settings.py +++ b/re2o/settings.py @@ -46,6 +46,8 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Auth definition PASSWORD_HASHERS = ( 're2o.login.SSHAPasswordHasher', + 're2o.login.MD5PasswordHasher', + 're2o.login.CryptPasswordHasher', 'django.contrib.auth.hashers.PBKDF2PasswordHasher', ) AUTH_USER_MODEL = 'users.User' # The class to use for authentication From 3244a46d945a3158b984533696fbfad371d643ea Mon Sep 17 00:00:00 2001 From: David Sinquin Date: Sat, 4 Aug 2018 22:50:13 +0200 Subject: [PATCH 055/171] login handler: Various code cleanings with no impact. --- re2o/login.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/re2o/login.py b/re2o/login.py index 4f4150e2..f80f3e42 100644 --- a/re2o/login.py +++ b/re2o/login.py @@ -28,15 +28,12 @@ Module in charge of handling the login process and verifications """ -import hashlib import binascii -import os -from base64 import encodestring -from base64 import decodestring -from base64 import b64encode -from base64 import b64decode -from collections import OrderedDict import crypt +import hashlib +import os +from base64 import encodestring, decodestring, b64encode, b64decode +from collections import OrderedDict from django.contrib.auth import hashers @@ -107,6 +104,7 @@ class CryptPasswordHasher(hashers.BasePasswordHasher): """ Crypt password hashing to allow for LDAP auth compatibility We do not encode, this should bot be used ! + The actual implementation may depend on the OS. """ algorithm = "{crypt}" @@ -116,7 +114,7 @@ class CryptPasswordHasher(hashers.BasePasswordHasher): def verify(self, password, encoded): """ - Check password against encoded using SSHA algorithm + Check password against encoded using CRYPT algorithm """ assert encoded.startswith(self.algorithm) salt = hash_password_salt(challenge_password) @@ -146,7 +144,7 @@ class CryptPasswordHasher(hashers.BasePasswordHasher): class MD5PasswordHasher(hashers.BasePasswordHasher): """ - MD5 password hashing to allow for LDAP auth compatibility + Salted MD5 password hashing to allow for LDAP auth compatibility We do not encode, this should bot be used ! """ @@ -157,7 +155,7 @@ class MD5PasswordHasher(hashers.BasePasswordHasher): def verify(self, password, encoded): """ - Check password against encoded using SSHA algorithm + Check password against encoded using SMD5 algorithm """ assert encoded.startswith(self.algorithm) salt = hash_password_salt(encoded) @@ -187,7 +185,7 @@ class MD5PasswordHasher(hashers.BasePasswordHasher): class SSHAPasswordHasher(hashers.BasePasswordHasher): """ - SSHA password hashing to allow for LDAP auth compatibility + Salted SHA-1 password hashing to allow for LDAP auth compatibility """ algorithm = ALGO_NAME From ca08234a810d0f314757b547b6fc200e24307cd5 Mon Sep 17 00:00:00 2001 From: David Sinquin Date: Sat, 4 Aug 2018 22:52:59 +0200 Subject: [PATCH 056/171] login handler: Use constant-time comparaison for hashes. An attacker knowing the salt but not the hash could try timming-attacks to guess a password hash and then try to find it from the hash. Although not a high risk, there is no good reason not to use a constant-time comparison, hence this commit. --- re2o/login.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/re2o/login.py b/re2o/login.py index f80f3e42..0bf9aed8 100644 --- a/re2o/login.py +++ b/re2o/login.py @@ -35,6 +35,7 @@ import os from base64 import encodestring, decodestring, b64encode, b64decode from collections import OrderedDict from django.contrib.auth import hashers +from hmac import compare_digest as constant_time_compare ALGO_NAME = "{SSHA}" @@ -63,12 +64,7 @@ def checkPassword(challenge_password, password): salt = challenge_bytes[DIGEST_LEN:] hr = hashlib.sha1(password.encode()) hr.update(salt) - valid_password = True - # La comparaison est volontairement en temps constant - # (pour éviter les timing-attacks) - for i, j in zip(digest, hr.digest()): - valid_password &= i == j - return valid_password + return constant_time_compare(digest, hr.digest()) def hash_password_salt(hashed_password): @@ -118,7 +114,8 @@ class CryptPasswordHasher(hashers.BasePasswordHasher): """ assert encoded.startswith(self.algorithm) salt = hash_password_salt(challenge_password) - return crypt.crypt(password.encode(), salt) == challenge.encode() + return constant_time_compare(crypt.crypt(password.encode(), salt), + challenge.encode()) def safe_summary(self, encoded): """ @@ -159,7 +156,9 @@ class MD5PasswordHasher(hashers.BasePasswordHasher): """ assert encoded.startswith(self.algorithm) salt = hash_password_salt(encoded) - return b64encode(hashlib.md5(password.encode() + salt).digest() + salt) == encoded.encode() + return constant_time_compare( + b64encode(hashlib.md5(password.encode() + salt).digest() + salt), + encoded.encode()) def safe_summary(self, encoded): """ From 63748168f22f6202e1323dbf8e325e0103f7cfff Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Sun, 5 Aug 2018 11:14:19 +0200 Subject: [PATCH 057/171] Export complet du prefix v6 --- api/serializers.py | 2 +- machines/models.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/api/serializers.py b/api/serializers.py index a4fa57bc..ba14dde5 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -673,7 +673,7 @@ class SubnetPortsOpenSerializer(serializers.ModelSerializer): class Meta: model = machines.IpType - fields = ('type', 'domaine_ip_start', 'domaine_ip_stop', 'prefix_v6', 'ouverture_ports') + fields = ('type', 'domaine_ip_start', 'domaine_ip_stop', 'complete_prefixv6', 'ouverture_ports') # DHCP diff --git a/machines/models.py b/machines/models.py index 2f5a6a62..007317e6 100644 --- a/machines/models.py +++ b/machines/models.py @@ -336,6 +336,11 @@ class IpType(RevMixin, AclMixin, models.Model): else: return None + @cached_property + def complete_prefixv6(self): + """Return the complete prefix v6 as cidr""" + return str(self.prefix_v6) + "/" + str(self.prefix_v6_length) + def ip_objects(self): """ Renvoie tous les objets ipv4 relié à ce type""" return IpList.objects.filter(ip_type=self) From cd7cf1cac45454abae2c869188e82de99badfdc0 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Sun, 5 Aug 2018 11:18:53 +0200 Subject: [PATCH 058/171] Affichage intelligent des ports ouverts dans la serialisation --- api/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/serializers.py b/api/serializers.py index ba14dde5..05075606 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -656,7 +656,7 @@ class LocalEmailUsersSerializer(NamespacedHMSerializer): class FirewallPortListSerializer(serializers.ModelSerializer): class Meta: model = machines.OuverturePort - fields = ('begin', 'end', 'protocole', 'io') + fields = ('begin', 'end', 'protocole', 'io', 'show_port') class FirewallOuverturePortListSerializer(serializers.ModelSerializer): tcp_ports_in = FirewallPortListSerializer(many=True, read_only=True) From 89bd17a477b26c60cae887be769db5ada32eef7e Mon Sep 17 00:00:00 2001 From: Charlie Date: Sun, 5 Aug 2018 11:33:54 +0200 Subject: [PATCH 059/171] fix export ldap for old hash --- users/models.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/users/models.py b/users/models.py index 695a2053..dc2d106c 100755 --- a/users/models.py +++ b/users/models.py @@ -537,7 +537,16 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, user_ldap.given_name = self.surname.lower() + '_'\ + self.name.lower()[:3] user_ldap.gid = LDAP['user_gid'] - user_ldap.user_password = self.password[:6] + self.password[7:] + if '{SSHA}' in self.password or '{SMD5}' in self.password: + # We remove the extra $ added at import from ldap + user_ldap.user_password = self.password[:6] + self.password[7:] + elif '{crypt}' in self.password: + # depending on the length, we need to remove or not a $ + if len(self.password)==41: + user_ldap.user_password = self.password + else: + user_ldap.user_password = self.password[:7] + self.password[8:] + user_ldap.sambat_nt_password = self.pwd_ntlm.upper() if self.get_shell: user_ldap.login_shell = str(self.get_shell) From 65b3c8768bf9675aacc312a5d4598ae048406870 Mon Sep 17 00:00:00 2001 From: chirac Date: Fri, 22 Jun 2018 17:48:10 +0200 Subject: [PATCH 060/171] Fix gen_range + macaddress non unique --- .../migrations/0089_auto_20180805_1148.py | 21 +++++++++++++++++++ machines/models.py | 9 +++----- 2 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 machines/migrations/0089_auto_20180805_1148.py diff --git a/machines/migrations/0089_auto_20180805_1148.py b/machines/migrations/0089_auto_20180805_1148.py new file mode 100644 index 00000000..76962283 --- /dev/null +++ b/machines/migrations/0089_auto_20180805_1148.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-08-05 09:48 +from __future__ import unicode_literals + +from django.db import migrations +import macaddress.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('machines', '0088_iptype_prefix_v6_length'), + ] + + operations = [ + migrations.AlterField( + model_name='interface', + name='mac_address', + field=macaddress.fields.MACAddressField(integer=False, max_length=17), + ), + ] diff --git a/machines/models.py b/machines/models.py index 2f5a6a62..8efe7633 100644 --- a/machines/models.py +++ b/machines/models.py @@ -351,12 +351,9 @@ class IpType(RevMixin, AclMixin, models.Model): crée les ip une par une. Si elles existent déjà, met à jour le type associé à l'ip""" # Creation du range d'ip dans les objets iplist - networks = [] - for net in self.ip_range.cidrs(): - networks += net.iter_hosts() - ip_obj = [IpList(ip_type=self, ipv4=str(ip)) for ip in networks] + ip_obj = [IpList(ip_type=self, ipv4=str(ip)) for ip in self.ip_range] listes_ip = IpList.objects.filter( - ipv4__in=[str(ip) for ip in networks] + ipv4__in=[str(ip) for ip in self.ip_range] ) # Si il n'y a pas d'ip, on les crée if not listes_ip: @@ -900,7 +897,7 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): blank=True, null=True ) - mac_address = MACAddressField(integer=False, unique=True) + mac_address = MACAddressField(integer=False) machine = models.ForeignKey('Machine', on_delete=models.CASCADE) type = models.ForeignKey('MachineType', on_delete=models.PROTECT) details = models.CharField(max_length=255, blank=True) From 1c75edb24f51eae8afb53f2f5e1d0b0c3442e09b Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Sun, 5 Aug 2018 12:44:21 +0200 Subject: [PATCH 061/171] Remet le field email dans UserForm et AdherentForm --- users/forms.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/users/forms.py b/users/forms.py index cbffd961..b5859539 100644 --- a/users/forms.py +++ b/users/forms.py @@ -220,7 +220,7 @@ class UserChangeForm(FormRevMixin, forms.ModelForm): class Meta: model = Adherent - fields = ('pseudo', 'password', 'surname') + fields = ('pseudo', 'password', 'surname', 'email') def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) @@ -313,6 +313,7 @@ class AdherentForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): 'name', 'surname', 'pseudo', + 'email', 'school', 'comment', 'room', From dc8cf8dbccaebe6d9da81c63e3669012a835cb44 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Sun, 5 Aug 2018 14:36:32 +0200 Subject: [PATCH 062/171] Serialisation des ouvertures de ports individuelles --- api/serializers.py | 9 +++++++++ api/urls.py | 1 + api/views.py | 4 ++++ 3 files changed, 14 insertions(+) diff --git a/api/serializers.py b/api/serializers.py index 05075606..9c0531ca 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -675,6 +675,15 @@ class SubnetPortsOpenSerializer(serializers.ModelSerializer): model = machines.IpType fields = ('type', 'domaine_ip_start', 'domaine_ip_stop', 'complete_prefixv6', 'ouverture_ports') +class InterfacePortsOpenSerializer(serializers.ModelSerializer): + port_lists = FirewallOuverturePortListSerializer(read_only=True, many=True) + ipv4 = serializers.CharField(source='ipv4.ipv4', read_only=True) + ipv6 = Ipv6ListSerializer(many=True, read_only=True) + + class Meta: + model = machines.Interface + fields = ('port_lists', 'ipv4', 'ipv6') + # DHCP diff --git a/api/urls.py b/api/urls.py index 3bcaee0e..19757c1d 100644 --- a/api/urls.py +++ b/api/urls.py @@ -103,6 +103,7 @@ router.register_view(r'dhcp/hostmacip', views.HostMacIpView), router.register_view(r'localemail/users', views.LocalEmailUsersView), # Firewall router.register_view(r'firewall/subnet-ports', views.SubnetPortsOpenView), +router.register_view(r'firewall/interface-ports', views.InterfacePortsOpenView), # DNS router.register_view(r'dns/zones', views.DNSZonesView), router.register_view(r'dns/reverse-zones', views.DNSReverseZonesView), diff --git a/api/views.py b/api/views.py index f284abbe..fae55d1e 100644 --- a/api/views.py +++ b/api/views.py @@ -544,6 +544,10 @@ class SubnetPortsOpenView(generics.ListAPIView): queryset = machines.IpType.objects.all() serializer_class = serializers.SubnetPortsOpenSerializer +class InterfacePortsOpenView(generics.ListAPIView): + queryset = machines.Interface.objects.filter(port_lists__isnull=False).distinct() + serializer_class = serializers.InterfacePortsOpenSerializer + # DNS From a911a082072fe1b1d695924a8dca1db012d0e815 Mon Sep 17 00:00:00 2001 From: chirac Date: Sun, 24 Jun 2018 15:59:04 +0200 Subject: [PATCH 063/171] =?UTF-8?q?Fonctions=20machines=20pour=20r=C3=A9cu?= =?UTF-8?q?p=20les=20tld=20en=20string=20,=20mise=20en=20cache?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- machines/models.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/machines/models.py b/machines/models.py index 8efe7633..cd00203c 100644 --- a/machines/models.py +++ b/machines/models.py @@ -36,6 +36,7 @@ import hashlib import base64 from django.db import models +from django.db.models import Q from django.db.models.signals import post_save, post_delete from django.dispatch import receiver from django.forms import ValidationError @@ -194,6 +195,27 @@ class Machine(RevMixin, FieldPermissionModelMixin, models.Model): "que les vôtres") return True, None + @cached_property + def short_name(self): + """Par defaut, renvoie le nom de la première interface + de cette machine""" + return str(self.interface_set.first().domain.name) + + @cached_property + def all_short_names(self): + """Renvoie de manière unique, le nom des interfaces de cette + machine""" + return Domain.objects.filter( + interface_parent__machine=self + ).values_list('name', flat=True).distinct() + + @cached_property + def all_complete_names(self): + """Renvoie tous les tls complets de la machine""" + return [str(domain) for domain in Domain.objects.filter( + Q(cname__interface_parent__machine=self) | Q(interface_parent__machine=self) + )] + def __init__(self, *args, **kwargs): super(Machine, self).__init__(*args, **kwargs) self.field_permissions = { From 27d18e034b4740d66986a57cda20a75b95f68838 Mon Sep 17 00:00:00 2001 From: Arthur Grisel-Davy Date: Sun, 5 Aug 2018 17:22:12 +0200 Subject: [PATCH 064/171] =?UTF-8?q?serialization=20des=20finfos=20pour=20c?= =?UTF-8?q?r=C3=A9er=20les=20home?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/serializers.py | 9 +++++++++ api/urls.py | 1 + api/views.py | 5 +++++ users/models.py | 5 +++++ 4 files changed, 20 insertions(+) diff --git a/api/serializers.py b/api/serializers.py index 9c0531ca..6cc020f3 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -552,6 +552,15 @@ class AdherentSerializer(NamespacedHMSerializer): 'shell': {'view_name': 'shell-detail'} } +class HomeCreationSerializer(NamespacedHMSerializer): + """Serialize 'users.models.User' minimal infos to create home + """ + uid = serializers.IntegerField(source='uid_number') + gid = serializers.IntegerField(source='gid_number') + + class Meta: + model = users.User + fields = ('pseudo', 'uid', 'gid') class ServiceUserSerializer(NamespacedHMSerializer): """Serialize `users.models.ServiceUser` objects. diff --git a/api/urls.py b/api/urls.py index 19757c1d..abc466e1 100644 --- a/api/urls.py +++ b/api/urls.py @@ -86,6 +86,7 @@ router.register_viewset(r'topologie/room', views.RoomViewSet) router.register(r'topologie/portprofile', views.PortProfileViewSet) # USERS router.register_viewset(r'users/user', views.UserViewSet) +router.register_viewset(r'users/homecreation', views.HomeCreationViewSet) router.register_viewset(r'users/club', views.ClubViewSet) router.register_viewset(r'users/adherent', views.AdherentViewSet) router.register_viewset(r'users/serviceuser', views.ServiceUserViewSet) diff --git a/api/views.py b/api/views.py index fae55d1e..0f6301bc 100644 --- a/api/views.py +++ b/api/views.py @@ -418,6 +418,11 @@ class UserViewSet(viewsets.ReadOnlyModelViewSet): queryset = users.User.objects.all() serializer_class = serializers.UserSerializer +class HomeCreationViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes infos of `users.models.Users` objects to create homes. + """ + queryset = users.User.objects.all() + serializer_class = serializers.HomeCreationSerializer class ClubViewSet(viewsets.ReadOnlyModelViewSet): """Exposes list and details of `users.models.Club` objects. diff --git a/users/models.py b/users/models.py index 695a2053..d414af38 100755 --- a/users/models.py +++ b/users/models.py @@ -283,6 +283,11 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, else: raise NotImplementedError("Type inconnu") + @cached_property + def gid_number(self): + """renvoie le gid par défaut des users""" + return int(LDAP['user_gid']) + @cached_property def is_class_club(self): """ Returns True if the object is a Club (subclassing User) """ From 28b78965f67c0be1afc25593295c93ffe1d510b7 Mon Sep 17 00:00:00 2001 From: detraz Date: Sun, 5 Aug 2018 23:01:42 +0200 Subject: [PATCH 065/171] Mail->email, hotfix --- users/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/users/models.py b/users/models.py index bdc37142..9627d225 100755 --- a/users/models.py +++ b/users/models.py @@ -148,7 +148,7 @@ class UserManager(BaseUserManager): pseudo=pseudo, surname=surname, name=surname, - email=self.normalize_email(mail), + email=self.normalize_email(email), ) user.set_password(password) From e9c0920a4207b7805f576d3090bf5e2f5858a1e1 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Tue, 7 Aug 2018 00:01:46 +0200 Subject: [PATCH 066/171] Fix object interface fonction index ipv6 --- machines/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/machines/views.py b/machines/views.py index 975cac08..3509be43 100644 --- a/machines/views.py +++ b/machines/views.py @@ -1533,7 +1533,7 @@ def index_sshfp(request, machine, machineid): @login_required -@can_view_all(Interface) +@can_view(Interface) def index_ipv6(request, interface, interfaceid): """ View used to display the list of existing IPv6 of an interface """ ipv6_list = Ipv6List.objects.filter(interface=interface) From 57b4d8ff748b4145b11652e65ca97d9ed58de3ad Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Tue, 7 Aug 2018 00:05:17 +0200 Subject: [PATCH 067/171] Header pour execution en python2 --- preferences/templatetags/__init__.py | 1 + re2o/templatetags/design.py | 1 + re2o/templatetags/self_adhesion.py | 1 + 3 files changed, 3 insertions(+) diff --git a/preferences/templatetags/__init__.py b/preferences/templatetags/__init__.py index 86d112b2..c0a03b85 100644 --- a/preferences/templatetags/__init__.py +++ b/preferences/templatetags/__init__.py @@ -1,3 +1,4 @@ +# -*- mode: python; coding: utf-8 -*- #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. diff --git a/re2o/templatetags/design.py b/re2o/templatetags/design.py index 87a0e0f8..c64e9b40 100644 --- a/re2o/templatetags/design.py +++ b/re2o/templatetags/design.py @@ -1,3 +1,4 @@ +# -*- mode: python; coding: utf-8 -*- #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. diff --git a/re2o/templatetags/self_adhesion.py b/re2o/templatetags/self_adhesion.py index 3b463e68..09fe5ede 100644 --- a/re2o/templatetags/self_adhesion.py +++ b/re2o/templatetags/self_adhesion.py @@ -1,3 +1,4 @@ +# -*- mode: python; coding: utf-8 -*- # 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. From efbd0a18da2c4027b859b8c4b8a29637bc8b5754 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Tue, 7 Aug 2018 00:08:37 +0200 Subject: [PATCH 068/171] L'ipv6 n'est pas unique au niveau bdd pour if serveurs multiples --- .../migrations/0090_auto_20180805_1459.py | 20 +++++++++++++++++++ machines/models.py | 1 - 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 machines/migrations/0090_auto_20180805_1459.py diff --git a/machines/migrations/0090_auto_20180805_1459.py b/machines/migrations/0090_auto_20180805_1459.py new file mode 100644 index 00000000..08af8587 --- /dev/null +++ b/machines/migrations/0090_auto_20180805_1459.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-08-05 12:59 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('machines', '0089_auto_20180805_1148'), + ] + + operations = [ + migrations.AlterField( + model_name='ipv6list', + name='ipv6', + field=models.GenericIPAddressField(protocol='IPv6'), + ), + ] diff --git a/machines/models.py b/machines/models.py index 139bb7eb..1f8cbc0b 100644 --- a/machines/models.py +++ b/machines/models.py @@ -1192,7 +1192,6 @@ class Ipv6List(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): ipv6 = models.GenericIPAddressField( protocol='IPv6', - unique=True ) interface = models.ForeignKey( 'Interface', From 34fe6e06b7a2bee015ce185040c4c52db1f44b86 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Tue, 7 Aug 2018 00:10:54 +0200 Subject: [PATCH 069/171] Specification optionnelle du range contenant domain ipstart-stop --- machines/forms.py | 8 ++-- .../migrations/0091_auto_20180806_2310.py | 26 ++++++++++++ machines/models.py | 42 +++++++++++++++++++ machines/templates/machines/aff_iptype.html | 2 +- 4 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 machines/migrations/0091_auto_20180806_2310.py diff --git a/machines/forms.py b/machines/forms.py index 49150349..c0af0bd6 100644 --- a/machines/forms.py +++ b/machines/forms.py @@ -217,10 +217,7 @@ class IpTypeForm(FormRevMixin, ModelForm): stop après creation""" class Meta: model = IpType - fields = ['type', 'extension', 'need_infra', 'domaine_ip_start', - 'domaine_ip_stop', 'dnssec_reverse_v4', 'prefix_v6', - 'prefix_v6_length','dnssec_reverse_v6', 'vlan', - 'ouverture_ports'] + fields = '__all__' def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) @@ -232,7 +229,8 @@ class EditIpTypeForm(IpTypeForm): """Edition d'un iptype. Pas d'edition du rangev4 possible, car il faudrait synchroniser les objets iplist""" class Meta(IpTypeForm.Meta): - fields = ['extension', 'type', 'need_infra', 'prefix_v6', 'prefix_v6_length', + fields = ['extension', 'type', 'need_infra', 'domaine_ip_network', 'domaine_ip_netmask', + 'prefix_v6', 'prefix_v6_length', 'vlan', 'dnssec_reverse_v4', 'dnssec_reverse_v6', 'ouverture_ports'] diff --git a/machines/migrations/0091_auto_20180806_2310.py b/machines/migrations/0091_auto_20180806_2310.py new file mode 100644 index 00000000..cd756cad --- /dev/null +++ b/machines/migrations/0091_auto_20180806_2310.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-08-06 21:10 +from __future__ import unicode_literals + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('machines', '0090_auto_20180805_1459'), + ] + + operations = [ + migrations.AddField( + model_name='iptype', + name='domaine_ip_netmask', + field=models.IntegerField(default=24, help_text='Netmask for the ipv4 range domain', validators=[django.core.validators.MaxValueValidator(31), django.core.validators.MinValueValidator(8)]), + ), + migrations.AddField( + model_name='iptype', + name='domaine_ip_network', + field=models.GenericIPAddressField(blank=True, help_text='Network containing the ipv4 range domain ip start/stop. Optional', null=True, protocol='IPv4'), + ), + ] diff --git a/machines/models.py b/machines/models.py index 1f8cbc0b..34f91adf 100644 --- a/machines/models.py +++ b/machines/models.py @@ -278,6 +278,20 @@ class IpType(RevMixin, AclMixin, models.Model): need_infra = models.BooleanField(default=False) domaine_ip_start = models.GenericIPAddressField(protocol='IPv4') domaine_ip_stop = models.GenericIPAddressField(protocol='IPv4') + domaine_ip_network = models.GenericIPAddressField( + protocol='IPv4', + null=True, + blank=True, + help_text="Network containing the ipv4 range domain ip start/stop. Optional" + ) + domaine_ip_netmask = models.IntegerField( + default=24, + validators=[ + MaxValueValidator(31), + MinValueValidator(8) + ], + help_text="Netmask for the ipv4 range domain" + ) dnssec_reverse_v4 = models.BooleanField( default=False, help_text="Activer DNSSEC sur le reverse DNS IPv4", @@ -331,6 +345,11 @@ class IpType(RevMixin, AclMixin, models.Model): """ Renvoie une liste des ip en string""" return [str(x) for x in self.ip_set] + @cached_property + def ip_set_cidrs_as_str(self): + """Renvoie la liste des cidrs du range en str""" + return [str(ip_range) for ip_range in self.ip_set.iter_cidrs()] + @cached_property def ip_set_full_info(self): """Iter sur les range cidr, et renvoie network, broacast , etc""" @@ -358,6 +377,24 @@ class IpType(RevMixin, AclMixin, models.Model): else: return None + @cached_property + def ip_network(self): + """Renvoie le network parent du range start-stop, si spécifié + Différent de ip_set_cidrs ou iP_set, car lui est supérieur ou égal""" + if self.domaine_ip_network: + return IPNetwork(str(self.domaine_ip_network) + '/' + str(self.domaine_ip_netmask)) + return None + + @cached_property + def ip_net_full_info(self): + """Renvoie les infos du network contenant du range""" + return { + 'network' : str(self.ip_network.network), + 'netmask' : str(self.ip_network.netmask), + 'broadcast' : str(self.ip_network.broadcast), + 'netmask_cidr' : str(self.ip_network.prefixlen), + } + @cached_property def complete_prefixv6(self): """Return the complete prefix v6 as cidr""" @@ -442,6 +479,11 @@ class IpType(RevMixin, AclMixin, models.Model): # On formate le prefix v6 if self.prefix_v6: self.prefix_v6 = str(IPNetwork(self.prefix_v6 + '/64').network) + # On vérifie qu'un domaine network/netmask contiens bien le domaine ip start-stop + if self.domaine_ip_network: + if not self.domaine_ip_start in self.ip_network or not self.domaine_ip_stop in self.ip_network: + raise ValidationError("If you specify a domaine ip network/netmask, it\ + must contain domaine ipstart-stop range") return def save(self, *args, **kwargs): diff --git a/machines/templates/machines/aff_iptype.html b/machines/templates/machines/aff_iptype.html index 7d4de6c5..ccb70a14 100644 --- a/machines/templates/machines/aff_iptype.html +++ b/machines/templates/machines/aff_iptype.html @@ -46,7 +46,7 @@ with this program; if not, write to the Free Software Foundation, Inc., - + From e86a0ff58b2940d0f39a375aee53bf4645d235f6 Mon Sep 17 00:00:00 2001 From: Charlie Jacomme Date: Tue, 7 Aug 2018 09:24:21 +0200 Subject: [PATCH 070/171] add prefix_v6_length serializer --- api/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/serializers.py b/api/serializers.py index 6cc020f3..55345b18 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -864,7 +864,7 @@ class DNSReverseZonesSerializer(serializers.ModelSerializer): model = machines.IpType fields = ('type', 'extension', 'soa', 'ns_records', 'mx_records', 'txt_records', 'ptr_records', 'ptr_v6_records', 'cidrs', - 'prefix_v6') + 'prefix_v6', 'prefix_v6_length') # MAILING From cee0d1188ee5e51e6e9e8889810ffeea89b4e34e Mon Sep 17 00:00:00 2001 From: Charlie Jacomme Date: Tue, 7 Aug 2018 09:52:00 +0200 Subject: [PATCH 071/171] rename booleans dnssec_reverse_* to reverse_* --- machines/forms.py | 2 +- .../migrations/0092_auto_20180807_0926.py | 25 +++++++++++++++++++ machines/models.py | 8 +++--- machines/templates/machines/aff_iptype.html | 2 +- 4 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 machines/migrations/0092_auto_20180807_0926.py diff --git a/machines/forms.py b/machines/forms.py index c0af0bd6..b68cb203 100644 --- a/machines/forms.py +++ b/machines/forms.py @@ -231,7 +231,7 @@ class EditIpTypeForm(IpTypeForm): class Meta(IpTypeForm.Meta): fields = ['extension', 'type', 'need_infra', 'domaine_ip_network', 'domaine_ip_netmask', 'prefix_v6', 'prefix_v6_length', - 'vlan', 'dnssec_reverse_v4', 'dnssec_reverse_v6', + 'vlan', 'reverse_v4', 'reverse_v6', 'ouverture_ports'] diff --git a/machines/migrations/0092_auto_20180807_0926.py b/machines/migrations/0092_auto_20180807_0926.py new file mode 100644 index 00000000..f109a650 --- /dev/null +++ b/machines/migrations/0092_auto_20180807_0926.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-08-07 07:26 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('machines', '0091_auto_20180806_2310'), + ] + + operations = [ + migrations.RenameField( + model_name='iptype', + old_name='dnssec_reverse_v4', + new_name='reverse_v4', + ), + migrations.RenameField( + model_name='iptype', + old_name='dnssec_reverse_v6', + new_name='reverse_v6', + ), + ] diff --git a/machines/models.py b/machines/models.py index 34f91adf..2bf362eb 100644 --- a/machines/models.py +++ b/machines/models.py @@ -292,9 +292,9 @@ class IpType(RevMixin, AclMixin, models.Model): ], help_text="Netmask for the ipv4 range domain" ) - dnssec_reverse_v4 = models.BooleanField( + reverse_v4 = models.BooleanField( default=False, - help_text="Activer DNSSEC sur le reverse DNS IPv4", + help_text="Enable reverse DNS for IPv4", ) prefix_v6 = models.GenericIPAddressField( protocol='IPv6', @@ -308,9 +308,9 @@ class IpType(RevMixin, AclMixin, models.Model): MinValueValidator(0) ] ) - dnssec_reverse_v6 = models.BooleanField( + reverse_v6 = models.BooleanField( default=False, - help_text="Activer DNSSEC sur le reverse DNS IPv6", + help_text="Enable reverse DNS for IPv6", ) vlan = models.ForeignKey( 'Vlan', diff --git a/machines/templates/machines/aff_iptype.html b/machines/templates/machines/aff_iptype.html index ccb70a14..afd35d1b 100644 --- a/machines/templates/machines/aff_iptype.html +++ b/machines/templates/machines/aff_iptype.html @@ -48,7 +48,7 @@ with this program; if not, write to the Free Software Foundation, Inc., - + - + @@ -46,7 +46,7 @@ with this program; if not, write to the Free Software Foundation, Inc., - + - + @@ -41,7 +41,8 @@ with this program; if not, write to the Free Software Foundation, Inc., - + + + {% if users.adherent.gpg_fingerprint %} + + + {% endif %} + + {% if users.shell %} From 7deb99f8838ed27d55cea8471962b43202eb9864 Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Tue, 14 Aug 2018 10:10:53 +0200 Subject: [PATCH 110/171] Fix les migrations --- users/migrations/0074_auto_20180810_2104.py | 2 +- users/migrations/0075_auto_20180811_0420.py | 26 --------------------- 2 files changed, 1 insertion(+), 27 deletions(-) delete mode 100644 users/migrations/0075_auto_20180811_0420.py diff --git a/users/migrations/0074_auto_20180810_2104.py b/users/migrations/0074_auto_20180810_2104.py index 5ab77369..bc32a266 100644 --- a/users/migrations/0074_auto_20180810_2104.py +++ b/users/migrations/0074_auto_20180810_2104.py @@ -17,6 +17,6 @@ class Migration(migrations.Migration): migrations.AddField( model_name='adherent', name='gpg_fingerprint', - field=models.CharField(blank=True, max_length=40, null=True, validators=[django.core.validators.RegexValidator('^[0-9A-F]{40}$', message='Une fingerprint GPG doit contenit 40 caractères hexadécimaux')]), + field=models.CharField(blank=True, max_length=40, null=True, validators=[django.core.validators.RegexValidator('^[0-9A-F]{40}$', message='Une fingerprint GPG doit contenir 40 caractères hexadécimaux')]), ), ] diff --git a/users/migrations/0075_auto_20180811_0420.py b/users/migrations/0075_auto_20180811_0420.py deleted file mode 100644 index 8ea2526f..00000000 --- a/users/migrations/0075_auto_20180811_0420.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10.7 on 2018-08-11 02:20 -from __future__ import unicode_literals - -import django.core.validators -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('users', '0074_auto_20180810_2104'), - ] - - operations = [ - migrations.AlterField( - model_name='adherent', - name='gpg_fingerprint', - field=models.CharField(blank=True, max_length=40, null=True, validators=[django.core.validators.RegexValidator('^[0-9A-F]{40}$', message='Une fingerprint GPG doit contenir 40 caractères hexadécimaux')]), - ), - migrations.AlterField( - model_name='user', - name='email', - field=models.EmailField(max_length=254, unique=True), - ), - ] From b73ccdd0430f4ce0e3cbfb688ff8b9ed217ae0c3 Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Tue, 14 Aug 2018 10:11:53 +0200 Subject: [PATCH 111/171] Affiche la fingerpring PGP correctement. --- users/templates/users/profil.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/users/templates/users/profil.html b/users/templates/users/profil.html index 84d23009..c2053612 100644 --- a/users/templates/users/profil.html +++ b/users/templates/users/profil.html @@ -51,7 +51,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% elif not users.is_adherent %} + @@ -241,7 +241,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% if users.adherent.gpg_fingerprint %} - + {% endif %} From 84764ce51b9e9a141a150c1c6c3beb9b66ce2351 Mon Sep 17 00:00:00 2001 From: grisel-davy Date: Thu, 9 Aug 2018 23:43:35 +0200 Subject: [PATCH 112/171] verification des extension mail externe et lower des adresses mail pour mieux verifier les doublons --- users/forms.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/users/forms.py b/users/forms.py index 49135266..97fb3fc3 100644 --- a/users/forms.py +++ b/users/forms.py @@ -138,6 +138,12 @@ class UserCreationForm(FormRevMixin, forms.ModelForm): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(UserCreationForm, self).__init__(*args, prefix=prefix, **kwargs) + def clean_email(self): + if not OptionalUser.objects.first().local_email_domain in self.cleaned_data.get('email'): + return self.cleaned_data.get('email').lower() + else: + raise forms.ValidationError("You can't use an internal address as your external address.") + class Meta: model = Adherent fields = ('pseudo', 'surname', 'email') @@ -308,6 +314,12 @@ class AdherentForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): self.fields['room'].empty_label = "Pas de chambre" self.fields['school'].empty_label = "Séléctionner un établissement" + def clean_email(self): + if not OptionalUser.objects.first().local_email_domain in self.cleaned_data.get('email'): + return self.cleaned_data.get('email').lower() + else: + raise forms.ValidationError("Vous ne pouvez pas utiliser une addresse {}".format(OptionalUser.objects.first().local_email_domain)) + class Meta: model = Adherent fields = [ @@ -323,6 +335,7 @@ class AdherentForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): 'gpg_fingerprint' ] + def clean_telephone(self): """Verifie que le tel est présent si 'option est validée dans preferences""" @@ -607,6 +620,9 @@ class EMailAddressForm(FormRevMixin, ModelForm): super(EMailAddressForm, self).__init__(*args, prefix=prefix, **kwargs) self.fields['local_part'].label = "Local part of the email" self.fields['local_part'].help_text = "Can't contain @" + + def clean_local_part(self): + return self.cleaned_data.get('local_part').lower() class Meta: model = EMailAddress @@ -631,6 +647,12 @@ class EmailSettingsForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): "Enable the use of the local email account" ) + def clean_email(self): + if not OptionalUser.objects.first().local_email_domain in self.cleaned_data.get('email'): + return self.cleaned_data.get('email').lower() + else: + raise forms.ValidationError("Vous ne pouvez pas utiliser une addresse {}".format(OptionalUser.objects.first().local_email_domain)) + class Meta: model = User fields = ['email', 'local_email_redirect', 'local_email_enabled'] From e70b063a3dada89baa85e217cd18767f3e44a2bb Mon Sep 17 00:00:00 2001 From: Charlie Jacomme Date: Fri, 10 Aug 2018 19:27:10 +0200 Subject: [PATCH 113/171] fix mail problems: check pseudo taken and lower) --- users/models.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/users/models.py b/users/models.py index 01de431c..121d83e2 100755 --- a/users/models.py +++ b/users/models.py @@ -103,6 +103,13 @@ def linux_user_validator(login): params={'label': login}, ) +def pseudo_taken(login): + """ Retourne une erreur de validation si le login ne respecte + pas les contraintes unix (maj, min, chiffres ou tiret)""" + if (EMailAddress.objects + .filter(local_part=login.lower())): + raise forms.ValidationError('Pseudo is already taken') + def get_fresh_user_uid(): """ Renvoie le plus petit uid non pris. Fonction très paresseuse """ @@ -193,7 +200,7 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, max_length=32, unique=True, help_text="Doit contenir uniquement des lettres, chiffres, ou tirets", - validators=[linux_user_validator] + validators=[linux_user_validator, pseudo_taken] ) email = models.EmailField() local_email_redirect = models.BooleanField( @@ -966,8 +973,8 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, """Check if this pseudo is already used by any mailalias. Better than raising an error in post-save and catching it""" if (EMailAddress.objects - .filter(local_part=self.pseudo) - .exclude(user=self)): + .filter(local_part=self.pseudo.lower()).exclude(user=self) + ): raise ValidationError("This pseudo is already in use.") def __str__(self): @@ -1106,7 +1113,7 @@ def user_post_save(**kwargs): Synchronise le ldap""" is_created = kwargs['created'] user = kwargs['instance'] - EMailAddress.objects.get_or_create(local_part=user.pseudo, user=user) + EMailAddress.objects.get_or_create(local_part=user.pseudo.lower(), user=user) if is_created: user.notif_inscription() user.state_sync() @@ -1803,6 +1810,7 @@ class EMailAddress(RevMixin, AclMixin, models.Model): "local email account") def clean(self, *args, **kwargs): + self.local_part = self.local_part.lower() if "@" in self.local_part: raise ValidationError("The local part cannot contain a @") super(EMailAddress, self).clean(*args, **kwargs) From ff2a6c27221ad853b6a022157812bd79aba00925 Mon Sep 17 00:00:00 2001 From: Charlie Jacomme Date: Sat, 11 Aug 2018 00:13:27 +0200 Subject: [PATCH 114/171] many fix mail --- users/models.py | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/users/models.py b/users/models.py index 121d83e2..ff596424 100755 --- a/users/models.py +++ b/users/models.py @@ -53,6 +53,7 @@ import sys from django.db import models from django.db.models import Q from django import forms +from django.forms import ValidationError from django.db.models.signals import post_save, post_delete, m2m_changed from django.dispatch import receiver from django.utils.functional import cached_property @@ -103,14 +104,6 @@ def linux_user_validator(login): params={'label': login}, ) -def pseudo_taken(login): - """ Retourne une erreur de validation si le login ne respecte - pas les contraintes unix (maj, min, chiffres ou tiret)""" - if (EMailAddress.objects - .filter(local_part=login.lower())): - raise forms.ValidationError('Pseudo is already taken') - - def get_fresh_user_uid(): """ Renvoie le plus petit uid non pris. Fonction très paresseuse """ uids = list(range( @@ -200,9 +193,9 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, max_length=32, unique=True, help_text="Doit contenir uniquement des lettres, chiffres, ou tirets", - validators=[linux_user_validator, pseudo_taken] + validators=[linux_user_validator] ) - email = models.EmailField() + email = models.EmailField(unique=True) local_email_redirect = models.BooleanField( default=False, help_text="Whether or not to redirect the local email messages to the main email." @@ -293,7 +286,7 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, if not OptionalUser.get_cached_value('local_email_accounts_enabled') or not self.local_email_enabled or self.local_email_redirect: return str(self.email) else: - return str(self.emailaddress_set.get(local_part=self.pseudo)) + return str(self.emailaddress_set.get(local_part=self.pseudo.lower())) @cached_property def class_name(self): @@ -973,7 +966,7 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, """Check if this pseudo is already used by any mailalias. Better than raising an error in post-save and catching it""" if (EMailAddress.objects - .filter(local_part=self.pseudo.lower()).exclude(user=self) + .filter(local_part=self.pseudo.lower()).exclude(user_id=self.id) ): raise ValidationError("This pseudo is already in use.") @@ -1775,7 +1768,7 @@ class EMailAddress(RevMixin, AclMixin, models.Model): a message and a boolean which is True if the user can delete the local email account. """ - if self.local_part == self.user.pseudo: + if self.local_part == self.user.pseudo.lower(): return False, ("You cannot delete a local email account whose " "local part is the same as the username.") if user_request.has_perm('users.delete_emailaddress'): @@ -1797,7 +1790,7 @@ class EMailAddress(RevMixin, AclMixin, models.Model): a message and a boolean which is True if the user can edit the local email account. """ - if self.local_part == self.user.pseudo: + if self.local_part == self.user.pseudo.lower(): return False, ("You cannot edit a local email account whose " "local part is the same as the username.") if user_request.has_perm('users.change_emailaddress'): From cef93af15a5c45b1a5ea4afcb566d67c59dc2dce Mon Sep 17 00:00:00 2001 From: Charlie Jacomme Date: Sat, 11 Aug 2018 04:32:50 +0200 Subject: [PATCH 115/171] last email migration --- users/migrations/0076_auto_20180811_0425.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 users/migrations/0076_auto_20180811_0425.py diff --git a/users/migrations/0076_auto_20180811_0425.py b/users/migrations/0076_auto_20180811_0425.py new file mode 100644 index 00000000..0b53af9a --- /dev/null +++ b/users/migrations/0076_auto_20180811_0425.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-08-11 02:25 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0075_auto_20180811_0420'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='email', + field=models.EmailField(max_length=254, unique=True), + ), + ] From 680b8a7ec73b81be0730c05dff15d2a664b4fa79 Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Tue, 14 Aug 2018 12:21:58 +0200 Subject: [PATCH 116/171] Rend l'affichage des mails locaux plus intuitif --- CHANGELOG.md | 8 ++++++ static/js/email_address.js | 17 ++++++++++++ users/forms.py | 13 +++------ users/migrations/0074_auto_20180814_1059.py | 30 +++++++++++++++++++++ users/migrations/0076_auto_20180811_0425.py | 20 -------------- users/models.py | 13 +++++---- users/templates/users/profil.html | 28 ++++++++++--------- users/templates/users/user.html | 3 +++ users/views.py | 6 +++-- 9 files changed, 88 insertions(+), 50 deletions(-) create mode 100644 static/js/email_address.js create mode 100644 users/migrations/0074_auto_20180814_1059.py delete mode 100644 users/migrations/0076_auto_20180811_0425.py diff --git a/CHANGELOG.md b/CHANGELOG.md index f390ba09..5ec2da1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -128,3 +128,11 @@ You need to ensure that your database character set is utf-8. ```sql ALTER DATABASE re2o CHARACTER SET utf8; ``` + +## MR 247: Fix des comptes mails + +Fix several issues with email accounts, you need to collect the static files. + +```bash +./manage.py collectstatic +``` diff --git a/static/js/email_address.js b/static/js/email_address.js new file mode 100644 index 00000000..10c1f544 --- /dev/null +++ b/static/js/email_address.js @@ -0,0 +1,17 @@ +/** To enable the redirection has no meaning if the local email adress is not + * enabled. Thus this function enable the checkbox if needed and changes its + * state. + */ +function enable_redirection_chkbox() { + var redirect = document.getElementById('id_User-local_email_redirect'); + var enabled = document.getElementById('id_User-local_email_enabled').checked; + if(!enabled) + { + redirect.checked = false; + } + redirect.disabled = !enabled; +} + +var enabled_chkbox = document.getElementById('id_User-local_email_enabled'); +enabled_chkbox.onclick = enable_redirection_chkbox; +enable_redirection_chkbox(); diff --git a/users/forms.py b/users/forms.py index 97fb3fc3..dac86d71 100644 --- a/users/forms.py +++ b/users/forms.py @@ -620,7 +620,7 @@ class EMailAddressForm(FormRevMixin, ModelForm): super(EMailAddressForm, self).__init__(*args, prefix=prefix, **kwargs) self.fields['local_part'].label = "Local part of the email" self.fields['local_part'].help_text = "Can't contain @" - + def clean_local_part(self): return self.cleaned_data.get('local_part').lower() @@ -634,18 +634,11 @@ class EmailSettingsForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(EmailSettingsForm, self).__init__(*args, prefix=prefix, **kwargs) - self.fields['email'].label = "Contact email address" + self.fields['email'].label = "Main email address" if 'local_email_redirect' in self.fields: self.fields['local_email_redirect'].label = "Redirect local emails" - self.fields['local_email_redirect'].help_text = ( - "Enable the automated redirection of the local email address " - "to the contact email address" - ) if 'local_email_enabled' in self.fields: self.fields['local_email_enabled'].label = "Use local emails" - self.fields['local_email_enabled'].help_text = ( - "Enable the use of the local email account" - ) def clean_email(self): if not OptionalUser.objects.first().local_email_domain in self.cleaned_data.get('email'): @@ -655,4 +648,4 @@ class EmailSettingsForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): class Meta: model = User - fields = ['email', 'local_email_redirect', 'local_email_enabled'] + fields = ['email','local_email_enabled', 'local_email_redirect'] diff --git a/users/migrations/0074_auto_20180814_1059.py b/users/migrations/0074_auto_20180814_1059.py new file mode 100644 index 00000000..ced792f4 --- /dev/null +++ b/users/migrations/0074_auto_20180814_1059.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-08-14 08:59 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0073_auto_20180629_1614'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='email', + field=models.EmailField(blank=True, help_text='External email address allowing us to contact you.', max_length=254), + ), + migrations.AlterField( + model_name='user', + name='local_email_enabled', + field=models.BooleanField(default=False, help_text='Enable the local email account.'), + ), + migrations.AlterField( + model_name='user', + name='local_email_redirect', + field=models.BooleanField(default=False, help_text='Enable redirection of the local email messages to the main email.'), + ), + ] diff --git a/users/migrations/0076_auto_20180811_0425.py b/users/migrations/0076_auto_20180811_0425.py deleted file mode 100644 index 0b53af9a..00000000 --- a/users/migrations/0076_auto_20180811_0425.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10.7 on 2018-08-11 02:25 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('users', '0075_auto_20180811_0420'), - ] - - operations = [ - migrations.AlterField( - model_name='user', - name='email', - field=models.EmailField(max_length=254, unique=True), - ), - ] diff --git a/users/models.py b/users/models.py index ff596424..7331a460 100755 --- a/users/models.py +++ b/users/models.py @@ -195,14 +195,17 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, help_text="Doit contenir uniquement des lettres, chiffres, ou tirets", validators=[linux_user_validator] ) - email = models.EmailField(unique=True) + email = models.EmailField( + blank=True, + help_text="External email address allowing us to contact you." + ) local_email_redirect = models.BooleanField( default=False, - help_text="Whether or not to redirect the local email messages to the main email." + help_text="Enable redirection of the local email messages to the main email." ) local_email_enabled = models.BooleanField( default=False, - help_text="Wether or not to enable the local email account." + help_text="Enable the local email account." ) school = models.ForeignKey( 'School', @@ -565,7 +568,7 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, # depending on the length, we need to remove or not a $ if len(self.password)==41: user_ldap.user_password = self.password - else: + else: user_ldap.user_password = self.password[:7] + self.password[8:] user_ldap.sambat_nt_password = self.pwd_ntlm.upper() @@ -1771,7 +1774,7 @@ class EMailAddress(RevMixin, AclMixin, models.Model): if self.local_part == self.user.pseudo.lower(): return False, ("You cannot delete a local email account whose " "local part is the same as the username.") - if user_request.has_perm('users.delete_emailaddress'): + if user_request.has_perm('users.delete_emailaddress'): return True, None if not OptionalUser.get_cached_value('local_email_accounts_enabled'): return False, "The local email accounts are not enabled." diff --git a/users/templates/users/profil.html b/users/templates/users/profil.html index c2053612..f573941b 100644 --- a/users/templates/users/profil.html +++ b/users/templates/users/profil.html @@ -27,15 +27,16 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load acl %} {% load logs_extra %} {% load design %} +{% load i18n %} {% block title %}Profil{% endblock %} {% block content %}
{% if user == users %} -

Welcome {{ users.name }} {{ users.surname }}

+

Welcome {{ users.name }} {{ users.surname }}

{% else %} -

Profil de {{ users.name }} {{ users.surname }}

+

Profil de {{ users.name }} {{ users.surname }}

{% endif %}
Extension Nécessite l'autorisation infra Plage ipv4Préfixe v6Préfixe v6DNSSEC reverse v4/v6 Sur vlan Ouverture ports par défault
{{ type.type }} {{ type.extension }} {{ type.need_infra|tick }}{{ type.domaine_ip_start }}-{{ type.domaine_ip_stop }}{{ type.prefix_v6 }}{{ type.domaine_ip_start }}-{{ type.domaine_ip_stop }}{{ type.prefix_v6 }}{{ type.dnssec_reverse_v4|tick }}/{{ type.dnssec_reverse_v6|tick }} {{ type.vlan }} {{ type.ouverture_ports }} From 7f6bc6c2ecb4d8edadca305cae039f4d92fc1d25 Mon Sep 17 00:00:00 2001 From: Charlie Jacomme Date: Mon, 25 Jun 2018 17:17:05 +0200 Subject: [PATCH 045/171] migrations for dnssec reverse --- .../migrations/0089_auto_20180625_1700.py | 25 ++++++++++++++ .../migrations/0090_auto_20180625_1706.py | 33 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 machines/migrations/0089_auto_20180625_1700.py create mode 100644 machines/migrations/0090_auto_20180625_1706.py diff --git a/machines/migrations/0089_auto_20180625_1700.py b/machines/migrations/0089_auto_20180625_1700.py new file mode 100644 index 00000000..dbaee1a8 --- /dev/null +++ b/machines/migrations/0089_auto_20180625_1700.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-06-25 15:00 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('machines', '0088_dname'), + ] + + operations = [ + migrations.AddField( + model_name='extension', + name='dnssec_reverse_v4', + field=models.BooleanField(default=False, help_text='Activer DNSSEC sur le reverse DNS IPv4'), + ), + migrations.AddField( + model_name='extension', + name='dnssec_reverse_v6', + field=models.BooleanField(default=False, help_text='Activer DNSSEC sur le reverse DNS IPv6'), + ), + ] diff --git a/machines/migrations/0090_auto_20180625_1706.py b/machines/migrations/0090_auto_20180625_1706.py new file mode 100644 index 00000000..9c5e523c --- /dev/null +++ b/machines/migrations/0090_auto_20180625_1706.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-06-25 15:06 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('machines', '0089_auto_20180625_1700'), + ] + + operations = [ + migrations.RemoveField( + model_name='extension', + name='dnssec_reverse_v4', + ), + migrations.RemoveField( + model_name='extension', + name='dnssec_reverse_v6', + ), + migrations.AddField( + model_name='iptype', + name='dnssec_reverse_v4', + field=models.BooleanField(default=False, help_text='Activer DNSSEC sur le reverse DNS IPv4'), + ), + migrations.AddField( + model_name='iptype', + name='dnssec_reverse_v6', + field=models.BooleanField(default=False, help_text='Activer DNSSEC sur le reverse DNS IPv6'), + ), + ] From bf06d133f0e213eb66a16bbdc7b893ce58cdbb13 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Wed, 15 Nov 2017 03:06:33 +0100 Subject: [PATCH 046/171] =?UTF-8?q?Fix=20bug=20sur=20l'edition=20du=20txt?= =?UTF-8?q?=20+=20=C3=A9largi=20le=20champ=20pour=20dnssec?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- machines/templates/machines/aff_txt.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/machines/templates/machines/aff_txt.html b/machines/templates/machines/aff_txt.html index 27d78d11..e961d7a9 100644 --- a/machines/templates/machines/aff_txt.html +++ b/machines/templates/machines/aff_txt.html @@ -34,7 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{{ txt.zone }} {{ txt.dns_entry }}{{ type.extension }} {{ type.need_infra|tick }} {{ type.domaine_ip_start }}-{{ type.domaine_ip_stop }}{{ type.prefix_v6 }}{{ type.prefix_v6 }}/{{ type.prefix_v6_length }} {{ type.dnssec_reverse_v4|tick }}/{{ type.dnssec_reverse_v6|tick }} {{ type.vlan }} {{ type.ouverture_ports }}{{ type.type }} {{ type.extension }} {{ type.need_infra|tick }}{{ type.domaine_ip_start }}-{{ type.domaine_ip_stop }}{{ type.domaine_ip_start }}-{{ type.domaine_ip_stop }}{% if type.ip_network %} on {{ type.ip_network }}{% endif %} {{ type.prefix_v6 }}/{{ type.prefix_v6_length }} {{ type.dnssec_reverse_v4|tick }}/{{ type.dnssec_reverse_v6|tick }} {{ type.vlan }}{{ type.need_infra|tick }} {{ type.domaine_ip_start }}-{{ type.domaine_ip_stop }}{% if type.ip_network %} on {{ type.ip_network }}{% endif %} {{ type.prefix_v6 }}/{{ type.prefix_v6_length }}{{ type.dnssec_reverse_v4|tick }}/{{ type.dnssec_reverse_v6|tick }}{{ type.reverse_v4|tick }}/{{ type.reverse_v6|tick }} {{ type.vlan }} {{ type.ouverture_ports }} From b9a4dd6d65579322df9195faefb727a2d5d4484d Mon Sep 17 00:00:00 2001 From: Charlie Jacomme Date: Tue, 7 Aug 2018 09:57:59 +0200 Subject: [PATCH 072/171] ptr are empty if revere disabled --- machines/models.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/machines/models.py b/machines/models.py index 2bf362eb..b9fd9205 100644 --- a/machines/models.py +++ b/machines/models.py @@ -450,14 +450,20 @@ class IpType(RevMixin, AclMixin, models.Model): def get_associated_ptr_records(self): from re2o.utils import all_active_assigned_interfaces - return (all_active_assigned_interfaces() - .filter(type__ip_type=self) - .filter(ipv4__isnull=False)) + if self.reverse_v4: + return (all_active_assigned_interfaces() + .filter(type__ip_type=self) + .filter(ipv4__isnull=False)) + else: + return None def get_associated_ptr_v6_records(self): from re2o.utils import all_active_interfaces - return (all_active_interfaces(full=True) - .filter(type__ip_type=self)) + if self.reverse_v6: + return (all_active_interfaces(full=True) + .filter(type__ip_type=self)) + else: + return None def clean(self): """ Nettoyage. Vérifie : From b89823faa111712de395d198a7dcd4ce0bac23ac Mon Sep 17 00:00:00 2001 From: Charlie Jacomme Date: Tue, 7 Aug 2018 10:30:23 +0200 Subject: [PATCH 073/171] we want full ndd for ns and mx --- api/serializers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/serializers.py b/api/serializers.py index 55345b18..1da76e6c 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -734,7 +734,7 @@ class NSRecordSerializer(NsSerializer): """Serialize `machines.models.Ns` objects with the data needed to generate a NS DNS record. """ - target = serializers.CharField(source='ns.name', read_only=True) + target = serializers.CharField(source='ns', read_only=True) class Meta(NsSerializer.Meta): fields = ('target',) @@ -744,7 +744,7 @@ class MXRecordSerializer(MxSerializer): """Serialize `machines.models.Mx` objects with the data needed to generate a MX DNS record. """ - target = serializers.CharField(source='name.name', read_only=True) + target = serializers.CharField(source='name', read_only=True) class Meta(MxSerializer.Meta): fields = ('target', 'priority') From ef2101e0c512dd0ac65bc6c36fad4381c89e4ec6 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Tue, 7 Aug 2018 17:22:21 +0200 Subject: [PATCH 074/171] =?UTF-8?q?Fix=20des=20erreurs,=20et=20am=C3=A9lio?= =?UTF-8?q?re=20la=20lisibilit=C3=A9=20des=20logs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- freeradius_utils/auth.py | 2 +- freeradius_utils/freeradius3/mods-enabled/python | 2 +- topologie/models.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/freeradius_utils/auth.py b/freeradius_utils/auth.py index 023448d1..afa834b0 100644 --- a/freeradius_utils/auth.py +++ b/freeradius_utils/auth.py @@ -348,7 +348,7 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, if not nas_machine: return ('?', u'Chambre inconnue', u'Nas inconnu', VLAN_OK) - sw_name = str(nas_machine) + sw_name = str(getattr(nas_machine, 'short_name', str(nas_machine))) port = (Port.objects .filter( diff --git a/freeradius_utils/freeradius3/mods-enabled/python b/freeradius_utils/freeradius3/mods-enabled/python index 414860a3..d4e99f35 100644 --- a/freeradius_utils/freeradius3/mods-enabled/python +++ b/freeradius_utils/freeradius3/mods-enabled/python @@ -9,7 +9,7 @@ python re2o { module = auth - python_path = /etc/freeradius/3.0:/usr/lib/python2.7/:/usr/lib/python2.7/dist-packages/:/usr/local/lib/python2.7/site-packages/:/usr/local/lib/python2.7/dist-packages/ + python_path = /etc/freeradius/3.0:/usr/lib/python2.7:/usr/lib/python2.7/dist-packages:/usr/local/lib/python2.7/site-packages:/usr/lib/python2.7/lib-dynload:/usr/local/lib/python2.7/dist-packages mod_instantiate = ${.module} func_instantiate = instantiate diff --git a/topologie/models.py b/topologie/models.py index a0333d46..89b3e6a9 100644 --- a/topologie/models.py +++ b/topologie/models.py @@ -427,7 +427,7 @@ class Port(AclMixin, RevMixin, models.Model): :returns: the profile of self (port)""" def profile_or_nothing(profile): port_profile = PortProfile.objects.filter( - profile_default=profile).first() + profil_default=profile).first() if port_profile: return port_profile else: From 96480ae588ae4840f48f4476a3d9a29f2c3631e0 Mon Sep 17 00:00:00 2001 From: Charlie Jacomme Date: Tue, 7 Aug 2018 11:19:45 +0200 Subject: [PATCH 075/171] mx not uniques, same mx for several extensions --- .../migrations/0093_auto_20180807_1115.py | 26 +++++++++++++++++++ machines/models.py | 4 +-- 2 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 machines/migrations/0093_auto_20180807_1115.py diff --git a/machines/migrations/0093_auto_20180807_1115.py b/machines/migrations/0093_auto_20180807_1115.py new file mode 100644 index 00000000..866cb87d --- /dev/null +++ b/machines/migrations/0093_auto_20180807_1115.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-08-07 09:15 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('machines', '0092_auto_20180807_0926'), + ] + + operations = [ + migrations.AlterField( + model_name='mx', + name='name', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='machines.Domain'), + ), + migrations.AlterField( + model_name='mx', + name='priority', + field=models.PositiveIntegerField(), + ), + ] diff --git a/machines/models.py b/machines/models.py index b9fd9205..f109b217 100644 --- a/machines/models.py +++ b/machines/models.py @@ -740,8 +740,8 @@ class Mx(RevMixin, AclMixin, models.Model): PRETTY_NAME = "Enregistrements MX" zone = models.ForeignKey('Extension', on_delete=models.PROTECT) - priority = models.PositiveIntegerField(unique=True) - name = models.OneToOneField('Domain', on_delete=models.PROTECT) + priority = models.PositiveIntegerField() + name = models.ForeignKey('Domain', on_delete=models.PROTECT) class Meta: permissions = ( From 1c8fb84f3ce413d54afb3f5a7065f357327958cd Mon Sep 17 00:00:00 2001 From: Charlie Jacomme Date: Tue, 7 Aug 2018 19:52:15 +0200 Subject: [PATCH 076/171] add info for mail aliases export --- api/serializers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/serializers.py b/api/serializers.py index 1da76e6c..09f06fb9 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -621,7 +621,7 @@ class WhitelistSerializer(NamespacedHMSerializer): class EMailAddressSerializer(NamespacedHMSerializer): """Serialize `users.models.EMailAddress` objects. """ - + user = serializers.CharField(source='user.pseudo', read_only=True) class Meta: model = users.EMailAddress fields = ('user', 'local_part', 'complete_email_address', 'api_url') @@ -657,7 +657,7 @@ class LocalEmailUsersSerializer(NamespacedHMSerializer): class Meta: model = users.User fields = ('local_email_enabled', 'local_email_redirect', - 'email_address') + 'email_address', 'email') #Firewall From bb12346132303efba7f6a5ef1c8503fd8f1f85c1 Mon Sep 17 00:00:00 2001 From: Charlie Jacomme Date: Wed, 8 Aug 2018 12:07:31 +0200 Subject: [PATCH 077/171] Fix cnames --- api/serializers.py | 5 ++--- machines/models.py | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/api/serializers.py b/api/serializers.py index 09f06fb9..4d406898 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -816,13 +816,12 @@ class CNAMERecordSerializer(serializers.ModelSerializer): """Serialize `machines.models.Domain` objects with the data needed to generate a CNAME DNS record. """ - alias = serializers.CharField(source='cname.name', read_only=True) + alias = serializers.CharField(source='cname', read_only=True) hostname = serializers.CharField(source='name', read_only=True) - extension = serializers.CharField(source='extension.name', read_only=True) class Meta: model = machines.Domain - fields = ('alias', 'hostname', 'extension') + fields = ('alias', 'hostname') class DNSZonesSerializer(serializers.ModelSerializer): diff --git a/machines/models.py b/machines/models.py index f109b217..c7dd3a6b 100644 --- a/machines/models.py +++ b/machines/models.py @@ -712,8 +712,7 @@ class Extension(RevMixin, AclMixin, models.Model): from re2o.utils import all_active_assigned_interfaces return (Domain.objects .filter(extension=self) - .filter(cname__isnull=False) - .filter(interface_parent__in=all_active_assigned_interfaces()) + .filter(cname__interface_parent__in=all_active_assigned_interfaces()) .prefetch_related('cname')) @staticmethod From d3033e8f485ca3c023b19cc6cd68e9f51a6dbd4f Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Thu, 9 Aug 2018 00:03:44 +0200 Subject: [PATCH 078/171] FIx la synthaxe correcte pour iptables --- machines/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/machines/models.py b/machines/models.py index c7dd3a6b..2e77f25c 100644 --- a/machines/models.py +++ b/machines/models.py @@ -1838,7 +1838,7 @@ class OuverturePort(RevMixin, AclMixin, models.Model): def __str__(self): if self.begin == self.end: return str(self.begin) - return '-'.join([str(self.begin), str(self.end)]) + return ':'.join([str(self.begin), str(self.end)]) def show_port(self): """Formatage plus joli, alias pour str""" From 50265a5c4b85f8cd9ea9eead40d6c71ecea931fb Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Thu, 9 Aug 2018 00:23:08 +0200 Subject: [PATCH 079/171] Fix bug evaluation des emailaddress + func get_mail sur user --- users/models.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/users/models.py b/users/models.py index 42310a03..73982b49 100755 --- a/users/models.py +++ b/users/models.py @@ -273,6 +273,20 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, else: raise NotImplementedError("Type inconnu") + @cached_property + def get_mail_addresses(self): + if self.local_email_enabled: + return self.emailaddress_set.all() + return None + + @cached_property + def get_mail(self): + """Return the mail address choosen by the user""" + if not OptionalUser.get_cached_value('local_email_accounts_enabled') or not self.local_email_enabled or self.local_email_redirect: + return str(self.email) + else: + return str(self.emailaddress_set.get(local_part=self.pseudo)) + @cached_property def class_name(self): """Renvoie si il s'agit d'un adhérent ou d'un club""" @@ -538,7 +552,7 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, user_ldap.sn = self.pseudo user_ldap.dialupAccess = str(self.has_access()) user_ldap.home_directory = '/home/' + self.pseudo - user_ldap.mail = self.email + user_ldap.mail = self.get_mail user_ldap.given_name = self.surname.lower() + '_'\ + self.name.lower()[:3] user_ldap.gid = LDAP['user_gid'] @@ -1684,11 +1698,11 @@ class EMailAddress(RevMixin, AclMixin, models.Model): verbose_name_plural = "Local email accounts" def __str__(self): - return self.local_part + OptionalUser.get_cached_value('local_email_domain') + return str(self.local_part) + OptionalUser.get_cached_value('local_email_domain') @cached_property def complete_email_address(self): - return self.local_part + OptionalUser.get_cached_value('local_email_domain') + return str(self.local_part) + OptionalUser.get_cached_value('local_email_domain') @staticmethod def can_create(user_request, userid, *_args, **_kwargs): From e270a738f2fec16076735361cad475a5af14d14c Mon Sep 17 00:00:00 2001 From: chirac Date: Thu, 9 Aug 2018 11:04:47 +0200 Subject: [PATCH 080/171] =?UTF-8?q?Update=20schema.ldiff,=20limit=20de=20r?= =?UTF-8?q?echerche=20=C3=A0=2050000?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- install_utils/schema.ldiff | 1 + 1 file changed, 1 insertion(+) diff --git a/install_utils/schema.ldiff b/install_utils/schema.ldiff index 4c217191..194f886a 100644 --- a/install_utils/schema.ldiff +++ b/install_utils/schema.ldiff @@ -1157,6 +1157,7 @@ olcDbIndex: dc eq olcDbIndex: entryCSN eq olcDbIndex: entryUUID eq olcDbIndex: radiusCallingStationId eq +olcSizeLimit: 50000 structuralObjectClass: olcHdbConfig entryUUID: fc8fa138-514b-1034-9c36-0faf5bc7ead5 creatorsName: cn=admin,cn=config From c6db3283ee50425ce23d6c461652f89aedb76e14 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Thu, 9 Aug 2018 11:15:40 +0200 Subject: [PATCH 081/171] Headers python2 --- re2o/contributors.py | 1 + re2o/urls.py | 1 + re2o/views.py | 1 + users/urls.py | 1 + users/views.py | 1 + 5 files changed, 5 insertions(+) diff --git a/re2o/contributors.py b/re2o/contributors.py index 951acc47..92b349a5 100644 --- a/re2o/contributors.py +++ b/re2o/contributors.py @@ -1,3 +1,4 @@ +# -*- mode: python; coding: utf-8 -*- """re2o.contributors A list of the proud contributors to Re2o """ diff --git a/re2o/urls.py b/re2o/urls.py index d1e11d52..39f51ec3 100644 --- a/re2o/urls.py +++ b/re2o/urls.py @@ -1,3 +1,4 @@ +# -*- mode: python; coding: utf-8 -*- # 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. diff --git a/re2o/views.py b/re2o/views.py index 8db2b4ea..1f9e6ab8 100644 --- a/re2o/views.py +++ b/re2o/views.py @@ -1,3 +1,4 @@ +# -*- mode: python; coding: utf-8 -*- # 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. diff --git a/users/urls.py b/users/urls.py index f5114600..5515dd29 100644 --- a/users/urls.py +++ b/users/urls.py @@ -1,3 +1,4 @@ +# -*- mode: python; coding: utf-8 -*- # 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. diff --git a/users/views.py b/users/views.py index a4f4ea83..ca7438df 100644 --- a/users/views.py +++ b/users/views.py @@ -1,3 +1,4 @@ +# -*- mode: python; coding: utf-8 -*- # 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. From cd13c5240afc9b37b87ecd819709698d863325d7 Mon Sep 17 00:00:00 2001 From: Maxime Bombar Date: Sat, 11 Aug 2018 15:24:58 +0200 Subject: [PATCH 082/171] Allows to display a French or English general message based on user's language choice. --- .../migrations/0048_auto_20180811_1515.py | 29 +++++++++++++++++++ preferences/models.py | 9 ++++-- re2o/context_processors.py | 6 +++- 3 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 preferences/migrations/0048_auto_20180811_1515.py diff --git a/preferences/migrations/0048_auto_20180811_1515.py b/preferences/migrations/0048_auto_20180811_1515.py new file mode 100644 index 00000000..9e0b7e51 --- /dev/null +++ b/preferences/migrations/0048_auto_20180811_1515.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-08-11 13:15 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('preferences', '0047_mailcontact'), + ] + + operations = [ + migrations.RemoveField( + model_name='generaloption', + name='general_message', + ), + migrations.AddField( + model_name='generaloption', + name='general_message_en', + field=models.TextField(blank=True, default='', help_text='General message displayed on the English version of the website.'), + ), + migrations.AddField( + model_name='generaloption', + name='general_message_fr', + field=models.TextField(blank=True, default='', help_text='Message général affiché sur le site (maintenance, etc)'), + ), + ] diff --git a/preferences/models.py b/preferences/models.py index 9226bd4a..ed4cd1e0 100644 --- a/preferences/models.py +++ b/preferences/models.py @@ -217,10 +217,15 @@ class GeneralOption(AclMixin, PreferencesModel): temps où les liens sont valides""" PRETTY_NAME = "Options générales" - general_message = models.TextField( + general_message_fr = models.TextField( default="", blank=True, - help_text="Message général affiché sur le site (maintenance, etc" + help_text="Message général affiché sur le site (maintenance, etc)" + ) + general_message_en = models.TextField( + default="", + blank=True, + help_text="General message displayed on the English version of the website." ) search_display_page = models.IntegerField(default=15) pagination_number = models.IntegerField(default=25) diff --git a/re2o/context_processors.py b/re2o/context_processors.py index ceb03be2..7cb965cc 100644 --- a/re2o/context_processors.py +++ b/re2o/context_processors.py @@ -28,13 +28,17 @@ import datetime from django.contrib import messages from preferences.models import GeneralOption, OptionalMachine +from django.utils.translation import get_language def context_user(request): """Fonction de context lorsqu'un user est logué (ou non), renvoie les infos sur l'user, la liste de ses droits, ses machines""" user = request.user - global_message = GeneralOption.get_cached_value('general_message') + if get_language()=='fr': + global_message = GeneralOption.get_cached_value('general_message_fr') + else: + global_message = GeneralOption.get_cached_value('general_message_en') if global_message: messages.warning(request, global_message) if user.is_authenticated(): From ed1a013c3f26a93e2b78a3a06c1c65518367d5b0 Mon Sep 17 00:00:00 2001 From: chirac Date: Sun, 12 Aug 2018 18:09:30 +0200 Subject: [PATCH 083/171] Rename instead of delete --- preferences/migrations/0048_auto_20180811_1515.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/preferences/migrations/0048_auto_20180811_1515.py b/preferences/migrations/0048_auto_20180811_1515.py index 9e0b7e51..cf55a660 100644 --- a/preferences/migrations/0048_auto_20180811_1515.py +++ b/preferences/migrations/0048_auto_20180811_1515.py @@ -12,16 +12,17 @@ class Migration(migrations.Migration): ] operations = [ - migrations.RemoveField( + migrations.RenameField( model_name='generaloption', - name='general_message', + old_name='general_message', + new_name='general_message_fr', ), migrations.AddField( model_name='generaloption', name='general_message_en', field=models.TextField(blank=True, default='', help_text='General message displayed on the English version of the website.'), ), - migrations.AddField( + migrations.AlterField( model_name='generaloption', name='general_message_fr', field=models.TextField(blank=True, default='', help_text='Message général affiché sur le site (maintenance, etc)'), From e797eb89b2797a8c0cb3554143e49d9eeff9bf20 Mon Sep 17 00:00:00 2001 From: Gabriel Le Bouder Date: Fri, 10 Aug 2018 20:38:17 +0200 Subject: [PATCH 084/171] templatetags python2 complient --- re2o/templatetags/acl.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/re2o/templatetags/acl.py b/re2o/templatetags/acl.py index fe13c5ac..19bd0fae 100644 --- a/re2o/templatetags/acl.py +++ b/re2o/templatetags/acl.py @@ -227,7 +227,9 @@ def acl_history_filter(parser, token): def acl_app_filter(parser, token): """Templatetag for acl checking on applications.""" try: - tag_name, *app_name = token.split_contents() + contents = token.split_contents() + tag_name = contents[0] + app_name = contents[1:] except ValueError: raise template.TemplateSyntaxError( "%r tag require 1 argument: an application" From df5861424e27486df6da0ce1055b09212b50b2b9 Mon Sep 17 00:00:00 2001 From: Charlie Jacomme Date: Fri, 10 Aug 2018 16:45:17 +0200 Subject: [PATCH 085/171] radius, make python compatible, and add traceback --- machines/models.py | 14 +++---- users/models.py | 94 +++++++++++++++++++++++----------------------- 2 files changed, 55 insertions(+), 53 deletions(-) diff --git a/machines/models.py b/machines/models.py index 2e77f25c..1ede490e 100644 --- a/machines/models.py +++ b/machines/models.py @@ -1005,7 +1005,7 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): @cached_property def gen_ipv6_dhcpv6(self): """Cree une ip, à assigner avec dhcpv6 sur une machine""" - prefix_v6 = self.type.ip_type.prefix_v6 + prefix_v6 = self.type.ip_type.prefix_v6.encode().decode('utf-8') if not prefix_v6: return None return IPv6Address( @@ -1331,14 +1331,14 @@ class Ipv6List(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): def check_and_replace_prefix(self, prefix=None): """Si le prefixe v6 est incorrect, on maj l'ipv6""" - prefix_v6 = prefix or self.interface.type.ip_type.prefix_v6 + prefix_v6 = prefix or self.interface.type.ip_type.prefix_v6.encode().decode('utf-8') if not prefix_v6: return - if (IPv6Address(self.ipv6).exploded[:20] != + if (IPv6Address(self.ipv6.encode().decode('utf-8')).exploded[:20] != IPv6Address(prefix_v6).exploded[:20]): self.ipv6 = IPv6Address( IPv6Address(prefix_v6).exploded[:20] + - IPv6Address(self.ipv6).exploded[20:] + IPv6Address(self.ipv6.encode().decode('utf-8')).exploded[20:] ) self.save() @@ -1347,9 +1347,9 @@ class Ipv6List(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): .filter(interface=self.interface, slaac_ip=True) .exclude(id=self.id)): raise ValidationError("Une ip slaac est déjà enregistrée") - prefix_v6 = self.interface.type.ip_type.prefix_v6 + prefix_v6 = self.interface.type.ip_type.prefix_v6.encode().decode('utf-8') if prefix_v6: - if (IPv6Address(self.ipv6).exploded[:20] != + if (IPv6Address(self.ipv6.encode().decode('utf-8')).exploded[:20] != IPv6Address(prefix_v6).exploded[:20]): raise ValidationError( "Le prefixv6 est incorrect et ne correspond pas au type " @@ -1850,7 +1850,7 @@ def machine_post_save(**kwargs): """Synchronisation ldap et régen parefeu/dhcp lors de la modification d'une machine""" user = kwargs['instance'].user - user.ldap_sync(base=False, access_refresh=False, mac_refresh=True) + #user.ldap_sync(base=False, access_refresh=False, mac_refresh=True) regen('dhcp') regen('mac_ip_list') diff --git a/users/models.py b/users/models.py index 73982b49..66bff7c6 100755 --- a/users/models.py +++ b/users/models.py @@ -48,6 +48,7 @@ from __future__ import unicode_literals import re import uuid import datetime +import sys from django.db import models from django.db.models import Q @@ -67,7 +68,7 @@ from django.contrib.auth.models import ( Group ) from django.core.validators import RegexValidator - +import traceback from reversion import revisions as reversion import ldapdb.models @@ -539,51 +540,52 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, mac_refresh : synchronise les machines de l'user group_refresh : synchronise les group de l'user Si l'instance n'existe pas, on crée le ldapuser correspondant""" - self.refresh_from_db() - try: - user_ldap = LdapUser.objects.get(uidNumber=self.uid_number) - except LdapUser.DoesNotExist: - user_ldap = LdapUser(uidNumber=self.uid_number) - base = True - access_refresh = True - mac_refresh = True - if base: - user_ldap.name = self.pseudo - user_ldap.sn = self.pseudo - user_ldap.dialupAccess = str(self.has_access()) - user_ldap.home_directory = '/home/' + self.pseudo - user_ldap.mail = self.get_mail - user_ldap.given_name = self.surname.lower() + '_'\ - + self.name.lower()[:3] - user_ldap.gid = LDAP['user_gid'] - if '{SSHA}' in self.password or '{SMD5}' in self.password: - # We remove the extra $ added at import from ldap - user_ldap.user_password = self.password[:6] + self.password[7:] - elif '{crypt}' in self.password: - # depending on the length, we need to remove or not a $ - if len(self.password)==41: - user_ldap.user_password = self.password - else: - user_ldap.user_password = self.password[:7] + self.password[8:] + if sys.version_info[0] >= 3: + self.refresh_from_db() + try: + user_ldap = LdapUser.objects.get(uidNumber=self.uid_number) + except LdapUser.DoesNotExist: + user_ldap = LdapUser(uidNumber=self.uid_number) + base = True + access_refresh = True + mac_refresh = True + if base: + user_ldap.name = self.pseudo + user_ldap.sn = self.pseudo + user_ldap.dialupAccess = str(self.has_access()) + user_ldap.home_directory = '/home/' + self.pseudo + user_ldap.mail = self.get_mail + user_ldap.given_name = self.surname.lower() + '_'\ + + self.name.lower()[:3] + user_ldap.gid = LDAP['user_gid'] + if '{SSHA}' in self.password or '{SMD5}' in self.password: + # We remove the extra $ added at import from ldap + user_ldap.user_password = self.password[:6] + self.password[7:] + elif '{crypt}' in self.password: + # depending on the length, we need to remove or not a $ + if len(self.password)==41: + user_ldap.user_password = self.password + else: + user_ldap.user_password = self.password[:7] + self.password[8:] - user_ldap.sambat_nt_password = self.pwd_ntlm.upper() - if self.get_shell: - user_ldap.login_shell = str(self.get_shell) - user_ldap.shadowexpire = self.get_shadow_expire - if access_refresh: - user_ldap.dialupAccess = str(self.has_access()) - if mac_refresh: - user_ldap.macs = [str(mac) for mac in Interface.objects.filter( - machine__user=self - ).values_list('mac_address', flat=True).distinct()] - if group_refresh: - # Need to refresh all groups because we don't know which groups - # were updated during edition of groups and the user may no longer - # be part of the updated group (case of group removal) - for group in Group.objects.all(): - if hasattr(group, 'listright'): - group.listright.ldap_sync() - user_ldap.save() + user_ldap.sambat_nt_password = self.pwd_ntlm.upper() + if self.get_shell: + user_ldap.login_shell = str(self.get_shell) + user_ldap.shadowexpire = self.get_shadow_expire + if access_refresh: + user_ldap.dialupAccess = str(self.has_access()) + if mac_refresh: + user_ldap.macs = [str(mac) for mac in Interface.objects.filter( + machine__user=self + ).values_list('mac_address', flat=True).distinct()] + if group_refresh: + # Need to refresh all groups because we don't know which groups + # were updated during edition of groups and the user may no longer + # be part of the updated group (case of group removal) + for group in Group.objects.all(): + if hasattr(group, 'listright'): + group.listright.ldap_sync() + user_ldap.save() def ldap_del(self): """ Supprime la version ldap de l'user""" @@ -679,7 +681,7 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, domain.save() self.notif_auto_newmachine(interface_cible) except Exception as error: - return False, error + return False, traceback.format_exc() return interface_cible, "Ok" def notif_auto_newmachine(self, interface): From 0409031c402e5c7d541e3abf64f4ecb1baf087fe Mon Sep 17 00:00:00 2001 From: Gabriel Le Bouder Date: Fri, 10 Aug 2018 20:22:03 +0200 Subject: [PATCH 086/171] removing spurius comment line --- machines/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/machines/models.py b/machines/models.py index 1ede490e..f201f667 100644 --- a/machines/models.py +++ b/machines/models.py @@ -1850,7 +1850,7 @@ def machine_post_save(**kwargs): """Synchronisation ldap et régen parefeu/dhcp lors de la modification d'une machine""" user = kwargs['instance'].user - #user.ldap_sync(base=False, access_refresh=False, mac_refresh=True) + user.ldap_sync(base=False, access_refresh=False, mac_refresh=True) regen('dhcp') regen('mac_ip_list') From 8aefc37eca30a4c6ba4d20f7ea64024a9f09d623 Mon Sep 17 00:00:00 2001 From: grisel-davy Date: Fri, 10 Aug 2018 10:23:54 +0200 Subject: [PATCH 087/171] change couleur des panel heading en accordeon pour une meilleur comprehension --- static/css/base.css | 9 +++++++++ users/templates/users/profil.html | 12 ++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/static/css/base.css b/static/css/base.css index 2dc17770..04bffbb7 100644 --- a/static/css/base.css +++ b/static/css/base.css @@ -129,3 +129,12 @@ td.long_text{ th.long_text{ width: 60%; } + +/* change color of panel heading on hover */ + +.panel > .profil:hover { + background-image: none; + background-color: #e6e6e6; + color: black; +} + diff --git a/users/templates/users/profil.html b/users/templates/users/profil.html index 1e1926cc..5fde20f2 100644 --- a/users/templates/users/profil.html +++ b/users/templates/users/profil.html @@ -110,7 +110,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
-
+

Informations détaillées

@@ -303,7 +303,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endif %}
-
+

Machines @@ -327,7 +327,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,

-
+

Cotisations @@ -358,7 +358,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,

-
+

Bannissements @@ -383,7 +383,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,

-
+

Accès à titre gracieux @@ -408,7 +408,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,

-
+

Email settings

From 5f4affd8ec7182a7fa2d7dbcc03fb151e97008bb Mon Sep 17 00:00:00 2001 From: grisel-davy Date: Fri, 10 Aug 2018 13:05:13 +0200 Subject: [PATCH 088/171] Vu profil d'un autre user et solde correct --- users/templates/users/profil.html | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/users/templates/users/profil.html b/users/templates/users/profil.html index 5fde20f2..d1f3ee4a 100644 --- a/users/templates/users/profil.html +++ b/users/templates/users/profil.html @@ -32,7 +32,11 @@ with this program; if not, write to the Free Software Foundation, Inc., {% block content %}
-

Welcome {{ users.name }} {{ users.surname }}

+ {% if user == users %} +

Welcome {{ users.name }} {{ users.surname }}

+ {% else %} +

Profil de {{ users.name }} {{ users.surname }}

+ {% endif %}
@@ -70,7 +74,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% trans "Cotisation type" %} {% trans "Duration (month)" %} {% trans "Concerned users" %}{% trans "Available for everyone" | tick %}{% trans "Available for everyone" %}
{{ article.type_cotisation }} {{ article.duration }} {{ article.type_user }}{{ article.available_for_everyone }}{{ article.available_for_everyone|tick }} {% can_edit article %} From bb67127c3d1aefb6cfc54f0249f970be3b67d969 Mon Sep 17 00:00:00 2001 From: Charlie Jacomme Date: Fri, 10 Aug 2018 16:03:30 +0200 Subject: [PATCH 091/171] media_url bug fix --- re2o/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/re2o/settings.py b/re2o/settings.py index b68e4997..8c5476f6 100644 --- a/re2o/settings.py +++ b/re2o/settings.py @@ -179,7 +179,7 @@ STATIC_URL = '/static/' # Directory where the media files served by the server are stored MEDIA_ROOT = os.path.join(BASE_DIR, 'media').replace('\\', '/') # The URL to access the static files -MEDIA_URL = '/media/' +MEDIA_URL = os.path.join(BASE_DIR,'/media/') # Models to use for graphs GRAPH_MODELS = { From c16f635c48893c1f09c01871f7245becc66401d7 Mon Sep 17 00:00:00 2001 From: grisel-davy Date: Fri, 10 Aug 2018 17:15:03 +0200 Subject: [PATCH 092/171] frontend sur le profil --- static/css/base.css | 5 +++++ users/templates/users/profil.html | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/static/css/base.css b/static/css/base.css index 04bffbb7..bf1775af 100644 --- a/static/css/base.css +++ b/static/css/base.css @@ -138,3 +138,8 @@ th.long_text{ color: black; } +/* add padding under title in profile */ +.title-dashboard{ + padding-bottom: 30px; +} + diff --git a/users/templates/users/profil.html b/users/templates/users/profil.html index d1f3ee4a..0e286aa7 100644 --- a/users/templates/users/profil.html +++ b/users/templates/users/profil.html @@ -31,7 +31,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% block title %}Profil{% endblock %} {% block content %} - From 2d41c8ba0b886c6e32d1959c56ed01797c0f7341 Mon Sep 17 00:00:00 2001 From: Charlie Jacomme Date: Fri, 10 Aug 2018 21:09:50 +0200 Subject: [PATCH 093/171] update contributors --- re2o/contributors.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/re2o/contributors.py b/re2o/contributors.py index 92b349a5..ac663d9c 100644 --- a/re2o/contributors.py +++ b/re2o/contributors.py @@ -27,5 +27,7 @@ CONTRIBUTORS = [ 'Hugo "Shaka" Hervieux', '"Mikachu"', 'Thomas "Nymous" Gaudin', - '"Esum"' + 'Benjamin "Esum" Graillot', + 'Gabriel "Boudy" Le Bouder', + 'Charlie "Le membre" Jacomme', ] From 018dae90fa77b22bfc5c2f9babbc0f5b9c0a681c Mon Sep 17 00:00:00 2001 From: Charlie Jacomme Date: Fri, 10 Aug 2018 21:15:16 +0200 Subject: [PATCH 094/171] update of footer and contributor text --- re2o/management/commands/gen_contrib.py | 2 +- re2o/templates/re2o/about.html | 4 +--- templates/base.html | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/re2o/management/commands/gen_contrib.py b/re2o/management/commands/gen_contrib.py index 6003b30f..9951a383 100644 --- a/re2o/management/commands/gen_contrib.py +++ b/re2o/management/commands/gen_contrib.py @@ -41,7 +41,7 @@ class Command(BaseCommand): self.stdout.write(self.style.SUCCESS("Exportation Sucessfull")) with open("re2o/contributors.py", "w") as contrib_file: contrib_file.write("\"\"\"re2o.contributors\n") - contrib_file.write("A list of the proud contributors to Re2o\n") + contrib_file.write("A list of the contributors to Re2o\n") contrib_file.write("\"\"\"\n") contrib_file.write("\n") contrib_file.write("CONTRIBUTORS = " + str(contributeurs)) diff --git a/re2o/templates/re2o/about.html b/re2o/templates/re2o/about.html index 8e88f5bb..d960067a 100644 --- a/re2o/templates/re2o/about.html +++ b/re2o/templates/re2o/about.html @@ -42,9 +42,7 @@ with this program; if not, write to the Free Software Foundation, Inc., so it can be setup in "a few steps". This tool is entirely free and available under a GNU Public License v2 (GPLv2) license on FedeRez gitlab.
- Re2o's mainteners are proud volunteers mainly from French engineering - schools (but not limited to) who have given a lot of their time to make - this project possible. So please be kind with them.
+ Re2o's mainteners are volunteers mainly from French schools.
If you want to get involved in the development process, we will be glad to welcome you so do not hesitate to contact us and come help us build the future of Re2o. diff --git a/templates/base.html b/templates/base.html index 8a21a612..49fb2da1 100644 --- a/templates/base.html +++ b/templates/base.html @@ -252,7 +252,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {# Read the documentation for more information #} From b24ef60d6ab1a9a3618263d9d05145ffd17bbc30 Mon Sep 17 00:00:00 2001 From: Charlie Jacomme Date: Sat, 11 Aug 2018 00:13:48 +0200 Subject: [PATCH 095/171] Preferences renamed in administration --- templates/base.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/base.html b/templates/base.html index 49fb2da1..baa6f46d 100644 --- a/templates/base.html +++ b/templates/base.html @@ -120,7 +120,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% can_view_app preferences %}
  • - {% trans "Preferences" %} + {% trans "Administration" %}
  • {% acl_end %} From 7938c64a56961c871d0f2b0a39fab671df74d863 Mon Sep 17 00:00:00 2001 From: Charlie Jacomme Date: Sat, 11 Aug 2018 00:34:32 +0200 Subject: [PATCH 096/171] bug fix acl -> this system is not understanble, and is buggy --- re2o/templatetags/acl.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/re2o/templatetags/acl.py b/re2o/templatetags/acl.py index 19bd0fae..6fd00e19 100644 --- a/re2o/templatetags/acl.py +++ b/re2o/templatetags/acl.py @@ -146,7 +146,7 @@ def get_callback(tag_name, obj=None): if tag_name == 'can_view_app': return acl_fct( lambda x: ( - not any(not sys.modules[o].can_view(x) for o in obj), + not any(not sys.modules[o].can_view(x)[0] for o in obj), None ), False @@ -154,7 +154,7 @@ def get_callback(tag_name, obj=None): if tag_name == 'cannot_view_app': return acl_fct( lambda x: ( - not any(not sys.modules[o].can_view(x) for o in obj), + not any(not sys.modules[o].can_view(x)[0] for o in obj), None ), True @@ -171,12 +171,12 @@ def get_callback(tag_name, obj=None): ) if tag_name == 'can_view_any_app': return acl_fct( - lambda x: (any(sys.modules[o].can_view(x) for o in obj), None), + lambda x: (any(sys.modules[o].can_view(x)[0] for o in obj), None), False ) if tag_name == 'cannot_view_any_app': return acl_fct( - lambda x: (any(sys.modules[o].can_view(x) for o in obj), None), + lambda x: (any(sys.modules[o].can_view(x)[0] for o in obj), None), True ) From c7a1d38b5376c1a340d23bc97550cea88f662f7f Mon Sep 17 00:00:00 2001 From: grisel-davy Date: Sat, 11 Aug 2018 01:21:57 +0200 Subject: [PATCH 097/171] on ne voit la barre de recherche qu'avec les droits suffisants --- templates/base.html | 46 +++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/templates/base.html b/templates/base.html index baa6f46d..e00cc337 100644 --- a/templates/base.html +++ b/templates/base.html @@ -132,30 +132,32 @@ with this program; if not, write to the Free Software Foundation, Inc., {% if not request.user.is_authenticated %} - {% if var_sa %} -
  • - - {% trans "Sign in" %} - -
  • - {% endif %} -
  • - - {% trans "Log in" %} - -
  • + {% if var_sa %} +
  • + + {% trans "Sign in" %} + +
  • + {% endif %} +
  • + + {% trans "Log in" %} + +
  • {% else %} -
  • -
  • + + + {% acl_end %} {% endif %}
  • {% include 'buttons/setlang.html' %} From ec0b47ac05094ec6adbf13b600633383ba50bd1e Mon Sep 17 00:00:00 2001 From: grisel-davy Date: Sat, 11 Aug 2018 01:37:08 +0200 Subject: [PATCH 098/171] =?UTF-8?q?r=C3=A9organisation=20des=20onglets=20d?= =?UTF-8?q?asn=20la=20navbar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- templates/base.html | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/templates/base.html b/templates/base.html index e00cc337..698195f7 100644 --- a/templates/base.html +++ b/templates/base.html @@ -76,15 +76,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
  • Temps minimum avant nouvelle régénération Temps avant nouvelle génération obligatoire (max) Serveurs inclusDemander la regeneration
    {{ service.service_type }} {{ service.min_time_regen }} {{ service.regular_time_regen }}{% for serv in service.servers.all %}{{ serv }}, {% endfor %}{% for serv in service.servers.all %}{{ serv }}, {% endfor %} {% can_edit service %} {% include 'buttons/edit.html' with href='machines:edit-service' id=service.id %} diff --git a/machines/urls.py b/machines/urls.py index 8c670308..d6f3a541 100644 --- a/machines/urls.py +++ b/machines/urls.py @@ -124,6 +124,7 @@ urlpatterns = [ views.edit_service, name='edit-service'), url(r'^del_service/$', views.del_service, name='del-service'), + url(r'^regen_service/(?P[0-9]+)$', views.regen_service, name='regen-service'), url(r'^index_service/$', views.index_service, name='index-service'), url(r'^add_role/$', views.add_role, name='add-role'), url(r'^edit_role/(?P[0-9]+)$', diff --git a/machines/views.py b/machines/views.py index 3509be43..f7d138be 100644 --- a/machines/views.py +++ b/machines/views.py @@ -33,7 +33,7 @@ The views for the Machines app from __future__ import unicode_literals from django.urls import reverse -from django.http import HttpResponse +from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import render, redirect from django.contrib import messages from django.contrib.auth.decorators import login_required, permission_required @@ -128,6 +128,7 @@ from .models import ( Role, Service, Service_link, + regen, Vlan, Nas, Txt, @@ -1261,6 +1262,15 @@ def del_service(request, instances): request ) +@login_required +@can_edit(Service) +def regen_service(request,service, **_kwargs): + """Ask for a regen of the service""" + + regen(service) + return index_service(request) + + @login_required @can_create(Vlan) From f123f13e10989c0cce04a4718f554d85a6b171e8 Mon Sep 17 00:00:00 2001 From: Fardale Date: Mon, 13 Aug 2018 00:31:31 +0200 Subject: [PATCH 107/171] =?UTF-8?q?typo=20Editer=20->=20=C3=89diter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- users/templates/users/profil.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/users/templates/users/profil.html b/users/templates/users/profil.html index d8138686..d5b4ad4b 100644 --- a/users/templates/users/profil.html +++ b/users/templates/users/profil.html @@ -123,7 +123,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
    - Editer + Éditer From 787ceab520e90242feaea0e86fee09c7d27030b0 Mon Sep 17 00:00:00 2001 From: Benjamin Graillot Date: Sat, 11 Aug 2018 03:58:03 +0200 Subject: [PATCH 108/171] [Adherent] Ajout gpg_fingerprint --- users/forms.py | 8 +++++++ users/migrations/0074_auto_20180810_2104.py | 22 +++++++++++++++++ users/migrations/0075_auto_20180811_0420.py | 26 +++++++++++++++++++++ users/models.py | 10 ++++++++ 4 files changed, 66 insertions(+) create mode 100644 users/migrations/0074_auto_20180810_2104.py create mode 100644 users/migrations/0075_auto_20180811_0420.py diff --git a/users/forms.py b/users/forms.py index b5859539..49135266 100644 --- a/users/forms.py +++ b/users/forms.py @@ -301,6 +301,7 @@ class AdherentForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): super(AdherentForm, self).__init__(*args, prefix=prefix, **kwargs) self.fields['name'].label = 'Prénom' self.fields['surname'].label = 'Nom' + self.fields['email'].label = 'Adresse mail' self.fields['school'].label = 'Établissement' self.fields['comment'].label = 'Commentaire' self.fields['room'].label = 'Chambre' @@ -319,6 +320,7 @@ class AdherentForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): 'room', 'shell', 'telephone', + 'gpg_fingerprint' ] def clean_telephone(self): @@ -331,6 +333,12 @@ class AdherentForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): ) return telephone + def clean_gpg_fingerprint(self): + """Format the GPG fingerprint""" + gpg_fingerprint = self.cleaned_data.get('gpg_fingerprint', None) + if gpg_fingerprint: + return gpg_fingerprint.replace(' ', '').upper() + force = forms.BooleanField( label="Forcer le déménagement ?", initial=False, diff --git a/users/migrations/0074_auto_20180810_2104.py b/users/migrations/0074_auto_20180810_2104.py new file mode 100644 index 00000000..5ab77369 --- /dev/null +++ b/users/migrations/0074_auto_20180810_2104.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-08-10 19:04 +from __future__ import unicode_literals + +import django.core.validators +from django.db import migrations, models +import users.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0073_auto_20180629_1614'), + ] + + operations = [ + migrations.AddField( + model_name='adherent', + name='gpg_fingerprint', + field=models.CharField(blank=True, max_length=40, null=True, validators=[django.core.validators.RegexValidator('^[0-9A-F]{40}$', message='Une fingerprint GPG doit contenit 40 caractères hexadécimaux')]), + ), + ] diff --git a/users/migrations/0075_auto_20180811_0420.py b/users/migrations/0075_auto_20180811_0420.py new file mode 100644 index 00000000..8ea2526f --- /dev/null +++ b/users/migrations/0075_auto_20180811_0420.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-08-11 02:20 +from __future__ import unicode_literals + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0074_auto_20180810_2104'), + ] + + operations = [ + migrations.AlterField( + model_name='adherent', + name='gpg_fingerprint', + field=models.CharField(blank=True, max_length=40, null=True, validators=[django.core.validators.RegexValidator('^[0-9A-F]{40}$', message='Une fingerprint GPG doit contenir 40 caractères hexadécimaux')]), + ), + migrations.AlterField( + model_name='user', + name='email', + field=models.EmailField(max_length=254, unique=True), + ), + ] diff --git a/users/models.py b/users/models.py index 66bff7c6..01de431c 100755 --- a/users/models.py +++ b/users/models.py @@ -985,6 +985,16 @@ class Adherent(User): blank=True, null=True ) + gpg_fingerprint = models.CharField( + max_length=40, + blank=True, + null=True, + validators=[RegexValidator( + '^[0-9A-F]{40}$', + message="Une fingerprint GPG doit contenir 40 " + "caractères hexadécimaux" + )] + ) @classmethod def get_instance(cls, adherentid, *_args, **_kwargs): From a5870aacce903f4c65b221e4cf0024593827fbdc Mon Sep 17 00:00:00 2001 From: Charlie Jacomme Date: Sat, 11 Aug 2018 04:22:14 +0200 Subject: [PATCH 109/171] gpg fpr display --- users/templates/users/profil.html | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/users/templates/users/profil.html b/users/templates/users/profil.html index d5b4ad4b..84d23009 100644 --- a/users/templates/users/profil.html +++ b/users/templates/users/profil.html @@ -239,6 +239,12 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endif %}
    Empreinte GPG{{ users.gpg_fingerprint }}
    Shell {{ users.shell }}Empreinte GPG{{ users.gpg_fingerprint }}{{ users.adherent.gpg_fingerprint }}
    - + - - -
    Contact email address{{ users.email }}{{ users.get_mail }}
    Enable the local email account {{ users.local_email_enabled | tick }} Enable the local email redirection{{ users.local_email_redirect | tick }}
    - - {% if users.local_email_enabled %} +
    {{ users.local_email_redirect | tick }}
    +

    {% blocktrans %}The Contact email is the email address where we send email to contact you. If you would like to use your external email address for that, you can either disable your local email address or enable the local email redirection.{% endblocktrans %}

    + + {% if users.local_email_enabled %} {% can_create EMailAddress users.id %} Add an email address @@ -462,7 +464,7 @@ with this program; if not, write to the Free Software Foundation, Inc., - +
    Contact email address{{ users.email }}{{ users.get_mail }}
    diff --git a/users/templates/users/user.html b/users/templates/users/user.html index edd57ea1..5e4048a3 100644 --- a/users/templates/users/user.html +++ b/users/templates/users/user.html @@ -36,6 +36,9 @@ with this program; if not, write to the Free Software Foundation, Inc., {% massive_bootstrap_form userform 'room,school,administrators,members' %} {% bootstrap_button action_name button_type="submit" icon="star" %} +{% if load_js_file %} + +{% endif %}
    {% if showCGU %}

    En cliquant sur Créer ou modifier, l'utilisateur s'engage à respecter les règles d'utilisation du réseau.

    diff --git a/users/views.py b/users/views.py index ca7438df..5279dbb0 100644 --- a/users/views.py +++ b/users/views.py @@ -541,7 +541,8 @@ def edit_emailaddress(request, emailaddress_instance, **_kwargs): return form( {'userform': emailaddress, 'showCGU': False, - 'action_name': 'Edit a local email account'}, + 'action_name': 'Edit a local email account', + }, 'users/user.html', request ) @@ -585,6 +586,7 @@ def edit_email_settings(request, user_instance, **_kwargs): return form( {'userform': email_settings, 'showCGU': False, + 'load_js_file': '/static/js/email_address.js', 'action_name': 'Edit the email settings'}, 'users/user.html', request @@ -1017,7 +1019,7 @@ def profil(request, users, **_kwargs): 'emailaddress_list': users.email_address, 'local_email_accounts_enabled': ( OptionalUser.objects.first().local_email_accounts_enabled - ) + ) } ) From 49585cade184655150eb92326f130eaea67196e7 Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Tue, 14 Aug 2018 13:09:13 +0200 Subject: [PATCH 117/171] =?UTF-8?q?Ajoute=20la=20possibilit=C3=A9=20d'util?= =?UTF-8?q?iser=20uniquement=20le=20compte=20mail=20local.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- users/migrations/0074_auto_20180814_1059.py | 2 +- users/models.py | 41 +++++++++++++++------ 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/users/migrations/0074_auto_20180814_1059.py b/users/migrations/0074_auto_20180814_1059.py index ced792f4..e3e8527f 100644 --- a/users/migrations/0074_auto_20180814_1059.py +++ b/users/migrations/0074_auto_20180814_1059.py @@ -15,7 +15,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='user', name='email', - field=models.EmailField(blank=True, help_text='External email address allowing us to contact you.', max_length=254), + field=models.EmailField(blank=True, null=True, help_text='External email address allowing us to contact you.', max_length=254), ), migrations.AlterField( model_name='user', diff --git a/users/models.py b/users/models.py index 7331a460..0fe2cd62 100755 --- a/users/models.py +++ b/users/models.py @@ -104,6 +104,7 @@ def linux_user_validator(login): params={'label': login}, ) + def get_fresh_user_uid(): """ Renvoie le plus petit uid non pris. Fonction très paresseuse """ uids = list(range( @@ -131,6 +132,7 @@ def get_fresh_gid(): class UserManager(BaseUserManager): """User manager basique de django""" + def _create_user( self, pseudo, @@ -197,6 +199,7 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, ) email = models.EmailField( blank=True, + null=True, help_text="External email address allowing us to contact you." ) local_email_redirect = models.BooleanField( @@ -563,13 +566,15 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, user_ldap.gid = LDAP['user_gid'] if '{SSHA}' in self.password or '{SMD5}' in self.password: # We remove the extra $ added at import from ldap - user_ldap.user_password = self.password[:6] + self.password[7:] + user_ldap.user_password = self.password[:6] + \ + self.password[7:] elif '{crypt}' in self.password: # depending on the length, we need to remove or not a $ - if len(self.password)==41: + if len(self.password) == 41: user_ldap.user_password = self.password else: - user_ldap.user_password = self.password[:7] + self.password[8:] + user_ldap.user_password = self.password[:7] + \ + self.password[8:] user_ldap.sambat_nt_password = self.pwd_ntlm.upper() if self.get_shell: @@ -614,7 +619,7 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, send_mail( 'Bienvenue au %(name)s / Welcome to %(name)s' % { 'name': AssoOption.get_cached_value('name') - }, + }, '', GeneralOption.get_cached_value('email_from'), [self.email], @@ -657,8 +662,8 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, une machine inconnue sur le compte de l'user""" all_interfaces = self.user_interfaces(active=False) if all_interfaces.count() > OptionalMachine.get_cached_value( - 'max_lambdauser_interfaces' - ): + 'max_lambdauser_interfaces' + ): return False, "Maximum de machines enregistrees atteinte" if not nas_type: return False, "Re2o ne sait pas à quel machinetype affecter cette\ @@ -961,7 +966,7 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, 'force': self.can_change_force, 'selfpasswd': self.check_selfpasswd, 'local_email_redirect': self.can_change_local_email_redirect, - 'local_email_enabled' : self.can_change_local_email_enabled, + 'local_email_enabled': self.can_change_local_email_enabled, } self.__original_state = self.state @@ -969,9 +974,22 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, """Check if this pseudo is already used by any mailalias. Better than raising an error in post-save and catching it""" if (EMailAddress.objects - .filter(local_part=self.pseudo.lower()).exclude(user_id=self.id) + .filter(local_part=self.pseudo.lower()).exclude(user_id=self.id) ): raise ValidationError("This pseudo is already in use.") + if not self.local_email_enabled and not self.email: + raise ValidationError( + {'email': ( + 'There is neither a local email address nor an external' + ' email address for this user.' + ), } + ) + if self.local_email_redirect and not self.email: + raise ValidationError( + {'local_email_redirect': ( + 'You cannot redirect your local email if no external email ' + 'has been set.'), } + ) def __str__(self): return self.pseudo @@ -1109,7 +1127,8 @@ def user_post_save(**kwargs): Synchronise le ldap""" is_created = kwargs['created'] user = kwargs['instance'] - EMailAddress.objects.get_or_create(local_part=user.pseudo.lower(), user=user) + EMailAddress.objects.get_or_create( + local_part=user.pseudo.lower(), user=user) if is_created: user.notif_inscription() user.state_sync() @@ -1132,6 +1151,7 @@ def user_group_relation_changed(**kwargs): mac_refresh=False, group_refresh=True) + @receiver(post_delete, sender=Adherent) @receiver(post_delete, sender=Club) @receiver(post_delete, sender=User) @@ -1520,7 +1540,7 @@ class Request(models.Model): hours=GeneralOption.get_cached_value( 'req_expire_hrs' ) - )) + )) if not self.token: self.token = str(uuid.uuid4()).replace('-', '') # remove hyphens super(Request, self).save() @@ -1810,4 +1830,3 @@ class EMailAddress(RevMixin, AclMixin, models.Model): if "@" in self.local_part: raise ValidationError("The local part cannot contain a @") super(EMailAddress, self).clean(*args, **kwargs) - From 56e6cfffe2f40ad381be121c6e67a2598f8ca5ad Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Sun, 12 Aug 2018 01:09:48 +0200 Subject: [PATCH 118/171] Fix comnpay url --- .../0031_comnpaypayment_production.py | 20 +++++++++++++++++++ cotisations/payment_methods/comnpay/models.py | 13 +++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 cotisations/migrations/0031_comnpaypayment_production.py diff --git a/cotisations/migrations/0031_comnpaypayment_production.py b/cotisations/migrations/0031_comnpaypayment_production.py new file mode 100644 index 00000000..25ec7fb8 --- /dev/null +++ b/cotisations/migrations/0031_comnpaypayment_production.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-08-11 23:03 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cotisations', '0030_custom_payment'), + ] + + operations = [ + migrations.AddField( + model_name='comnpaypayment', + name='production', + field=models.BooleanField(default=True, verbose_name='Production mode enabled (production url, instead of homologation)'), + ), + ] diff --git a/cotisations/payment_methods/comnpay/models.py b/cotisations/payment_methods/comnpay/models.py index ff6fed0d..dbc2f4ba 100644 --- a/cotisations/payment_methods/comnpay/models.py +++ b/cotisations/payment_methods/comnpay/models.py @@ -65,6 +65,16 @@ class ComnpayPayment(PaymentMethodMixin, models.Model): decimal_places=2, default=1, ) + production = models.BooleanField( + default=True, + verbose_name=_l("Production mode enabled (production url, instead of homologation)"), + ) + + def return_url_comnpay(self): + if self.production: + return 'https://secure.comnpay.com' + else: + return 'https://secure.homologation.comnpay.com' def end_payment(self, invoice, request): """ @@ -87,8 +97,9 @@ class ComnpayPayment(PaymentMethodMixin, models.Model): "", "D" ) + r = { - 'action': 'https://secure.homologation.comnpay.com', + 'action': self.return_url_comnpay(), 'method': 'POST', 'content': p.buildSecretHTML( _("Pay invoice no : ")+str(invoice.id), From 9b58fc1829b075d67f30ed27806ac48c5dc05b59 Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Wed, 15 Aug 2018 22:02:39 +0200 Subject: [PATCH 119/171] fix migrations --- users/migrations/0075_merge_20180815_2202.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 users/migrations/0075_merge_20180815_2202.py diff --git a/users/migrations/0075_merge_20180815_2202.py b/users/migrations/0075_merge_20180815_2202.py new file mode 100644 index 00000000..c24ebf4e --- /dev/null +++ b/users/migrations/0075_merge_20180815_2202.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-08-15 20:02 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0074_auto_20180814_1059'), + ('users', '0074_auto_20180810_2104'), + ] + + operations = [ + ] From ba31a94c20bdfd11e92d90befec05b14fdd275cd Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Mon, 13 Aug 2018 19:36:57 +0200 Subject: [PATCH 120/171] users can change their shell --- .../0049_optionaluser_self_change_shell.py | 20 +++++++++++++++++++ preferences/models.py | 4 ++++ .../preferences/display_preferences.html | 9 +++++++-- users/models.py | 12 +++++------ 4 files changed, 37 insertions(+), 8 deletions(-) create mode 100644 preferences/migrations/0049_optionaluser_self_change_shell.py diff --git a/preferences/migrations/0049_optionaluser_self_change_shell.py b/preferences/migrations/0049_optionaluser_self_change_shell.py new file mode 100644 index 00000000..161792eb --- /dev/null +++ b/preferences/migrations/0049_optionaluser_self_change_shell.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-08-13 17:18 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('preferences', '0048_auto_20180811_1515'), + ] + + operations = [ + migrations.AddField( + model_name='optionaluser', + name='self_change_shell', + field=models.BooleanField(default=False, help_text='Users can change their shell'), + ), + ] diff --git a/preferences/models.py b/preferences/models.py index ed4cd1e0..0ebb2fec 100644 --- a/preferences/models.py +++ b/preferences/models.py @@ -85,6 +85,10 @@ class OptionalUser(AclMixin, PreferencesModel): blank=True, null=True ) + self_change_shell = models.BooleanField( + default=False, + help_text="Users can change their shell" + ) local_email_accounts_enabled = models.BooleanField( default=False, help_text="Enable local email accounts for users" diff --git a/preferences/templates/preferences/display_preferences.html b/preferences/templates/preferences/display_preferences.html index 2e34db5a..a7ada02b 100644 --- a/preferences/templates/preferences/display_preferences.html +++ b/preferences/templates/preferences/display_preferences.html @@ -45,10 +45,11 @@ with this program; if not, write to the Free Software Foundation, Inc., {{ useroptions.self_adhesion|tick }} - Champ gpg fingerprint - {{ useroptions.gpg_fingerprint|tick }} + Shell par défaut des utilisateurs {{ useroptions.shell_default }} + Les utilisateurs peuvent changer leur shell + {{ useroptions.self_change_shell|tick }} Creations d'adhérents par tous @@ -56,6 +57,10 @@ with this program; if not, write to the Free Software Foundation, Inc., Creations de clubs par tous {{ useroptions.all_can_create_club|tick }} + + Champ gpg fingerprint + {{ useroptions.gpg_fingerprint|tick }} +
    Comptes mails
    diff --git a/users/models.py b/users/models.py index 0fe2cd62..12f3cf1c 100755 --- a/users/models.py +++ b/users/models.py @@ -832,18 +832,18 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, "Droit requis pour changer l'état" ) - @staticmethod - def can_change_shell(user_request, *_args, **_kwargs): + def can_change_shell(self, user_request, *_args, **_kwargs): """ Check if a user can change a shell :param user_request: The user who request :returns: a message and a boolean which is True if the user has the right to change a shell """ - return ( - user_request.has_perm('users.change_user_shell'), - "Droit requis pour changer le shell" - ) + if not ((self == user_request and OptionalUser.get_cached_value('self_change_shell')) + or user_request.has_perm('users.change_user_shell')): + return False, u"Droit requis pour changer le shell" + else: + return True, None @staticmethod def can_change_local_email_redirect(user_request, *_args, **_kwargs): From 1e47fa16a05e1a2b7af43b1490ff3b787555e6cf Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Wed, 15 Aug 2018 23:14:30 +0200 Subject: [PATCH 121/171] Rend can_change_shell de User cast-proof --- users/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/users/models.py b/users/models.py index 12f3cf1c..bceb84bb 100755 --- a/users/models.py +++ b/users/models.py @@ -839,7 +839,7 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, :returns: a message and a boolean which is True if the user has the right to change a shell """ - if not ((self == user_request and OptionalUser.get_cached_value('self_change_shell')) + if not ((self.pk == user_request.pk and OptionalUser.get_cached_value('self_change_shell')) or user_request.has_perm('users.change_user_shell')): return False, u"Droit requis pour changer le shell" else: From 904c7b279baea4bcf418af15c3c14033a7795ed3 Mon Sep 17 00:00:00 2001 From: Delphine SALVY Date: Wed, 18 Jul 2018 02:08:03 +0200 Subject: [PATCH 122/171] POC de l'envoi de facture par mail. --- .../templates/cotisations/email_invoice | 8 ++++ cotisations/tex.py | 17 +++++-- cotisations/views.py | 47 ++++++++++++++++++- 3 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 cotisations/templates/cotisations/email_invoice diff --git a/cotisations/templates/cotisations/email_invoice b/cotisations/templates/cotisations/email_invoice new file mode 100644 index 00000000..71459733 --- /dev/null +++ b/cotisations/templates/cotisations/email_invoice @@ -0,0 +1,8 @@ +Dear {{name}}, + +Thank you for your purchase. Here is your invoice. + +Should you need extra information, you can email us at {{contact_mail}}. + +Best regards, + {{ asso_name }}'s team diff --git a/cotisations/tex.py b/cotisations/tex.py index f456fe8a..4487d5b8 100644 --- a/cotisations/tex.py +++ b/cotisations/tex.py @@ -61,11 +61,9 @@ def render_invoice(_request, ctx={}): return r -def render_tex(_request, template, ctx={}): +def create_pdf(template, ctx={}): """ - Creates a PDF from a LaTex templates using pdflatex. - Writes it in a temporary directory and send back an HTTP response for - accessing this file. + Creates and returns a PDF from a LaTeX template using pdflatex. """ context = Context(ctx) template = get_template(template) @@ -81,6 +79,17 @@ def render_tex(_request, template, ctx={}): process.communicate(rendered_tpl) with open(os.path.join(tempdir, 'texput.pdf'), 'rb') as f: pdf = f.read() + + return pdf + + +def render_tex(_request, template, ctx={}): + """ + Creates a PDF from a LaTex templates using pdflatex. + Writes it in a temporary directory and send back an HTTP response for + accessing this file. + """ + pdf = create_pdf(template, ctx={}) r = HttpResponse(content_type='application/pdf') r.write(pdf) return r diff --git a/cotisations/views.py b/cotisations/views.py index 8b9fe79e..5ac55324 100644 --- a/cotisations/views.py +++ b/cotisations/views.py @@ -40,6 +40,8 @@ from django.db.models import Q from django.forms import modelformset_factory, formset_factory from django.utils import timezone from django.utils.translation import ugettext as _ +from django.core.mail import EmailMessage +from django.template.loader import get_template # Import des models, forms et fonctions re2o from reversion import revisions as reversion @@ -72,7 +74,7 @@ from .forms import ( SelectClubArticleForm, RechargeForm ) -from .tex import render_invoice +from .tex import create_pdf, render_invoice from .payment_methods.forms import payment_method_factory from .utils import find_payment_method @@ -147,6 +149,48 @@ def new_facture(request, user, userid): p.facture = new_invoice_instance p.save() + facture = new_invoice_instance # BErk + purchases_info = [] + for purchase in facture.vente_set.all(): + purchases_info.append({ + 'name': purchase.name, + 'price': purchase.prix, + 'quantity': purchase.number, + 'total_price': purchase.prix_total + }) + ctx = { + 'paid': True, + 'fid': facture.id, + 'DATE': facture.date, + 'recipient_name': "{} {}".format( + facture.user.name, + facture.user.surname + ), + 'address': facture.user.room, + 'article': purchases_info, + 'total': facture.prix_total(), + 'asso_name': AssoOption.get_cached_value('name'), + 'line1': AssoOption.get_cached_value('adresse1'), + 'line2': AssoOption.get_cached_value('adresse2'), + 'siret': AssoOption.get_cached_value('siret'), + 'email': AssoOption.get_cached_value('contact'), + 'phone': AssoOption.get_cached_value('telephone'), + 'tpl_path': os.path.join(settings.BASE_DIR, LOGO_PATH) + } + + pdf = create_pdf('cotisations/factures.tex', ctx) + + template = get_template('cotisations/email_invoice') + + mail = EmailMessage( + _('Your invoice'), + template.render(ctx), + GeneralOption.get_cached_value('email_from'), + [new_invoice_instance.user.email], + attachments = [('invoice.pdf', pdf, 'application/pdf')] + ) + mail.send() + return new_invoice_instance.paiement.end_payment( new_invoice_instance, request @@ -161,6 +205,7 @@ def new_facture(request, user, userid): balance = user.solde else: balance = None + return form( { 'factureform': invoice_form, From 9dd54a99a5b9fba1970d1b69008763d7062a7468 Mon Sep 17 00:00:00 2001 From: Delphine SALVY Date: Mon, 23 Jul 2018 00:13:25 +0200 Subject: [PATCH 123/171] Fix 141 : envoi des factures par mail --- .../templates/cotisations/email_invoice | 14 +++++ cotisations/utils.py | 63 +++++++++++++++++++ cotisations/views.py | 51 ++------------- 3 files changed, 83 insertions(+), 45 deletions(-) diff --git a/cotisations/templates/cotisations/email_invoice b/cotisations/templates/cotisations/email_invoice index 71459733..8d6b2cc2 100644 --- a/cotisations/templates/cotisations/email_invoice +++ b/cotisations/templates/cotisations/email_invoice @@ -1,3 +1,17 @@ +=== English version below === + +Bonjour {{name}}, + +Nous vous remercions pour votre achat auprès de {{asso_name}} et nous vous en joignons la facture. + +En cas de question, n’hésitez pas à nous contacter par mail à {{contact_mail}}. + +Cordialement, +L’équipe de {{asso_name}} + + +=== English version === + Dear {{name}}, Thank you for your purchase. Here is your invoice. diff --git a/cotisations/utils.py b/cotisations/utils.py index f36b376f..0211dd40 100644 --- a/cotisations/utils.py +++ b/cotisations/utils.py @@ -19,6 +19,16 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +import os + +from django.template.loader import get_template +from django.core.mail import EmailMessage + +from .tex import create_pdf +from preferences.models import AssoOption, GeneralOption +from re2o.settings import LOGO_PATH +from re2o import settings + def find_payment_method(payment): """Finds the payment method associated to the payment if it exists.""" @@ -30,3 +40,56 @@ def find_payment_method(payment): except method.PaymentMethod.DoesNotExist: pass return None + + +def send_mail_invoice(invoice): + """Creates the pdf of the invoice and sends it by email to the client""" + purchases_info = [] + for purchase in invoice.vente_set.all(): + purchases_info.append({ + 'name': purchase.name, + 'price': purchase.prix, + 'quantity': purchase.number, + 'total_price': purchase.prix_total + }) + + ctx = { + 'paid': True, + 'fid': invoice.id, + 'DATE': invoice.date, + 'recipient_name': "{} {}".format( + invoice.user.name, + invoice.user.surname + ), + 'address': invoice.user.room, + 'article': purchases_info, + 'total': invoice.prix_total(), + 'asso_name': AssoOption.get_cached_value('name'), + 'line1': AssoOption.get_cached_value('adresse1'), + 'line2': AssoOption.get_cached_value('adresse2'), + 'siret': AssoOption.get_cached_value('siret'), + 'email': AssoOption.get_cached_value('contact'), + 'phone': AssoOption.get_cached_value('telephone'), + 'tpl_path': os.path.join(settings.BASE_DIR, LOGO_PATH) + } + + pdf = create_pdf('cotisations/factures.tex', ctx) + template = get_template('cotisations/email_invoice') + + ctx = { + 'name': "{} {}".format( + invoice.user.name, + invoice.user.surname + ), + 'contact_mail': AssoOption.get_cached_value('contact'), + 'asso_name': AssoOption.get_cached_value('name') + } + + mail = EmailMessage( + 'Votre facture / Your invoice', + template.render(ctx), + GeneralOption.get_cached_value('email_from'), + [invoice.user.email], + attachments=[('invoice.pdf', pdf, 'application/pdf')] + ) + mail.send() diff --git a/cotisations/views.py b/cotisations/views.py index 5ac55324..66eb66f5 100644 --- a/cotisations/views.py +++ b/cotisations/views.py @@ -40,8 +40,6 @@ from django.db.models import Q from django.forms import modelformset_factory, formset_factory from django.utils import timezone from django.utils.translation import ugettext as _ -from django.core.mail import EmailMessage -from django.template.loader import get_template # Import des models, forms et fonctions re2o from reversion import revisions as reversion @@ -74,9 +72,9 @@ from .forms import ( SelectClubArticleForm, RechargeForm ) -from .tex import create_pdf, render_invoice +from .tex import render_invoice from .payment_methods.forms import payment_method_factory -from .utils import find_payment_method +from .utils import find_payment_method, send_mail_invoice @login_required @@ -149,47 +147,7 @@ def new_facture(request, user, userid): p.facture = new_invoice_instance p.save() - facture = new_invoice_instance # BErk - purchases_info = [] - for purchase in facture.vente_set.all(): - purchases_info.append({ - 'name': purchase.name, - 'price': purchase.prix, - 'quantity': purchase.number, - 'total_price': purchase.prix_total - }) - ctx = { - 'paid': True, - 'fid': facture.id, - 'DATE': facture.date, - 'recipient_name': "{} {}".format( - facture.user.name, - facture.user.surname - ), - 'address': facture.user.room, - 'article': purchases_info, - 'total': facture.prix_total(), - 'asso_name': AssoOption.get_cached_value('name'), - 'line1': AssoOption.get_cached_value('adresse1'), - 'line2': AssoOption.get_cached_value('adresse2'), - 'siret': AssoOption.get_cached_value('siret'), - 'email': AssoOption.get_cached_value('contact'), - 'phone': AssoOption.get_cached_value('telephone'), - 'tpl_path': os.path.join(settings.BASE_DIR, LOGO_PATH) - } - - pdf = create_pdf('cotisations/factures.tex', ctx) - - template = get_template('cotisations/email_invoice') - - mail = EmailMessage( - _('Your invoice'), - template.render(ctx), - GeneralOption.get_cached_value('email_from'), - [new_invoice_instance.user.email], - attachments = [('invoice.pdf', pdf, 'application/pdf')] - ) - mail.send() + send_mail_invoice(new_invoice_instance) return new_invoice_instance.paiement.end_payment( new_invoice_instance, @@ -791,6 +749,9 @@ def credit_solde(request, user, **_kwargs): prix=refill_form.cleaned_data['value'], number=1 ) + + send_mail_invoice(invoice) + return invoice.paiement.end_payment(invoice, request) p = get_object_or_404(Paiement, is_balance=True) return form({ From 4ab0dad146161afbe870a92d13cddc66364fbbf7 Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Wed, 15 Aug 2018 23:56:34 +0200 Subject: [PATCH 124/171] Documentation propre de render_tex et create_pdf --- cotisations/tex.py | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/cotisations/tex.py b/cotisations/tex.py index 4487d5b8..f3f8601b 100644 --- a/cotisations/tex.py +++ b/cotisations/tex.py @@ -62,15 +62,23 @@ def render_invoice(_request, ctx={}): def create_pdf(template, ctx={}): - """ - Creates and returns a PDF from a LaTeX template using pdflatex. + """Creates and returns a PDF from a LaTeX template using pdflatex. + + It create a temporary file for the PDF then read it to return its content. + + Args: + template: Path to the LaTeX template. + ctx: Dict with the context for rendering the template. + + Returns: + The content of the temporary PDF file generated. """ context = Context(ctx) template = get_template(template) rendered_tpl = template.render(context).encode('utf-8') with tempfile.TemporaryDirectory() as tempdir: - for i in range(2): + for _ in range(2): process = Popen( ['pdflatex', '-output-directory', tempdir], stdin=PIPE, @@ -84,10 +92,18 @@ def create_pdf(template, ctx={}): def render_tex(_request, template, ctx={}): - """ - Creates a PDF from a LaTex templates using pdflatex. - Writes it in a temporary directory and send back an HTTP response for + """Creates a PDF from a LaTex templates using pdflatex. + + Calls `create_pdf` and send back an HTTP response for accessing this file. + + Args: + _request: Unused, but allow using this function as a Django view. + template: Path to the LaTeX template. + ctx: Dict with the context for rendering the template. + + Returns: + An HttpResponse with type `application/pdf` containing the PDF file. """ pdf = create_pdf(template, ctx={}) r = HttpResponse(content_type='application/pdf') From d6091d117ce2db8faa4eac3abf6228f90bfacdb3 Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Sun, 22 Jul 2018 00:16:05 +0200 Subject: [PATCH 125/171] Custom invoices. --- cotisations/forms.py | 23 +- cotisations/migrations/0031_custom_invoice.py | 72 +++++++ cotisations/models.py | 187 ++++++++++------- .../cotisations/aff_custom_invoice.html | 108 ++++++++++ .../cotisations/index_custom_invoice.html | 36 ++++ .../templates/cotisations/sidebar.html | 7 +- cotisations/urls.py | 26 ++- cotisations/views.py | 196 ++++++++++++++---- re2o/utils.py | 8 + templates/buttons/add.html | 2 +- 10 files changed, 526 insertions(+), 139 deletions(-) create mode 100644 cotisations/migrations/0031_custom_invoice.py create mode 100644 cotisations/templates/cotisations/aff_custom_invoice.html create mode 100644 cotisations/templates/cotisations/index_custom_invoice.html diff --git a/cotisations/forms.py b/cotisations/forms.py index 7ad9e413..ccf9d5d6 100644 --- a/cotisations/forms.py +++ b/cotisations/forms.py @@ -46,7 +46,7 @@ from django.shortcuts import get_object_or_404 from re2o.field_permissions import FieldPermissionFormMixin from re2o.mixins import FormRevMixin -from .models import Article, Paiement, Facture, Banque +from .models import Article, Paiement, Facture, Banque, CustomInvoice from .payment_methods import balance @@ -131,24 +131,13 @@ class SelectClubArticleForm(Form): self.fields['article'].queryset = Article.find_allowed_articles(user) -# TODO : change Facture to Invoice -class NewFactureFormPdf(Form): +class CustomInvoiceForm(FormRevMixin, ModelForm): """ - Form used to create a custom PDF invoice. + Form used to create a custom invoice. """ - paid = forms.BooleanField(label=_l("Paid"), required=False) - # TODO : change dest field to recipient - dest = forms.CharField( - required=True, - max_length=255, - label=_l("Recipient") - ) - # TODO : change chambre field to address - chambre = forms.CharField( - required=False, - max_length=10, - label=_l("Address") - ) + class Meta: + model = CustomInvoice + fields = '__all__' class ArticleForm(FormRevMixin, ModelForm): diff --git a/cotisations/migrations/0031_custom_invoice.py b/cotisations/migrations/0031_custom_invoice.py new file mode 100644 index 00000000..52921739 --- /dev/null +++ b/cotisations/migrations/0031_custom_invoice.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-07-21 20:01 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +import re2o.field_permissions +import re2o.mixins + + +def reattribute_ids(apps, schema_editor): + Facture = apps.get_model('cotisations', 'Facture') + BaseInvoice = apps.get_model('cotisations', 'BaseInvoice') + + for f in Facture.objects.all(): + base = BaseInvoice.objects.create(id=f.pk, date=f.date) + f.baseinvoice_ptr = base + f.save() + +class Migration(migrations.Migration): + + dependencies = [ + ('cotisations', '0030_custom_payment'), + ] + + operations = [ + migrations.CreateModel( + name='BaseInvoice', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date', models.DateTimeField(auto_now_add=True, verbose_name='Date')), + ], + bases=(re2o.mixins.RevMixin, re2o.mixins.AclMixin, re2o.field_permissions.FieldPermissionModelMixin, models.Model), + ), + migrations.CreateModel( + name='CustomInvoice', + fields=[ + ('baseinvoice_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='cotisations.BaseInvoice')), + ('recipient', models.CharField(max_length=255, verbose_name='Recipient')), + ('payment', models.CharField(max_length=255, verbose_name='Payment type')), + ('address', models.CharField(max_length=255, verbose_name='Address')), + ('paid', models.BooleanField(verbose_name='Paid')), + ], + bases=('cotisations.baseinvoice',), + options={'permissions': (('view_custom_invoice', 'Can view a custom invoice'),)}, + ), + migrations.AddField( + model_name='facture', + name='baseinvoice_ptr', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='cotisations.BaseInvoice', null=True), + preserve_default=False, + ), + migrations.RunPython(reattribute_ids), + migrations.AlterField( + model_name='vente', + name='facture', + field=models.ForeignKey(on_delete=models.CASCADE, verbose_name='Invoice', to='cotisations.BaseInvoice') + ), + migrations.RemoveField( + model_name='facture', + name='id', + ), + migrations.RemoveField( + model_name='facture', + name='date', + ), + migrations.AlterField( + model_name='facture', + name='baseinvoice_ptr', + field=models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='cotisations.BaseInvoice'), + ) + ] diff --git a/cotisations/models.py b/cotisations/models.py index 52d71b58..c4515cc7 100644 --- a/cotisations/models.py +++ b/cotisations/models.py @@ -55,80 +55,11 @@ from cotisations.utils import find_payment_method from cotisations.validators import check_no_balance -# TODO : change facture to invoice -class Facture(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): - """ - The model for an invoice. It reprensents the fact that a user paid for - something (it can be multiple article paid at once). - - An invoice is linked to : - * one or more purchases (one for each article sold that time) - * a user (the one who bought those articles) - * a payment method (the one used by the user) - * (if applicable) a bank - * (if applicable) a cheque number. - Every invoice is dated throught the 'date' value. - An invoice has a 'controlled' value (default : False) which means that - someone with high enough rights has controlled that invoice and taken it - into account. It also has a 'valid' value (default : True) which means - that someone with high enough rights has decided that this invoice was not - valid (thus it's like the user never paid for his articles). It may be - necessary in case of non-payment. - """ - - user = models.ForeignKey('users.User', on_delete=models.PROTECT) - # TODO : change paiement to payment - paiement = models.ForeignKey('Paiement', on_delete=models.PROTECT) - # TODO : change banque to bank - banque = models.ForeignKey( - 'Banque', - on_delete=models.PROTECT, - blank=True, - null=True - ) - # TODO : maybe change to cheque nummber because not evident - cheque = models.CharField( - max_length=255, - blank=True, - verbose_name=_l("Cheque number") - ) +class BaseInvoice(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): date = models.DateTimeField( auto_now_add=True, verbose_name=_l("Date") ) - # TODO : change name to validity for clarity - valid = models.BooleanField( - default=True, - verbose_name=_l("Validated") - ) - # TODO : changed name to controlled for clarity - control = models.BooleanField( - default=False, - verbose_name=_l("Controlled") - ) - - class Meta: - abstract = False - permissions = ( - # TODO : change facture to invoice - ('change_facture_control', - _l("Can change the \"controlled\" state")), - # TODO : seems more likely to be call create_facture_pdf - # or create_invoice_pdf - ('change_facture_pdf', - _l("Can create a custom PDF invoice")), - ('view_facture', - _l("Can see an invoice's details")), - ('change_all_facture', - _l("Can edit all the previous invoices")), - ) - verbose_name = _l("Invoice") - verbose_name_plural = _l("Invoices") - - def linked_objects(self): - """Return linked objects : machine and domain. - Usefull in history display""" - return self.vente_set.all() # TODO : change prix to price def prix(self): @@ -167,6 +98,78 @@ class Facture(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): ).values_list('name', flat=True)) return name + +# TODO : change facture to invoice +class Facture(BaseInvoice): + """ + The model for an invoice. It reprensents the fact that a user paid for + something (it can be multiple article paid at once). + + An invoice is linked to : + * one or more purchases (one for each article sold that time) + * a user (the one who bought those articles) + * a payment method (the one used by the user) + * (if applicable) a bank + * (if applicable) a cheque number. + Every invoice is dated throught the 'date' value. + An invoice has a 'controlled' value (default : False) which means that + someone with high enough rights has controlled that invoice and taken it + into account. It also has a 'valid' value (default : True) which means + that someone with high enough rights has decided that this invoice was not + valid (thus it's like the user never paid for his articles). It may be + necessary in case of non-payment. + """ + + user = models.ForeignKey('users.User', on_delete=models.PROTECT) + # TODO : change paiement to payment + paiement = models.ForeignKey('Paiement', on_delete=models.PROTECT) + # TODO : change banque to bank + banque = models.ForeignKey( + 'Banque', + on_delete=models.PROTECT, + blank=True, + null=True + ) + # TODO : maybe change to cheque nummber because not evident + cheque = models.CharField( + max_length=255, + blank=True, + verbose_name=_l("Cheque number") + ) + # TODO : change name to validity for clarity + valid = models.BooleanField( + default=True, + verbose_name=_l("Validated") + ) + # TODO : changed name to controlled for clarity + control = models.BooleanField( + default=False, + verbose_name=_l("Controlled") + ) + + class Meta: + abstract = False + permissions = ( + # TODO : change facture to invoice + ('change_facture_control', + _l("Can change the \"controlled\" state")), + # TODO : seems more likely to be call create_facture_pdf + # or create_invoice_pdf + ('change_facture_pdf', + _l("Can create a custom PDF invoice")), + ('view_facture', + _l("Can see an invoice's details")), + ('change_all_facture', + _l("Can edit all the previous invoices")), + ) + verbose_name = _l("Invoice") + verbose_name_plural = _l("Invoices") + + def linked_objects(self): + """Return linked objects : machine and domain. + Usefull in history display""" + return self.vente_set.all() + def can_edit(self, user_request, *args, **kwargs): if not user_request.has_perm('cotisations.change_facture'): return False, _("You don't have the right to edit an invoice.") @@ -265,6 +268,28 @@ def facture_post_delete(**kwargs): user.ldap_sync(base=False, access_refresh=True, mac_refresh=False) +class CustomInvoice(BaseInvoice): + class Meta: + permissions = ( + ('view_custom_invoice', _l("Can view a custom invoice")), + ) + recipient = models.CharField( + max_length=255, + verbose_name=_l("Recipient") + ) + payment = models.CharField( + max_length=255, + verbose_name=_l("Payment type") + ) + address = models.CharField( + max_length=255, + verbose_name=_l("Address") + ) + paid = models.BooleanField( + verbose_name="Paid" + ) + + # TODO : change Vente to Purchase class Vente(RevMixin, AclMixin, models.Model): """ @@ -288,7 +313,7 @@ class Vente(RevMixin, AclMixin, models.Model): # TODO : change facture to invoice facture = models.ForeignKey( - 'Facture', + 'BaseInvoice', on_delete=models.CASCADE, verbose_name=_l("Invoice") ) @@ -355,6 +380,10 @@ class Vente(RevMixin, AclMixin, models.Model): cotisation_type defined (which means the article sold represents a cotisation) """ + try: + invoice = self.facture.facture + except Facture.DoesNotExist: + return if not hasattr(self, 'cotisation') and self.type_cotisation: cotisation = Cotisation(vente=self) cotisation.type_cotisation = self.type_cotisation @@ -362,7 +391,7 @@ class Vente(RevMixin, AclMixin, models.Model): end_cotisation = Cotisation.objects.filter( vente__in=Vente.objects.filter( facture__in=Facture.objects.filter( - user=self.facture.user + user=invoice.user ).exclude(valid=False)) ).filter( Q(type_cotisation='All') | @@ -371,9 +400,9 @@ class Vente(RevMixin, AclMixin, models.Model): date_start__lt=date_start ).aggregate(Max('date_end'))['date_end__max'] elif self.type_cotisation == "Adhesion": - end_cotisation = self.facture.user.end_adhesion() + end_cotisation = invoice.user.end_adhesion() else: - end_cotisation = self.facture.user.end_connexion() + end_cotisation = invoice.user.end_connexion() date_start = date_start or timezone.now() end_cotisation = end_cotisation or date_start date_max = max(end_cotisation, date_start) @@ -445,6 +474,10 @@ def vente_post_save(**kwargs): LDAP user when a purchase has been saved. """ purchase = kwargs['instance'] + try: + purchase.facture.facture + except Facture.DoesNotExist: + return if hasattr(purchase, 'cotisation'): purchase.cotisation.vente = purchase purchase.cotisation.save() @@ -462,8 +495,12 @@ def vente_post_delete(**kwargs): Synchronise the LDAP user after a purchase has been deleted. """ purchase = kwargs['instance'] + try: + invoice = purchase.facture.facture + except Facture.DoesNotExist: + return if purchase.type_cotisation: - user = purchase.facture.user + user = invoice.user user.ldap_sync(base=False, access_refresh=True, mac_refresh=False) diff --git a/cotisations/templates/cotisations/aff_custom_invoice.html b/cotisations/templates/cotisations/aff_custom_invoice.html new file mode 100644 index 00000000..1a477613 --- /dev/null +++ b/cotisations/templates/cotisations/aff_custom_invoice.html @@ -0,0 +1,108 @@ +{% 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 +Copyright © 2018 Hugo Levy-Falk + +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 i18n %} +{% load acl %} + +
    + {% if custom_invoice_list.paginator %} + {% include 'pagination.html' with list=custom_invoice_list %} + {% endif %} + + + + + + + + + + + + + + + + {% for invoice in custom_invoice_list %} + + + + + + + + + + + + {% endfor %} +
    + {% trans "Recipient" as tr_recip %} + {% include 'buttons/sort.html' with prefix='invoice' col='user' text=tr_user %} + {% trans "Designation" %}{% trans "Total price" %} + {% trans "Payment method" as tr_payment_method %} + {% include 'buttons/sort.html' with prefix='invoice' col='payement' text=tr_payment_method %} + + {% trans "Date" as tr_date %} + {% include 'buttons/sort.html' with prefix='invoice' col='date' text=tr_date %} + + {% trans "Invoice id" as tr_invoice_id %} + {% include 'buttons/sort.html' with prefix='invoice' col='id' text=tr_invoice_id %} + {% trans "Paid" %}
    {{ invoice.recipient }}{{ invoice.name }}{{ invoice.prix_total }}{{ invoice.payment }}{{ invoice.date }}{{ invoice.id }}{{ invoice.paid }} + + + + {% trans "PDF" %} + +
    + + {% if custom_invoice_list.paginator %} + {% include 'pagination.html' with list=custom_invoice_list %} + {% endif %} +
    diff --git a/cotisations/templates/cotisations/index_custom_invoice.html b/cotisations/templates/cotisations/index_custom_invoice.html new file mode 100644 index 00000000..67d00126 --- /dev/null +++ b/cotisations/templates/cotisations/index_custom_invoice.html @@ -0,0 +1,36 @@ +{% extends "cotisations/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 acl %} +{% load i18n %} + +{% block title %}{% trans "Custom invoices" %}{% endblock %} + +{% block content %} +

    {% trans "Custom invoices list" %}

    +{% can_create CustomInvoice %} +{% include "buttons/add.html" with href='cotisations:new-custom-invoice'%} +{% acl_end %} +{% include 'cotisations/aff_custom_invoice.html' with custom_invoice_list=custom_invoice_list %} +{% endblock %} diff --git a/cotisations/templates/cotisations/sidebar.html b/cotisations/templates/cotisations/sidebar.html index 296730f2..8d37bb6a 100644 --- a/cotisations/templates/cotisations/sidebar.html +++ b/cotisations/templates/cotisations/sidebar.html @@ -28,7 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% block sidebar %} {% can_change Facture pdf %} - + {% trans "Create an invoice" %} @@ -40,6 +40,11 @@ with this program; if not, write to the Free Software Foundation, Inc., {% trans "Invoices" %} {% acl_end %} + {% can_view_all CustomInvoice %} + + {% trans "Custom invoices" %} + + {% acl_end %} {% can_view_all Article %} {% trans "Available articles" %} diff --git a/cotisations/urls.py b/cotisations/urls.py index 470ccbfa..edc448fe 100644 --- a/cotisations/urls.py +++ b/cotisations/urls.py @@ -52,9 +52,29 @@ urlpatterns = [ name='facture-pdf' ), url( - r'^new_facture_pdf/$', - views.new_facture_pdf, - name='new-facture-pdf' + r'^index_custom_invoice/$', + views.index_custom_invoice, + name='index-custom-invoice' + ), + url( + r'^new_custom_invoice/$', + views.new_custom_invoice, + name='new-custom-invoice' + ), + url( + r'^edit_custom_invoice/(?P[0-9]+)$', + views.edit_custom_invoice, + name='edit-custom-invoice' + ), + url( + r'^custom_invoice_pdf/(?P[0-9]+)$', + views.custom_invoice_pdf, + name='custom-invoice-pdf', + ), + url( + r'^del_custom_invoice/(?P[0-9]+)$', + views.del_custom_invoice, + name='del-custom-invoice' ), url( r'^credit_solde/(?P[0-9]+)$', diff --git a/cotisations/views.py b/cotisations/views.py index 66eb66f5..90bc3632 100644 --- a/cotisations/views.py +++ b/cotisations/views.py @@ -58,7 +58,15 @@ from re2o.acl import ( can_change, ) from preferences.models import AssoOption, GeneralOption -from .models import Facture, Article, Vente, Paiement, Banque +from .models import ( + Facture, + Article, + Vente, + Paiement, + Banque, + CustomInvoice, + BaseInvoice +) from .forms import ( FactureForm, ArticleForm, @@ -67,10 +75,10 @@ from .forms import ( DelPaiementForm, BanqueForm, DelBanqueForm, - NewFactureFormPdf, SelectUserArticleForm, SelectClubArticleForm, - RechargeForm + RechargeForm, + CustomInvoiceForm ) from .tex import render_invoice from .payment_methods.forms import payment_method_factory @@ -178,10 +186,10 @@ def new_facture(request, user, userid): # TODO : change facture to invoice @login_required -@can_change(Facture, 'pdf') -def new_facture_pdf(request): +@can_create(CustomInvoice) +def new_custom_invoice(request): """ - View used to generate a custom PDF invoice. It's mainly used to + View used to generate a custom invoice. It's mainly used to get invoices that are not taken into account, for the administrative point of view. """ @@ -190,7 +198,7 @@ def new_facture_pdf(request): Q(type_user='All') | Q(type_user=request.user.class_name) ) # Building the invocie form and the article formset - invoice_form = NewFactureFormPdf(request.POST or None) + invoice_form = CustomInvoiceForm(request.POST or None) if request.user.is_class_club: articles_formset = formset_factory(SelectClubArticleForm)( request.POST or None, @@ -202,44 +210,31 @@ def new_facture_pdf(request): form_kwargs={'user': request.user} ) if invoice_form.is_valid() and articles_formset.is_valid(): - # Get the article list and build an list out of it - # contiaining (article_name, article_price, quantity, total_price) - articles_info = [] - for articles_form in articles_formset: - if articles_form.cleaned_data: - article = articles_form.cleaned_data['article'] - quantity = articles_form.cleaned_data['quantity'] - articles_info.append({ - 'name': article.name, - 'price': article.prix, - 'quantity': quantity, - 'total_price': article.prix * quantity - }) - paid = invoice_form.cleaned_data['paid'] - recipient = invoice_form.cleaned_data['dest'] - address = invoice_form.cleaned_data['chambre'] - total_price = sum(a['total_price'] for a in articles_info) + new_invoice_instance = invoice_form.save() + for art_item in articles_formset: + if art_item.cleaned_data: + article = art_item.cleaned_data['article'] + quantity = art_item.cleaned_data['quantity'] + Vente.objects.create( + facture=new_invoice_instance, + name=article.name, + prix=article.prix, + type_cotisation=article.type_cotisation, + duration=article.duration, + number=quantity + ) + messages.success( + request, + _('The custom invoice was successfully created.') + ) + return redirect(reverse('cotisations:index-custom-invoice')) + - return render_invoice(request, { - 'DATE': timezone.now(), - 'recipient_name': recipient, - 'address': address, - 'article': articles_info, - 'total': total_price, - 'paid': paid, - 'asso_name': AssoOption.get_cached_value('name'), - 'line1': AssoOption.get_cached_value('adresse1'), - 'line2': AssoOption.get_cached_value('adresse2'), - 'siret': AssoOption.get_cached_value('siret'), - 'email': AssoOption.get_cached_value('contact'), - 'phone': AssoOption.get_cached_value('telephone'), - 'tpl_path': os.path.join(settings.BASE_DIR, LOGO_PATH) - }) return form({ 'factureform': invoice_form, 'action_name': _("Create"), 'articlesformset': articles_formset, - 'articles': articles + 'articlelist': articles }, 'cotisations/facture.html', request) @@ -292,7 +287,7 @@ def facture_pdf(request, facture, **_kwargs): def edit_facture(request, facture, **_kwargs): """ View used to edit an existing invoice. - Articles can be added or remove to the invoice and quantity + Articles can be added or removed to the invoice and quantity can be set as desired. This is also the view used to invalidate an invoice. """ @@ -347,6 +342,100 @@ def del_facture(request, facture, **_kwargs): }, 'cotisations/delete.html', request) +@login_required +@can_edit(CustomInvoice) +def edit_custom_invoice(request, invoice, **kwargs): + # Building the invocie form and the article formset + invoice_form = CustomInvoiceForm( + request.POST or None, + instance=invoice + ) + purchases_objects = Vente.objects.filter(facture=invoice) + purchase_form_set = modelformset_factory( + Vente, + fields=('name', 'number'), + extra=0, + max_num=len(purchases_objects) + ) + purchase_form = purchase_form_set( + request.POST or None, + queryset=purchases_objects + ) + if invoice_form.is_valid() and purchase_form.is_valid(): + if invoice_form.changed_data: + invoice_form.save() + purchase_form.save() + messages.success( + request, + _("The invoice has been successfully edited.") + ) + return redirect(reverse('cotisations:index-custom-invoice')) + + return form({ + 'factureform': invoice_form, + 'venteform': purchase_form + }, 'cotisations/edit_facture.html', request) + + +@login_required +@can_view(CustomInvoice) +def custom_invoice_pdf(request, invoice, **_kwargs): + """ + View used to generate a PDF file from an existing invoice in database + Creates a line for each Purchase (thus article sold) and generate the + invoice with the total price, the payment method, the address and the + legal information for the user. + """ + # TODO : change vente to purchase + purchases_objects = Vente.objects.all().filter(facture=invoice) + # Get the article list and build an list out of it + # contiaining (article_name, article_price, quantity, total_price) + purchases_info = [] + for purchase in purchases_objects: + purchases_info.append({ + 'name': purchase.name, + 'price': purchase.prix, + 'quantity': purchase.number, + 'total_price': purchase.prix_total + }) + return render_invoice(request, { + 'paid': invoice.paid, + 'fid': invoice.id, + 'DATE': invoice.date, + 'recipient_name': invoice.recipient, + 'address': invoice.address, + 'article': purchases_info, + 'total': invoice.prix_total(), + 'asso_name': AssoOption.get_cached_value('name'), + 'line1': AssoOption.get_cached_value('adresse1'), + 'line2': AssoOption.get_cached_value('adresse2'), + 'siret': AssoOption.get_cached_value('siret'), + 'email': AssoOption.get_cached_value('contact'), + 'phone': AssoOption.get_cached_value('telephone'), + 'tpl_path': os.path.join(settings.BASE_DIR, LOGO_PATH) + }) + + +# TODO : change facture to invoice +@login_required +@can_delete(CustomInvoice) +def del_custom_invoice(request, invoice, **_kwargs): + """ + View used to delete an existing invocie. + """ + if request.method == "POST": + invoice.delete() + messages.success( + request, + _("The invoice has been successfully deleted.") + ) + return redirect(reverse('cotisations:index-custom-invoice')) + return form({ + 'objet': invoice, + 'objet_name': _("Invoice") + }, 'cotisations/delete.html', request) + + @login_required @can_create(Article) def add_article(request): @@ -681,8 +770,31 @@ def index_banque(request): }) +@login_required +@can_view_all(CustomInvoice) +def index_custom_invoice(request): + """View used to display every custom invoice.""" + pagination_number = GeneralOption.get_cached_value('pagination_number') + custom_invoice_list = CustomInvoice.objects.prefetch_related('vente_set') + custom_invoice_list = SortTable.sort( + custom_invoice_list, + request.GET.get('col'), + request.GET.get('order'), + SortTable.COTISATIONS_CUSTOM + ) + custom_invoice_list = re2o_paginator( + request, + custom_invoice_list, + pagination_number, + ) + return render(request, 'cotisations/index_custom_invoice.html', { + 'custom_invoice_list': custom_invoice_list + }) + + @login_required @can_view_all(Facture) +@can_view_all(CustomInvoice) def index(request): """ View used to display the list of all exisitng invoices. @@ -698,7 +810,7 @@ def index(request): ) invoice_list = re2o_paginator(request, invoice_list, pagination_number) return render(request, 'cotisations/index.html', { - 'facture_list': invoice_list + 'facture_list': invoice_list, }) diff --git a/re2o/utils.py b/re2o/utils.py index 75304369..6f7870f0 100644 --- a/re2o/utils.py +++ b/re2o/utils.py @@ -250,6 +250,14 @@ class SortTable: 'cotis_id': ['id'], 'default': ['-date'] } + COTISATIONS_CUSTOM = { + 'invoice_date': ['date'], + 'invoice_id': ['id'], + 'invoice_recipient': ['recipient'], + 'invoice_address': ['address'], + 'invoice_payment': ['payment'], + 'default': ['-date'] + } COTISATIONS_CONTROL = { 'control_name': ['user__adherent__name'], 'control_surname': ['user__surname'], diff --git a/templates/buttons/add.html b/templates/buttons/add.html index 17058b89..33148a7b 100644 --- a/templates/buttons/add.html +++ b/templates/buttons/add.html @@ -21,6 +21,6 @@ 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 %} - + From 5236b659b6ef2c2ee82dfa4249501d46040de366 Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Tue, 24 Jul 2018 22:39:36 +0200 Subject: [PATCH 126/171] Fix l'historique --- cotisations/admin.py | 7 +++++++ cotisations/templates/cotisations/aff_custom_invoice.html | 5 ++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/cotisations/admin.py b/cotisations/admin.py index 587bc066..afe4621c 100644 --- a/cotisations/admin.py +++ b/cotisations/admin.py @@ -30,6 +30,7 @@ from django.contrib import admin from reversion.admin import VersionAdmin from .models import Facture, Article, Banque, Paiement, Cotisation, Vente +from .models import CustomInvoice class FactureAdmin(VersionAdmin): @@ -37,6 +38,11 @@ class FactureAdmin(VersionAdmin): pass +class CustomInvoiceAdmin(VersionAdmin): + """Admin class for custom invoices.""" + pass + + class VenteAdmin(VersionAdmin): """Class admin d'une vente, tous les champs (facture related)""" pass @@ -69,3 +75,4 @@ admin.site.register(Banque, BanqueAdmin) admin.site.register(Paiement, PaiementAdmin) admin.site.register(Vente, VenteAdmin) admin.site.register(Cotisation, CotisationAdmin) +admin.site.register(CustomInvoice, CustomInvoiceAdmin) diff --git a/cotisations/templates/cotisations/aff_custom_invoice.html b/cotisations/templates/cotisations/aff_custom_invoice.html index 1a477613..6cd7e1b8 100644 --- a/cotisations/templates/cotisations/aff_custom_invoice.html +++ b/cotisations/templates/cotisations/aff_custom_invoice.html @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load i18n %} {% load acl %} +{% load logs_extra %}
    {% if custom_invoice_list.paginator %} @@ -86,9 +87,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% acl_end %}
  • - - {% trans "Historique" %} - + {% history_button invoice text=True html_class=False %}
  • From e38ae33c6e94c53e987d7889d17b5f055f09115a Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Wed, 25 Jul 2018 11:18:08 +0200 Subject: [PATCH 127/171] =?UTF-8?q?Utilise=20le=20filter=20tick=20pour=20a?= =?UTF-8?q?fficher=20les=20bool=C3=A9ens=20de=20custom=20invoice?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cotisations/templates/cotisations/aff_custom_invoice.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cotisations/templates/cotisations/aff_custom_invoice.html b/cotisations/templates/cotisations/aff_custom_invoice.html index 6cd7e1b8..22c0819b 100644 --- a/cotisations/templates/cotisations/aff_custom_invoice.html +++ b/cotisations/templates/cotisations/aff_custom_invoice.html @@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load i18n %} {% load acl %} {% load logs_extra %} +{% load design %}
    {% if custom_invoice_list.paginator %} @@ -65,7 +66,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {{ invoice.payment }} {{ invoice.date }} {{ invoice.id }} - {{ invoice.paid }} + {{ invoice.paid|tick }} - - + {% can_edit invoice %} + {% include 'buttons/edit.html' with href='cotisations:edit-custom-invoice' id=invoice.id %} + {% acl_end %} + {% can_delete invoice %} + {% include 'buttons/suppr.html' with href='cotisations:del-custom-invoice' id=invoice.id %} + {% acl_end %} + {% history_button invoice %} {% trans "PDF" %} diff --git a/cotisations/templates/cotisations/sidebar.html b/cotisations/templates/cotisations/sidebar.html index 8d37bb6a..7be91b1c 100644 --- a/cotisations/templates/cotisations/sidebar.html +++ b/cotisations/templates/cotisations/sidebar.html @@ -27,7 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load i18n %} {% block sidebar %} - {% can_change Facture pdf %} + {% can_create CustomInvoice %} {% trans "Create an invoice" %} From 63c80d69a3d2447a221c93fbafbd1cfa3835f155 Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Thu, 16 Aug 2018 00:22:07 +0200 Subject: [PATCH 133/171] Fix de render_tex --- cotisations/tex.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cotisations/tex.py b/cotisations/tex.py index f3f8601b..b7e1cb81 100644 --- a/cotisations/tex.py +++ b/cotisations/tex.py @@ -105,7 +105,7 @@ def render_tex(_request, template, ctx={}): Returns: An HttpResponse with type `application/pdf` containing the PDF file. """ - pdf = create_pdf(template, ctx={}) + pdf = create_pdf(template, ctx) r = HttpResponse(content_type='application/pdf') r.write(pdf) return r From 6cc22c376d91637ee2e56d9d48c679ac8add1237 Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Thu, 16 Aug 2018 00:22:39 +0200 Subject: [PATCH 134/171] =?UTF-8?q?Adapte=20les=20migrations=20de=20custom?= =?UTF-8?q?=5Finvoice=20=C3=A0=20la=20branche=20dev?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{0031_custom_invoice.py => 0032_custom_invoice.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename cotisations/migrations/{0031_custom_invoice.py => 0032_custom_invoice.py} (98%) diff --git a/cotisations/migrations/0031_custom_invoice.py b/cotisations/migrations/0032_custom_invoice.py similarity index 98% rename from cotisations/migrations/0031_custom_invoice.py rename to cotisations/migrations/0032_custom_invoice.py index fd5ac336..e143ae13 100644 --- a/cotisations/migrations/0031_custom_invoice.py +++ b/cotisations/migrations/0032_custom_invoice.py @@ -45,7 +45,7 @@ def update_rights(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ('cotisations', '0030_custom_payment'), + ('cotisations', '0031_comnpaypayment_production'), ] operations = [ From c148306eb31571ecdd470b3dee1848e2ffb8edcf Mon Sep 17 00:00:00 2001 From: grisel-davy Date: Wed, 15 Aug 2018 19:08:00 +0200 Subject: [PATCH 135/171] nom cliquable dans la liste des whitelist --- users/templates/users/aff_whitelists.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/users/templates/users/aff_whitelists.html b/users/templates/users/aff_whitelists.html index 98989093..653e31e0 100644 --- a/users/templates/users/aff_whitelists.html +++ b/users/templates/users/aff_whitelists.html @@ -43,7 +43,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% else %} {% endif %} - {{ whitelist.user }} + {{ whitelist.user }} {{ whitelist.raison }} {{ whitelist.date_start }} {{ whitelist.date_end }} From 92cdb3004e816bae15e6348346b6fedb884f6bc0 Mon Sep 17 00:00:00 2001 From: grisel-davy Date: Thu, 16 Aug 2018 10:33:00 +0200 Subject: [PATCH 136/171] nom cliquable dans les bans aussi --- users/templates/users/aff_bans.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/users/templates/users/aff_bans.html b/users/templates/users/aff_bans.html index 7f25a2ab..ebcbdeee 100644 --- a/users/templates/users/aff_bans.html +++ b/users/templates/users/aff_bans.html @@ -43,7 +43,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% else %} {% endif %} - {{ ban.user }} + {{ ban.user }} {{ ban.raison }} {{ ban.date_start }} {{ ban.date_end }} From 9c065b57e3300e25ddc47a9131ec7a12719a0fb0 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Thu, 16 Aug 2018 19:31:56 +0200 Subject: [PATCH 137/171] Fix #153, pas de solde par solde --- cotisations/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cotisations/forms.py b/cotisations/forms.py index ccf9d5d6..244b1c13 100644 --- a/cotisations/forms.py +++ b/cotisations/forms.py @@ -272,7 +272,7 @@ class RechargeForm(FormRevMixin, Form): super(RechargeForm, self).__init__(*args, **kwargs) self.fields['payment'].empty_label = \ _("Select a payment method") - self.fields['payment'].queryset = Paiement.find_allowed_payments(user) + self.fields['payment'].queryset = Paiement.find_allowed_payments(user).exclude(is_balance=True) def clean(self): """ From ded0aadb9d13cd629cda6c0511dd88ec7cb58c5e Mon Sep 17 00:00:00 2001 From: grisel-davy Date: Sat, 4 Aug 2018 23:13:01 +0200 Subject: [PATCH 138/171] =?UTF-8?q?Ajout=20du=20gid=20au=20s=C3=A9rialiser?= =?UTF-8?q?=20Adherent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/serializers.py | 2 +- users/models.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/api/serializers.py b/api/serializers.py index 4d406898..2e938a35 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -547,7 +547,7 @@ class AdherentSerializer(NamespacedHMSerializer): fields = ('name', 'surname', 'pseudo', 'email', 'local_email_redirect', 'local_email_enabled', 'school', 'shell', 'comment', 'state', 'registered', 'telephone', 'room', 'solde', - 'access', 'end_access', 'uid', 'api_url') + 'access', 'end_access', 'uid', 'api_url','gid') extra_kwargs = { 'shell': {'view_name': 'shell-detail'} } diff --git a/users/models.py b/users/models.py index bceb84bb..f3e786c0 100755 --- a/users/models.py +++ b/users/models.py @@ -349,6 +349,11 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, """ Renvoie seulement le nom""" return self.surname + @cached_property + def gid(self): + """return the default gid of user""" + return LDAP['user_gid'] + @property def get_shell(self): """ A utiliser de préférence, prend le shell par défaut From 0472f913fa941c2c83d1d76d413d13306ef3b339 Mon Sep 17 00:00:00 2001 From: Laouen Fernet Date: Sun, 20 May 2018 21:21:29 +0200 Subject: [PATCH 139/171] Translation of templates/ (front) --- templates/base.html | 41 +-- templates/buttons/add.html | 7 +- templates/buttons/edit.html | 7 +- templates/buttons/history.html | 1 + templates/buttons/multiple_checkbox_alt.html | 1 + templates/buttons/setlang.html | 3 +- templates/buttons/sort.html | 13 +- templates/buttons/suppr.html | 7 +- templates/errors/404.html | 10 +- templates/errors/500.html | 31 ++- templates/locale/fr/LC_MESSAGES/django.mo | Bin 2042 -> 5348 bytes templates/locale/fr/LC_MESSAGES/django.po | 276 +++++++++++++------ templates/pagination.html | 1 + templates/registration/login.html | 17 +- 14 files changed, 277 insertions(+), 138 deletions(-) diff --git a/templates/base.html b/templates/base.html index 698195f7..aeb840da 100644 --- a/templates/base.html +++ b/templates/base.html @@ -39,8 +39,8 @@ with this program; if not, write to the Free Software Foundation, Inc., - - + + {# Load CSS and JavaScript #} {% bootstrap_css %} @@ -78,17 +78,17 @@ with this program; if not, write to the Free Software Foundation, Inc.,
    + diff --git a/cotisations/templates/cotisations/aff_custom_invoice.html b/cotisations/templates/cotisations/aff_custom_invoice.html index 1d182178..41984c2c 100644 --- a/cotisations/templates/cotisations/aff_custom_invoice.html +++ b/cotisations/templates/cotisations/aff_custom_invoice.html @@ -47,7 +47,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% include 'buttons/sort.html' with prefix='invoice' col='date' text=tr_date %} - {% trans "Invoice id" as tr_invoice_id %} + {% trans "Invoice ID" as tr_invoice_id %} {% include 'buttons/sort.html' with prefix='invoice' col='id' text=tr_invoice_id %} diff --git a/cotisations/templates/cotisations/aff_paiement.html b/cotisations/templates/cotisations/aff_paiement.html index 46523928..633eb456 100644 --- a/cotisations/templates/cotisations/aff_paiement.html +++ b/cotisations/templates/cotisations/aff_paiement.html @@ -41,7 +41,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {{ paiement.moyen }} {{ paiement.available_for_everyone|tick }} - {{paiement.get_payment_method_name}} + {{ paiement.get_payment_method_name }} {% can_edit paiement %} diff --git a/cotisations/templates/cotisations/control.html b/cotisations/templates/cotisations/control.html index bb3a06b6..6a4a5cca 100644 --- a/cotisations/templates/cotisations/control.html +++ b/cotisations/templates/cotisations/control.html @@ -30,17 +30,20 @@ with this program; if not, write to the Free Software Foundation, Inc., {% block title %}{% trans "Invoice control" %}{% endblock %} {% block content %} +

    {% trans "Invoice control and validation" %}

    + {% if facture_list.paginator %} {% include 'pagination.html' with list=facture_list %} {% endif %} +
    {% csrf_token %} {{ controlform.management_form }} - + @@ -65,7 +68,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
    {% trans "Profil" %}{% trans "Profile" %} {% trans "Last name" as tr_last_name %} {% include 'buttons/sort.html' with prefix='control' col='name' text=tr_last_name %} @@ -50,11 +53,11 @@ with this program; if not, write to the Free Software Foundation, Inc., {% include 'buttons/sort.html' with prefix='control' col='surname' text=tr_first_name %} - {% trans "Invoice id" as tr_invoice_id %} + {% trans "Invoice ID" as tr_invoice_id %} {% include 'buttons/sort.html' with prefix='control' col='id' text=tr_invoice_id %} - {% trans "User id" as tr_user_id %} + {% trans "User ID" as tr_user_id %} {% include 'buttons/sort.html' with prefix='control' col='user-id' text=tr_user_id %} {% trans "Designation" %} {% trans "Date" as tr_date %} - {% include 'buttons/sort.html' with prefix='control' col='date' text=tr_date %}i + {% include 'buttons/sort.html' with prefix='control' col='date' text=tr_date %} {% trans "Validated" as tr_validated %} @@ -109,3 +112,4 @@ with this program; if not, write to the Free Software Foundation, Inc., {% if facture_list.paginator %} {% include 'pagination.html' with list=facture_list %} {% endif %} + diff --git a/cotisations/templates/cotisations/delete.html b/cotisations/templates/cotisations/delete.html index a1c95d7a..dc06e5a5 100644 --- a/cotisations/templates/cotisations/delete.html +++ b/cotisations/templates/cotisations/delete.html @@ -26,18 +26,17 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} {% load i18n %} -{% block title %}{% trans "Deletion of cotisations" %}{% endblock %} +{% block title %}{% trans "Deletion of subscriptions" %}{% endblock %} {% block content %} {% csrf_token %}

    - {% blocktrans %} - Warning. Are you sure you really want te delete this {{ object_name }} object ( {{ objet }} ) ? - {% endblocktrans %} + {% blocktrans %}Warning: are you sure you really want to delete this {{ object_name }} object ( {{ objet }} )?{% endblocktrans %}

    {% trans "Confirm" as tr_confirm %} {% bootstrap_button tr_confirm button_type='submit' icon='trash' %} {% endblock %} + diff --git a/cotisations/templates/cotisations/edit_facture.html b/cotisations/templates/cotisations/edit_facture.html index d28f8511..9ddcac8c 100644 --- a/cotisations/templates/cotisations/edit_facture.html +++ b/cotisations/templates/cotisations/edit_facture.html @@ -28,7 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load massive_bootstrap_form %} {% load i18n %} -{% block title %}{% trans "Invoices creation and edition" %}{% endblock %} +{% block title %}{% trans "Creation and editing of invoices" %}{% endblock %} {% block content %} {% bootstrap_form_errors factureform %} @@ -62,3 +62,4 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endblock %} + diff --git a/cotisations/templates/cotisations/facture.html b/cotisations/templates/cotisations/facture.html index 4ec05ec1..4f905160 100644 --- a/cotisations/templates/cotisations/facture.html +++ b/cotisations/templates/cotisations/facture.html @@ -27,20 +27,21 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load staticfiles%} {% load i18n %} -{% block title %}{% trans "Invoices creation and edition" %}{% endblock %} +{% block title %}{% trans "Creation and editing of invoices" %}{% endblock %} {% block content %} + {% if title %} -

    {{title}}

    +

    {{ title }}

    {% else %}

    {% trans "New invoice" %}

    {% endif %} {% if max_balance %} -

    {% trans "Maximum allowed balance : "%}{{max_balance}} €

    +

    {% blocktrans %}Maximum allowed balance: {{ max_balance }} €{% endblocktrans %}

    {% endif %} {% if balance is not None %}

    - {% trans "Current balance :" %} {{ balance }} € +{% blocktrans %}Current balance: {{ balance }} €{% endblocktrans %}

    {% endif %} @@ -68,9 +69,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,

    - {% blocktrans %} - Total price : 0,00 € - {% endblocktrans %} + {% blocktrans %}Total price: 0,00 €{% endblocktrans %}

    {% endif %} {% bootstrap_button action_name button_type='submit' icon='star' %} @@ -183,3 +182,4 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endif %} {% endblock %} + diff --git a/cotisations/templates/cotisations/index.html b/cotisations/templates/cotisations/index.html index 9482cb5a..ca9cde5b 100644 --- a/cotisations/templates/cotisations/index.html +++ b/cotisations/templates/cotisations/index.html @@ -29,7 +29,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% block title %}{% trans "Invoices" %}{% endblock %} {% block content %} -

    {% trans "Cotisations" %}

    +

    {% trans "Subscriptions" %}

    {% include 'cotisations/aff_cotisations.html' with facture_list=facture_list %} {% endblock %} diff --git a/cotisations/templates/cotisations/index_article.html b/cotisations/templates/cotisations/index_article.html index 5e6c3967..41ffb62e 100644 --- a/cotisations/templates/cotisations/index_article.html +++ b/cotisations/templates/cotisations/index_article.html @@ -37,7 +37,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% acl_end %} - {% trans "Delete article types" %} + {% trans "Delete one or several article types" %} {% include 'cotisations/aff_article.html' with article_list=article_list %} {% endblock %} diff --git a/cotisations/templates/cotisations/index_banque.html b/cotisations/templates/cotisations/index_banque.html index e9118d75..f4dea1b1 100644 --- a/cotisations/templates/cotisations/index_banque.html +++ b/cotisations/templates/cotisations/index_banque.html @@ -37,7 +37,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% acl_end %} - {% trans "Delete banks" %} + {% trans "Delete one or several banks" %} {% include 'cotisations/aff_banque.html' with banque_list=banque_list %} {% endblock %} diff --git a/cotisations/templates/cotisations/index_paiement.html b/cotisations/templates/cotisations/index_paiement.html index d84c72eb..f4908d02 100644 --- a/cotisations/templates/cotisations/index_paiement.html +++ b/cotisations/templates/cotisations/index_paiement.html @@ -27,17 +27,17 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load acl %} {% load i18n %} -{% block title %}{% trans "Payments" %}{% endblock %} +{% block title %}{% trans "Payment methods" %}{% endblock %} {% block content %} -

    {% trans "Payment types list" %}

    +

    {% trans "List of payment methods" %}

    {% can_create Paiement %} - {% trans "Add a payment type" %} + {% trans "Add a payment method" %} {% acl_end %} - {% trans "Delete payment types" %} + {% trans "Delete one or several payment methods" %} {% include 'cotisations/aff_paiement.html' with paiement_list=paiement_list %} {% endblock %} diff --git a/cotisations/templates/cotisations/payment.html b/cotisations/templates/cotisations/payment.html index e1c8b0d0..997168fd 100644 --- a/cotisations/templates/cotisations/payment.html +++ b/cotisations/templates/cotisations/payment.html @@ -31,11 +31,9 @@ with this program; if not, write to the Free Software Foundation, Inc., {% block content %}

    - {% blocktrans %} - Pay {{ amount }} € - {% endblocktrans %} + {% blocktrans %}Pay {{ amount }} €{% endblocktrans %}

    -
    + {{ content | safe }} {% if form %} {% csrf_token %} @@ -45,3 +43,4 @@ with this program; if not, write to the Free Software Foundation, Inc., {% bootstrap_button tr_pay button_type='submit' icon='piggy-bank' %}
    {% endblock %} + diff --git a/cotisations/templates/cotisations/sidebar.html b/cotisations/templates/cotisations/sidebar.html index 7be91b1c..4f077fad 100644 --- a/cotisations/templates/cotisations/sidebar.html +++ b/cotisations/templates/cotisations/sidebar.html @@ -61,3 +61,4 @@ with this program; if not, write to the Free Software Foundation, Inc., {% acl_end %} {% endblock %} + diff --git a/cotisations/validators.py b/cotisations/validators.py index fa8ea2cf..b1683e82 100644 --- a/cotisations/validators.py +++ b/cotisations/validators.py @@ -17,5 +17,6 @@ def check_no_balance(is_balance): p = Paiement.objects.filter(is_balance=True) if len(p) > 0: raise ValidationError( - _("There are already payment method(s) for user balance") + _("There is already a payment method for user balance.") ) + diff --git a/cotisations/views.py b/cotisations/views.py index 90bc3632..193f4321 100644 --- a/cotisations/views.py +++ b/cotisations/views.py @@ -225,7 +225,7 @@ def new_custom_invoice(request): ) messages.success( request, - _('The custom invoice was successfully created.') + _("The custom invoice was created.") ) return redirect(reverse('cotisations:index-custom-invoice')) @@ -313,7 +313,7 @@ def edit_facture(request, facture, **_kwargs): purchase_form.save() messages.success( request, - _("The invoice has been successfully edited.") + _("The invoice was edited.") ) return redirect(reverse('cotisations:index')) return form({ @@ -333,7 +333,7 @@ def del_facture(request, facture, **_kwargs): facture.delete() messages.success( request, - _("The invoice has been successfully deleted.") + _("The invoice was deleted.") ) return redirect(reverse('cotisations:index')) return form({ @@ -367,7 +367,7 @@ def edit_custom_invoice(request, invoice, **kwargs): purchase_form.save() messages.success( request, - _("The invoice has been successfully edited.") + _("The invoice was edited.") ) return redirect(reverse('cotisations:index-custom-invoice')) @@ -427,7 +427,7 @@ def del_custom_invoice(request, invoice, **_kwargs): invoice.delete() messages.success( request, - _("The invoice has been successfully deleted.") + _("The invoice was deleted.") ) return redirect(reverse('cotisations:index-custom-invoice')) return form({ @@ -453,7 +453,7 @@ def add_article(request): article.save() messages.success( request, - _("The article has been successfully created.") + _("The article was created.") ) return redirect(reverse('cotisations:index-article')) return form({ @@ -475,7 +475,7 @@ def edit_article(request, article_instance, **_kwargs): article.save() messages.success( request, - _("The article has been successfully edited.") + _("The article was edited.") ) return redirect(reverse('cotisations:index-article')) return form({ @@ -497,7 +497,7 @@ def del_article(request, instances): article_del.delete() messages.success( request, - _("The article(s) have been successfully deleted.") + _("The articles were deleted.") ) return redirect(reverse('cotisations:index-article')) return form({ @@ -525,7 +525,7 @@ def add_paiement(request): payment_method.save(payment) messages.success( request, - _("The payment method has been successfully created.") + _("The payment method was created.") ) return redirect(reverse('cotisations:index-paiement')) return form({ @@ -561,8 +561,7 @@ def edit_paiement(request, paiement_instance, **_kwargs): if payment_method is not None: payment_method.save() messages.success( - request, - _("The payement method has been successfully edited.") + request,_("The payment method was edited.") ) return redirect(reverse('cotisations:index-paiement')) return form({ @@ -588,8 +587,7 @@ def del_paiement(request, instances): payment_del.delete() messages.success( request, - _("The payment method %(method_name)s has been \ - successfully deleted.") % { + _("The payment method %(method_name)s was deleted.") % { 'method_name': payment_del } ) @@ -621,7 +619,7 @@ def add_banque(request): bank.save() messages.success( request, - _("The bank has been successfully created.") + _("The bank was created.") ) return redirect(reverse('cotisations:index-banque')) return form({ @@ -644,7 +642,7 @@ def edit_banque(request, banque_instance, **_kwargs): bank.save() messages.success( request, - _("The bank has been successfully edited") + _("The bank was edited.") ) return redirect(reverse('cotisations:index-banque')) return form({ @@ -669,8 +667,7 @@ def del_banque(request, instances): bank_del.delete() messages.success( request, - _("The bank %(bank_name)s has been successfully \ - deleted.") % { + _("The bank %(bank_name)s was deleted.") % { 'bank_name': bank_del } ) @@ -873,3 +870,4 @@ def credit_solde(request, user, **_kwargs): 'action_name': _("Pay"), 'max_balance': p.payment_method.maximum_balance, }, 'cotisations/facture.html', request) + From af40e3ea4e59107a29ba34c081692632d61d2ab0 Mon Sep 17 00:00:00 2001 From: Laouen Fernet Date: Wed, 15 Aug 2018 19:15:26 +0200 Subject: [PATCH 142/171] Translation of users/ (front) --- users/acl.py | 6 +- users/forms.py | 116 +- users/locale/fr/LC_MESSAGES/django.mo | Bin 1499 -> 25257 bytes users/locale/fr/LC_MESSAGES/django.po | 1312 ++++++++++++++++- users/migrations/0076_auto_20180818_1321.py | 109 ++ users/models.py | 269 ++-- users/templates/users/aff_bans.html | 29 +- users/templates/users/aff_clubs.html | 47 +- users/templates/users/aff_emailaddress.html | 22 +- users/templates/users/aff_rights.html | 3 +- users/templates/users/aff_schools.html | 7 +- users/templates/users/aff_serviceusers.html | 53 +- users/templates/users/aff_shell.html | 29 +- users/templates/users/aff_users.html | 56 +- users/templates/users/aff_whitelists.html | 23 +- users/templates/users/delete.html | 9 +- users/templates/users/index.html | 5 +- users/templates/users/index_ban.html | 5 +- users/templates/users/index_clubs.html | 5 +- users/templates/users/index_emailaddress.html | 7 +- users/templates/users/index_listright.html | 9 +- users/templates/users/index_rights.html | 5 +- users/templates/users/index_schools.html | 11 +- users/templates/users/index_serviceusers.html | 10 +- users/templates/users/index_shell.html | 7 +- users/templates/users/index_whitelist.html | 5 +- users/templates/users/mass_archive.html | 9 +- users/templates/users/profil.html | 377 +++-- users/templates/users/sidebar.html | 26 +- users/templates/users/user.html | 8 +- users/views.py | 159 +- 31 files changed, 2108 insertions(+), 630 deletions(-) create mode 100644 users/migrations/0076_auto_20180818_1321.py diff --git a/users/acl.py b/users/acl.py index 8eca1e63..cb3a16db 100644 --- a/users/acl.py +++ b/users/acl.py @@ -25,7 +25,7 @@ Here are defined some functions to check acl on the application. """ - +from django.utils.translation import ugettext as _ def can_view(user): """Check if an user can view the application. @@ -38,4 +38,6 @@ def can_view(user): viewing is granted and msg is a message (can be None). """ can = user.has_module_perms('users') - return can, None if can else "Vous ne pouvez pas voir cette application." + return can, None if can else _("You don't have the right to view this" + " application.") + diff --git a/users/forms.py b/users/forms.py index dac86d71..f1b50652 100644 --- a/users/forms.py +++ b/users/forms.py @@ -39,6 +39,7 @@ from django.contrib.auth.forms import ReadOnlyPasswordHashField from django.core.validators import MinLengthValidator from django.utils import timezone from django.contrib.auth.models import Group, Permission +from django.utils.translation import ugettext_lazy as _ from preferences.models import OptionalUser from re2o.utils import remove_user_room, get_input_formats_help_text @@ -66,18 +67,18 @@ class PassForm(FormRevMixin, FieldPermissionFormMixin, forms.ModelForm): nouveaux mots de passe renseignés sont identiques et respectent une norme""" selfpasswd = forms.CharField( - label=u'Saisir le mot de passe existant', + label=_("Current password"), max_length=255, widget=forms.PasswordInput ) passwd1 = forms.CharField( - label=u'Nouveau mot de passe', + label=_("New password"), max_length=255, validators=[MinLengthValidator(8)], widget=forms.PasswordInput ) passwd2 = forms.CharField( - label=u'Saisir à nouveau le mot de passe', + label=_("New password confirmation"), max_length=255, validators=[MinLengthValidator(8)], widget=forms.PasswordInput @@ -94,7 +95,7 @@ class PassForm(FormRevMixin, FieldPermissionFormMixin, forms.ModelForm): password2 = self.cleaned_data.get("passwd2") if password1 and password2 and password1 != password2: raise forms.ValidationError( - "Les 2 nouveaux mots de passe sont différents" + _("The new passwords don't match.") ) return password2 @@ -103,7 +104,7 @@ class PassForm(FormRevMixin, FieldPermissionFormMixin, forms.ModelForm): if not self.instance.check_password( self.cleaned_data.get("selfpasswd") ): - raise forms.ValidationError("Le mot de passe actuel est incorrect") + raise forms.ValidationError(_("The current password is incorrect.")) return def save(self, commit=True): @@ -121,18 +122,18 @@ class UserCreationForm(FormRevMixin, forms.ModelForm): l'admin, lors de la creation d'un user par admin. Inclu tous les champs obligatoires""" password1 = forms.CharField( - label='Password', + label=_("Password"), widget=forms.PasswordInput, validators=[MinLengthValidator(8)], max_length=255 ) password2 = forms.CharField( - label='Password confirmation', + label=_("Password confirmation"), widget=forms.PasswordInput, validators=[MinLengthValidator(8)], max_length=255 ) - is_admin = forms.BooleanField(label='is admin') + is_admin = forms.BooleanField(label=_("Is admin")) def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) @@ -142,7 +143,8 @@ class UserCreationForm(FormRevMixin, forms.ModelForm): if not OptionalUser.objects.first().local_email_domain in self.cleaned_data.get('email'): return self.cleaned_data.get('email').lower() else: - raise forms.ValidationError("You can't use an internal address as your external address.") + raise forms.ValidationError(_("You can't use an internal address" + " as your external address.")) class Meta: model = Adherent @@ -154,7 +156,7 @@ class UserCreationForm(FormRevMixin, forms.ModelForm): password1 = self.cleaned_data.get("password1") password2 = self.cleaned_data.get("password2") if password1 and password2 and password1 != password2: - raise forms.ValidationError("Passwords don't match") + raise forms.ValidationError(_("The passwords don't match.")) return password2 def save(self, commit=True): @@ -173,13 +175,13 @@ class ServiceUserCreationForm(FormRevMixin, forms.ModelForm): Formulaire pour la creation de nouveaux serviceusers. Requiert seulement un mot de passe; et un pseudo""" password1 = forms.CharField( - label='Password', + label=_("Password"), widget=forms.PasswordInput, min_length=8, max_length=255 ) password2 = forms.CharField( - label='Password confirmation', + label=_("Password confirmation"), widget=forms.PasswordInput, min_length=8, max_length=255 @@ -203,7 +205,7 @@ class ServiceUserCreationForm(FormRevMixin, forms.ModelForm): password1 = self.cleaned_data.get("password1") password2 = self.cleaned_data.get("password2") if password1 and password2 and password1 != password2: - raise forms.ValidationError("Passwords don't match") + raise forms.ValidationError(_("The passwords don't match.")) return password2 def save(self, commit=True): @@ -222,7 +224,7 @@ class UserChangeForm(FormRevMixin, forms.ModelForm): Formulaire pour la modification d'un user coté admin """ password = ReadOnlyPasswordHashField() - is_admin = forms.BooleanField(label='is admin', required=False) + is_admin = forms.BooleanField(label=_("Is admin"), required=False) class Meta: model = Adherent @@ -231,7 +233,7 @@ class UserChangeForm(FormRevMixin, forms.ModelForm): def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(UserChangeForm, self).__init__(*args, prefix=prefix, **kwargs) - print("User is admin : %s" % kwargs['instance'].is_admin) + print(_("User is admin: %s") % kwargs['instance'].is_admin) self.initial['is_admin'] = kwargs['instance'].is_admin def clean_password(self): @@ -279,7 +281,7 @@ class ServiceUserChangeForm(FormRevMixin, forms.ModelForm): class ResetPasswordForm(forms.Form): """Formulaire de demande de reinitialisation de mot de passe, mdp oublié""" - pseudo = forms.CharField(label=u'Pseudo', max_length=255) + pseudo = forms.CharField(label=_("Username"), max_length=255) email = forms.EmailField(max_length=255) @@ -294,8 +296,9 @@ class MassArchiveForm(forms.Form): date = cleaned_data.get("date") if date: if date > timezone.now(): - raise forms.ValidationError("Impossible d'archiver des\ - utilisateurs dont la fin d'accès se situe dans le futur !") + raise forms.ValidationError(_("Impossible to archive users" + " whose end access date is in" + " the future.")) class AdherentForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): @@ -305,20 +308,22 @@ class AdherentForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(AdherentForm, self).__init__(*args, prefix=prefix, **kwargs) - self.fields['name'].label = 'Prénom' - self.fields['surname'].label = 'Nom' - self.fields['email'].label = 'Adresse mail' - self.fields['school'].label = 'Établissement' - self.fields['comment'].label = 'Commentaire' - self.fields['room'].label = 'Chambre' - self.fields['room'].empty_label = "Pas de chambre" - self.fields['school'].empty_label = "Séléctionner un établissement" + self.fields['name'].label = _("First name") + self.fields['surname'].label = _("Surname") + self.fields['email'].label = _("Email address") + self.fields['school'].label = _("School") + self.fields['comment'].label = _("Comment") + self.fields['room'].label = _("Room") + self.fields['room'].empty_label = _("No room") + self.fields['school'].empty_label = _("Select a school") def clean_email(self): if not OptionalUser.objects.first().local_email_domain in self.cleaned_data.get('email'): return self.cleaned_data.get('email').lower() else: - raise forms.ValidationError("Vous ne pouvez pas utiliser une addresse {}".format(OptionalUser.objects.first().local_email_domain)) + raise forms.ValidationError( + _("You can't use a {} address.").format( + OptionalUser.objects.first().local_email_domain)) class Meta: model = Adherent @@ -342,7 +347,7 @@ class AdherentForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): telephone = self.cleaned_data['telephone'] if not telephone and OptionalUser.get_cached_value('is_tel_mandatory'): raise forms.ValidationError( - "Un numéro de téléphone valide est requis" + _("A valid telephone number is required.") ) return telephone @@ -353,7 +358,7 @@ class AdherentForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): return gpg_fingerprint.replace(' ', '').upper() force = forms.BooleanField( - label="Forcer le déménagement ?", + label=_("Force the move?"), initial=False, required=False ) @@ -373,13 +378,13 @@ class ClubForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(ClubForm, self).__init__(*args, prefix=prefix, **kwargs) - self.fields['surname'].label = 'Nom' - self.fields['school'].label = 'Établissement' - self.fields['comment'].label = 'Commentaire' - self.fields['room'].label = 'Local' - self.fields['room'].empty_label = "Pas de chambre" - self.fields['school'].empty_label = "Séléctionner un établissement" - self.fields['mailing'].label = 'Utiliser une mailing' + self.fields['surname'].label = _("Name") + self.fields['school'].label = _("School") + self.fields['comment'].label = _("Comment") + self.fields['room'].label = _("Room") + self.fields['room'].empty_label = _("No room") + self.fields['school'].empty_label = _("Select a school") + self.fields['mailing'].label = _("Use a mailing list") class Meta: model = Club @@ -400,7 +405,7 @@ class ClubForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): telephone = self.cleaned_data['telephone'] if not telephone and OptionalUser.get_cached_value('is_tel_mandatory'): raise forms.ValidationError( - "Un numéro de téléphone valide est requis" + _("A valid telephone number is required.") ) return telephone @@ -436,7 +441,7 @@ class PasswordForm(FormRevMixin, ModelForm): class ServiceUserForm(FormRevMixin, ModelForm): """ Modification d'un service user""" password = forms.CharField( - label=u'Nouveau mot de passe', + label=_("New password"), max_length=255, validators=[MinLengthValidator(8)], widget=forms.PasswordInput, @@ -493,7 +498,7 @@ class GroupForm(FieldPermissionFormMixin, FormRevMixin, ModelForm): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(GroupForm, self).__init__(*args, prefix=prefix, **kwargs) if 'is_superuser' in self.fields: - self.fields['is_superuser'].label = "Superuser" + self.fields['is_superuser'].label = _("Superuser") class SchoolForm(FormRevMixin, ModelForm): @@ -505,7 +510,7 @@ class SchoolForm(FormRevMixin, ModelForm): def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(SchoolForm, self).__init__(*args, prefix=prefix, **kwargs) - self.fields['name'].label = 'Établissement' + self.fields['name'].label = _("School") class ShellForm(FormRevMixin, ModelForm): @@ -517,7 +522,7 @@ class ShellForm(FormRevMixin, ModelForm): def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(ShellForm, self).__init__(*args, prefix=prefix, **kwargs) - self.fields['shell'].label = 'Nom du shell' + self.fields['shell'].label = _("Shell name") class ListRightForm(FormRevMixin, ModelForm): @@ -536,7 +541,7 @@ class ListRightForm(FormRevMixin, ModelForm): def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(ListRightForm, self).__init__(*args, prefix=prefix, **kwargs) - self.fields['unix_name'].label = 'Nom UNIX du groupe' + self.fields['unix_name'].label = _("Name of the group of rights") class NewListRightForm(ListRightForm): @@ -547,15 +552,15 @@ class NewListRightForm(ListRightForm): def __init__(self, *args, **kwargs): super(NewListRightForm, self).__init__(*args, **kwargs) - self.fields['gid'].label = ("Gid, attention, cet attribut ne doit " - "pas être modifié après création") + self.fields['gid'].label = _("GID. Warning: this field must not be" + " edited after creation.") class DelListRightForm(Form): """Suppression d'un ou plusieurs groupes""" listrights = forms.ModelMultipleChoiceField( queryset=ListRight.objects.none(), - label="Droits actuels", + label=_("Current groups of rights"), widget=forms.CheckboxSelectMultiple ) @@ -572,7 +577,7 @@ class DelSchoolForm(Form): """Suppression d'une ou plusieurs écoles""" schools = forms.ModelMultipleChoiceField( queryset=School.objects.none(), - label="Etablissements actuels", + label=_("Current schools"), widget=forms.CheckboxSelectMultiple ) @@ -590,7 +595,7 @@ class BanForm(FormRevMixin, ModelForm): def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(BanForm, self).__init__(*args, prefix=prefix, **kwargs) - self.fields['date_end'].label = 'Date de fin' + self.fields['date_end'].label = _("End date") self.fields['date_end'].localize = False class Meta: @@ -604,7 +609,7 @@ class WhitelistForm(FormRevMixin, ModelForm): def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(WhitelistForm, self).__init__(*args, prefix=prefix, **kwargs) - self.fields['date_end'].label = 'Date de fin' + self.fields['date_end'].label = _("End date") self.fields['date_end'].localize = False class Meta: @@ -618,8 +623,8 @@ class EMailAddressForm(FormRevMixin, ModelForm): def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(EMailAddressForm, self).__init__(*args, prefix=prefix, **kwargs) - self.fields['local_part'].label = "Local part of the email" - self.fields['local_part'].help_text = "Can't contain @" + self.fields['local_part'].label = _("Local part of the email address") + self.fields['local_part'].help_text = _("Can't contain @") def clean_local_part(self): return self.cleaned_data.get('local_part').lower() @@ -634,18 +639,21 @@ class EmailSettingsForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(EmailSettingsForm, self).__init__(*args, prefix=prefix, **kwargs) - self.fields['email'].label = "Main email address" + self.fields['email'].label = _("Main email address") if 'local_email_redirect' in self.fields: - self.fields['local_email_redirect'].label = "Redirect local emails" + self.fields['local_email_redirect'].label = _("Redirect local emails") if 'local_email_enabled' in self.fields: - self.fields['local_email_enabled'].label = "Use local emails" + self.fields['local_email_enabled'].label = _("Use local emails") def clean_email(self): if not OptionalUser.objects.first().local_email_domain in self.cleaned_data.get('email'): return self.cleaned_data.get('email').lower() else: - raise forms.ValidationError("Vous ne pouvez pas utiliser une addresse {}".format(OptionalUser.objects.first().local_email_domain)) + raise forms.ValidationError( + _("You can't use a {} address.").format( + OptionalUser.objects.first().local_email_domain)) class Meta: model = User fields = ['email','local_email_enabled', 'local_email_redirect'] + diff --git a/users/locale/fr/LC_MESSAGES/django.mo b/users/locale/fr/LC_MESSAGES/django.mo index 9808ced65ede1f1f5db7688f356b2ff90c3145c7..ef4052cee9d92180376084d26911a13ad70e05f8 100644 GIT binary patch literal 25257 zcmcJX37lM2o$qfTKnSvh#jszL(A^~62|*wvB&5?>XiZ}5?hpha+*I987u3?!t?Hy% zX6Er^929g$T!3L5m61spTtSp^8|?RBR9pvCeCVj7^9J51=r9U`&-wn&Id`k7PQvH$ z>ci>pUCuqr|NPJY{Ld18G-u{}J$`4+@Vpb@{ReyA?Od-vN>4rS6UTYpvG9}dF!+z~ zQ20HlC_jgzAA!=Bk3p650Vq9w1WGS{09B9YVIQ1*x^4gSpuSrPRjv!6-Vei>@CGP3 zz6~A#?}BRI-BA7a0jPN2f(OFKq1y3DsPvw8{7)!-l`&F&$3mrZGSv6;-Tfk{@0LTg z4&((C1=z8`@H!ONiJ7DCD8MyUGU1rLPZ zfy(CzcmE8O9Df5Pzk_?M98Q3We=1b@yB&L>+HtXa{|2ahhur-oQ0*H)<#RQZyxs;S z-}ge*|1Y7^zuVn^)V=>KRQxYORL*-89t@v_%I9}b?Z4j{j1BN8sPaDvC9h|o`svL1 zo_7J93-$bB_!O)_>D`tER!-NzRoveJ-voaM)gKr3dY%tALdmlR)vwV3>2o8D%q_!W3AyaB5H{vN6v{{&U8U&3KH>rC5DZ-J7_HfZb( zB#QTWcqse@l-ys0N5I1tTRk}i9?AV_Q1w3tB69B{I1AnYQC;sHQ2KBmlw2Q#bKtk3 z>iILMbZ4A}ZoxSa5qlxzKkrulDBdIRVE8jQ8~zgR2Va0A@Fl44-}E}qdn5cCsQ&&O zls!D+Y|mQ@*F#jtyVdcFa1HlQLFK#f96JtQ25Bxjq6F|JP9c;n4G~o-BZBzZFpRyb!8CT@01pbx{57 zPN@Fy5L^lW8A_f$f0WPVQ1yQYRQ+y+`u=vP@;wMofZv8E!e2niarO#Z-jkuqwFIi1 zTi~&<4#~>92}*w-hAQtj;p^cOQ2Cv9ftB+aP;x#OZiH)~^yfWL@xSbN=t`?67sGix zzZsqcKLt;L--43EvrzRvy5F|TdGK`ZFNON<26!^u1=U`UK$ZJxsCv&>W#xJtRD1V8 z>DeOp{A{T5uYuBoH^4(+0F_P!s=jY^d?!@8x52~UC*Y~@OHk$i2~;|NfYO6w2CSSH zLVY&?mENUL`Wr&!ca7tpLdosjQ1bo>lwLj!)o%N*w)yx_?X&_)UT=1sglgy8;cMWh zq3qH3q009(RKBy;*#2}9R6YBk`pFu&4h})(cZcI=pwfE`s=U92^Wgq#t$cf+(mfAK z9vh+FkHd4}`=Ipm8;;+FD*ull|9LO)M|!i8%ujk`qBHK!8X%24(lIfd|8T;cWPTyMG8`s=Tkk{o%|v*mP#W z1G)F1WS zz79v=Gf?`y`c3?qf>+FI$JKA9W+7DZG!vOW})9?Y?E$+P~|d;@<^*_!X%4Ps1*F zIGLOd&xO74a;SE?8A|Uz4Hv;Dpbuwnw%^T%7jr)hRo;7{;(r6G-F^*~?=ctI^p-*8 zSA@#%X7~I)_xwqydd{SCs-K<=CFe<~_INKm4}Kad{b!)+F^59v{#;1ay-T3v|53OK zJ_u#cUV;Pg_%~ZUz8osuGCU664E5dJQ0@LH=)-S9hA!_}D1BN;;rie?@KrE`KCDBP z_g2^gKMv=@Cm^EsehrtwwKS5_y8%iMAA*O&A3Ht^_v7As3u`lAf4B{9hgZO_!ufFi zWwzb7KvcxL3+nlEa53~RxAGW<=Wu^FlnVV2E`iumHlj407E`!HkVaMHz zpz8B>NSF2Qhl)QVu>EielpKcPq3{Yg6IP-6Lj;e2lkiCRcE>y6QQUtF%AR}~s{cI- z4};%>lFPqB#rq9B8qP)-bKogZ&lkf};9B?^I12UMO^&z28Qkl4guA%S-kQG+{=SZ& zwEvv&KEi(^e4lUv;ZF$*38;_PPtfljgkwnSP?w(a`z+x}!frzRQ++<*ZltT2s^s^t zTzxmWv$i1M{#B$YdF!Y4xRDm{ypk0e0Y<)R=?NpYJzm}^Sa@8KH(U`gl^pLNYc}<2X+&#=lTZ1 zfn1-c7yLd!SY@v4SiXkq4aYp^t*y^6yLoYzMb&D36B%>Q{Q?u@9u^N5pE=WgrMK?g#RR*!TS;T zTBzSU2^#D4Q(v3wo}a|^`w6FT-$%eic`pz?M2LUaxr={~AHbRJ`)j%0pYSchsXV(C z{+6KMzgc*H1OJS$nDA4=r3C%{58(;ITXo}pOW{?79}zxBxQ}oKLBIb=_zdA#LN8%H z;d_KZ!Xf1S8~6jl9}~_c{EBce&;JOWWeHQ{Rn z{YEW}zg6b?gM^ZMb_D!A;XuN3?%AnuIpIooKb!R5!1W&pA0<@WbKzr|>-og1alaq@ zBI1m0M_uB|_3CD6j4iAHK2uBmLzel)vituH^e|IlVhFb_PrHo$ybDVZyHo`hAM9AEB3Yy5X~g3kU~szX0m@AmI?gk%YGqeoUAm z=y#rlx7|_LL^zS}J-@$L^aFo;z1AG}YomU>w0*1*#g8jNVXRaQKq)hj`xRI7UanqsNpGK|7Tqg35a)HS96>HP*`td_O^hS{7_X=36og|&7eCf98_%hjyD{2iuXl`G$bgOt@16PN1)6_W!V_3MXoBg=zS*1M5RnC zWqO_ZpH0~BMyidHnhW)=l%fb-=*fQ_sbSJjT3$9xUE}o3OXQ2CkS?num`4+(a0gkU zIDT#0l~g?Yv_*+J#OR<<9b#E^>hvOGygI$M6>KY)X|wj?Cd!#fsw?e~E4_jCrp!Kz zBrjsl_CF&Fr*%9CMG=V5iURIi9m zQ+3(E8pK`E*=DDp=(Ne*s;j+|)@HNxlDOz^Ishb3B_eO2S+7&7cH$cZ{yF-E|2eOPf;br;HcI=I{CSw4Y}RMh->4sQ#i0N|~Brjo2ET(kDHL z&&G1n94n2-PqG6DPD%V&CC4bmX>qAU>h)TEsXty01I#Mvn?8i;GVMjNcQU?K(-F>! z)yFi~cE}tzjm+ZjCTq<;Z*8g0m@R)pm+@83PF~=x8(iJzZwcyE5?M-nFiek@!gA3L z_|;m&--a5XZqn;f%HS8&cj-8NUWrM`m22kz8t~CFOmf zNs|0%v(c=FecoU$4b{-R&Ke^6yfMcbuWL>FzRQl8TM_(Ly^Ne+MS@ke5RQSZg*O=4 zw)55pGy@h9nc3@dZF|XGMq$VmPKWet{p$Wrxjr2wuWW}?BsuBN>CTH_An0rkz%7 z40krXjWrnrfp_sZ%3Y~CQDXw67qJtoOKY|%I<+g{eMX8$&wgTV z$cS;vTt;|vp|fbRcbK6~&Y?{%@}-SmlQDw=k=i{~Z&Tva5i6Z*WPDBI-1Y5CM0qXK zMojvw^qaog+DE4SFz+i8tsYL4YE5ciuZ=SCi>U^l*X<^3^SYxZ23V6fHur`{h6V>l z)?l{q3edN52`AgMXpc7(F0T2-S{POF(5|Z1cHr+ zd}vTGpKFR1G`VFKTCj4hF+nj`QJ+89uya^Vs2E;obUwBww#5~*6TEJ~pLn$?Y#hjwVT5#?I@EEH%m>TLf4vUgq_ zrQO4{RT-J@^>HrIPVT%UA-UzuKBh-&6FU&QhF|lp%`5Q-M}52#e@Cr}lTj{RW%>f` zqo>SXJG(h*8u{1@%(n&Ai7^>x#0@C+!>h{JM3mr>M zYqyLhYEOi1eIKiHOf}>sYRs~0V@%6)&Wu$QzvK$tnxa}bqh6g}=TxmzglwhFd$%x^ zeV!FIeNNtaRyD0er;C&;!E}-Kk{i`TALYX)o{QAnre?esv#O;C@yNf@ma1a?7W5M_ z$EG~o$(?;)%?$1l5oGBrL8CBcUnd%5dZL+`yKWo5>F|-Yx355ZyM$VrIrB6p+syN< zII~mB_Eu|qn@=(`IGAs937dtF*1{zZ^9d!7b17&jX-iio{{O}JneRI^wRx`MW{aSQ z8VUWbdEMn;TUhR49^iC{2~@4Vt%UtH3v9{Cs8la;&P?$$oiP6*CxrZ~OexT9EJM{< zLJc>W6;L-ntZGcdTJFiLl8EYq>ehcjv@OV8`pVpk*HhOjGNes}zwmhQPAP11bQlyHJFGpXVvJ``3Jr-tb)CBrr8 z+xOUsCi6{L#*fnE!-|26)VX%=-@L0bXVfwTK5y&+krY~yajkOS+Of;1ndIV@Gg(wIyLO#qfkFignex6Q zMO%dFl1cny`Ih?_b?>Rh&7Ua-^S=&-HR74BY}szH_dKn3>@~+d*Rd^;eTZ)Q;NHb& zcRMoW+jG(;I^CD04bzl(*Hxn@PN$|5n)yzeVq7C;Zj#)-eM2{7!;38|Go#vv;=P=j zC$?p}3b}R|JnK}mWe^3vJ~P>4LWiun_HEl#q%&n|Ly$j9sO zEb`o1ja%jMtbgVZc}P3m63O21%FM#e-^f$5_}w-UO@dU^9`lsFXP0*ToITb=e17+6 zJv2*OEZEf99JI6LObouCCJM_Z+Ggt=EcRZ6Qn22+)L-0p_Uxf>yjE}YZiv|C?p@W~ z9`%mYmin_dy>X;>fM(IcXzyx#I$|$6b4l-_CB0`Z@y|SK=~?H@U$l79A|mzA7=sXcF&I{HTI zD`u}}i>JxPL+?l!R47_}zmyI2t!sy7Zy4OLCau$%eT!xfFfC@Gpmzklr zi<-;KZa8Lp`R0+ey-U(~Di%5SuBjGk+CW|EFWFXV%-&RPvcTB8mU9NtQolNGZlmRk zm$4O~H_N-L{tExho@KK+0dXa(&@#JK>qUi(?0&IcV|zFIQrjl+sfzTAsof2BVAyO8 z&2C;cu_n9j+U^URI%bi4f_tg4WHexrFq(`SQ@dFB%)JlHAY`^_IFo?`!(KKErYayq zH5G_Tr0G`XLgqYaai@=*FDNw|Oiy8ugkCeNp4uJikOiV-yP}TEh>OZ8gv!*e1_Pt< z5R)kH9%&88N zruJr4mzrb`ag3U~QNR1jW^{G4D`?`-u?Uns#gXWhyhgT{og$|C)~?ix zHQ8SJGFABWnM9n`_w-FiwN78l;7#q4&ISdvcV{l2LapKU#U(0J7}GGcVyViRhJb!# zm8!y`0wUCWUoM+fgOK(7$*JA(A(5%w?vMzUhrQ7 zZrjf_f|?^E=#ou;lKh%9IxSpfg+lvs!?kkJ927~VyecDd?O3AprK_7t8@F4>N9a%N z!Z#BY;|NLF@5-pRA9b-%MM+gaR7WRCOjTJn57`{E>-*}7q>-t6QPs$#V`C?p++f|^ z)Grf-jTfztqQxkFfi^(lq`KNoUt3Bo_3o0!QNE7#Wl4b5UI_;SMhDYYm$Ad93heS&qp7J`zI$|dZ|j&= z(Uv2hMtgVZkWfn(=@28+C`?6+=g}#*c42jjl}W7A>(+j^k=~>Br3)hXO<=<8kTp&{QEd*z zl-PCAh%%~*?tn*;x&6c3zDQW>cH%uB^{TpI{5QG>#u)-2vjr#ety?Pb0M=~&O`s3l*z75c?t7PK-m;Q8@M8Z!i%a~AB;npl)}3{_R9O}>gpa0+T4 zHgoEgq=wFs4CiuiVR<+aHgP){pIy(Urs$tLos;YRc1=ec4-9Qu|Kv1*9l87oJxUVL z&ND*+d$ci`26P-Ie0NZ%pK}T9PSP!xGB9e)t&Fn*%nk%To($#GJ#;2FB+BokTjI+U z$i_{&*JOP|*)JBoNH3B(yPsy6FaL}dWJ>F`WkdALMCv!-|9;aoek8)-DB^k)QMrpOZ>{KJK7V=eb8RTBr zQ$Ky#LqC+HlDN~^-eJp_G07|(+C+?r^4m`UN%xJs&Q%!FTSsT^Bt>b8wd~KOo zBhBQ2QCE3vnMmog^*YMZUTZfBn;mO&qQ}N@7C>dUhBWgoZ|LlqBKuYTjh$nt{d01b z^3YcH{E+?5jQ30nnFPi2!U5x-Ta#fNbUf$vM6jkH_gvaKp|hJ-S=~57229r)CAZ9~ zj9RP>Cw|PdR{ksAjv^QX)p9EjD;b<}9W%979@=haf799H{#fr=Kd8uw=fsRGH8=Ly zk;&bbhwVjs^ax2&WwcteFSJ6&N2gTND~`M;7HPFQZ4@@$Bu-|*Owpw2+5%x%VEbXL zG|F-dlgXyfY%y#gCrYA_&Kon${`vr!=s-|&B4iepb;V=9#aIG;jDc#8G~6srV6Aij zNXh0#UiU0jQulx*0v)Q0zloP0`kMvg-6S=RzsGDLN*mpoiOhUUUr_-W*I0hKGbQmh zY3f}J$2BLmQ%v=L40YDiX{%{Y3`3d8S|W>F49+zfgO?d(@L1!c>TUvzd#X8kGVkdy zWzY;|Z(peoqASxyOl=f8(ALYbg4BmuDq%v5F|Xnx;27jhs~3-_@+D#uo*bsiywtxUvC}LwmgLvoOrKYS8~dAo zmqgFl;Op^yCW_4&~lMKurIy}a$F$igK z#Z=TdL)M*%8zNH-pZ(T?+ONb~44&3#NuC?NI*+m@g3&N<8PKBOAO49T`oE#9s_T(S-@&*ey4vC|Y2H94)hiAC%~TN4|Wg9Y|e?kD&(($JZV z^PQ70-C2_!Tx6P=JA(VooS=V!i z#7LlwXyOHXQ}4F4WA(^t^t6%kT0L#FSTt5LHd=O}JbrIP=bT5QQf{9vE|cv9rrd3l z&7`N~%QStwI8$4wP9CcUGBwyZPLa_i>k#W$XVzuS&cqUTjw5C3IaT1?c}^^jj2fcA$5;BVg z>E6&Qr&O&8$E}xY)O0bjO*dVfOtWQOpm+pL)F9E7J;uuAnCUld$z`)`ABdA&T^(5% zS>Iv2PNF5*?k!@Rt%+xx#w3mSm9ol~9ODp6{1q59a!I%7Zth*2XQo)_IOBM+)^=X5 zBz2Y<&TdK>sj@C1C2yS`BsI$DknVfNZm=i}95hFWL-P`d&q`0rw(3on zqSTr4?CIOKUdi-`&sWjqQA@T4I!-P7Tb3AG?>Po|krq+a;MNz^nCyDGQPmD!L%dX& zAFZW|IE0m{dpH-Uy$W2SxHUTDVa?h9*DTEXyP5n>)bc%w?S33Px17RqP`uL=+n;Jz zY;|co7RgHt6CLVc+u8N#*uq_V{l3RFrrFBA)o*u2iPP$$w_amxDPlDpR<<)e1kmn| zS(OX49h6nbJtc@^#Nvf(6Xr?#8aJcwd;TO+v9#H_H3) zElq`^o;$v1yH_VQXBkWz`ui>$RkF=o?x~ulZ?Sl$b!OQxvj}IF%&)!7sj0OeZYf?h zRJU>(9NP-Jq?#44m%V8};Apb2qpuhH8p&kTYwI8GNvE8eZhrq`VmLu>5PX3j~bZV9vUB!XFmR}B4MY4$Qu{TF3iJ-z?{ delta 669 zcmYk&&1(}u7{~GHYm8N^Ew&n87^(3EQ;9tkTU44Bu^3wl_F@rb%s$Z2WZUlS20Znu zM;XM62!bF*1TR87i5C9?Pa=5rq6d%Sp?+t(hy$~q*=L{K`R#0e>iF8X@ok{{v0@xx z9%a5@4l_#~To?~<1Rr7!U*inE#XkIvdF<&_su%llHx6MBp1^~68i(*Q`u;_nR%%1t z$sRn%T|8)`5AXp8@Cz=n$q)2Fu66GOS;2YkE$+vUIEbIoZ}2_y7y4j-(D!wc>JScM zp8Zvc#WaO+JcjqtzwiV#KEosU9?7A|k)8ykgYTxN=TCzcwJ)vioWdT~{-^r0nx~8D zgZQ!6zvR>U4PCoX%vEY}eKoXhU0)r&VQj2TT2SfQiiB!Tn)f8iu*>x|H7_lR-1e!% z$75~lVNG=LjD*46z{GOas+-B0M2V%vT)3UX&@{!h$4c($#B6?ibnK!omnX~a=fu>= zoY{KS&{c_A(r6fcDUjAw(@0|_tDKj_El$34?+PdXMSV20;NHv@&sBout<6ZnSfwk= pf2ixGY3f#0l3EgqHon8x-FCH*|36pNb=?~7U9ge_t8RVi%3q{re^dYf diff --git a/users/locale/fr/LC_MESSAGES/django.po b/users/locale/fr/LC_MESSAGES/django.po index 2814cb83..6c637db9 100644 --- a/users/locale/fr/LC_MESSAGES/django.po +++ b/users/locale/fr/LC_MESSAGES/django.po @@ -21,9 +21,9 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-06-28 00:06+0200\n" +"POT-Creation-Date: 2018-08-18 13:36+0200\n" "PO-Revision-Date: 2018-06-27 23:35+0200\n" -"Last-Translator: Maël Kervella \n" +"Last-Translator: Laouen Fernet \n" "Language-Team: \n" "Language: fr_FR\n" "MIME-Version: 1.0\n" @@ -31,93 +31,1343 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: templates/users/aff_listright.html:37 -msgid "Superuser" -msgstr "Superuser" +#: acl.py:41 +msgid "You don't have the right to view this application." +msgstr "Vous n'avez pas le droit de voir cette application." -#: templates/users/aff_listright.html:39 +#: forms.py:70 +msgid "Current password" +msgstr "Mot de passe actuel" + +#: forms.py:75 forms.py:444 +msgid "New password" +msgstr "Nouveau mot de passe" + +#: forms.py:81 +msgid "New password confirmation" +msgstr "Confirmation du nouveau mot de passe" + +#: forms.py:98 +msgid "The new passwords don't match." +msgstr "Les nouveaux mots de passe ne correspondent pas." + +#: forms.py:107 +msgid "The current password is incorrect." +msgstr "Le mot de passe actuel est incorrect." + +#: forms.py:125 forms.py:178 models.py:1578 +msgid "Password" +msgstr "Mot de passe" + +#: forms.py:131 forms.py:184 +msgid "Password confirmation" +msgstr "Confirmation du mot de passe" + +#: forms.py:136 forms.py:227 +msgid "Is admin" +msgstr "Est admin" + +#: forms.py:146 +msgid "You can't use an internal address as your external address." +msgstr "" +"Vous ne pouvez pas utiliser une adresse interne pour votre adresse externe." + +#: forms.py:159 forms.py:208 +msgid "The passwords don't match." +msgstr "Les mots de passe ne correspondent pas." + +#: forms.py:236 +#, python-format +msgid "User is admin: %s" +msgstr "L'utilisateur est admin : %s" + +#: forms.py:284 templates/users/aff_clubs.html:38 +#: templates/users/aff_listright.html:63 templates/users/aff_listright.html:168 +#: templates/users/aff_users.html:39 templates/users/profil.html:165 +#: templates/users/profil.html:279 templates/users/profil.html:298 +msgid "Username" +msgstr "Pseudo" + +#: forms.py:299 +msgid "Impossible to archive users whose end access date is in the future." +msgstr "" +"Impossible d'archiver des utilisateurs dont la date de fin de connexion est " +"dans le futur." + +#: forms.py:311 templates/users/profil.html:278 templates/users/profil.html:297 +msgid "First name" +msgstr "Prénom" + +#: forms.py:312 templates/users/aff_users.html:37 +#: templates/users/profil.html:161 templates/users/profil.html:277 +#: templates/users/profil.html:296 +msgid "Surname" +msgstr "Nom" + +#: forms.py:313 models.py:1579 templates/users/aff_emailaddress.html:36 +#: templates/users/profil.html:167 +msgid "Email address" +msgstr "Adresse mail" + +#: forms.py:314 forms.py:382 forms.py:513 templates/users/aff_schools.html:37 +#: templates/users/profil.html:177 +msgid "School" +msgstr "Établissement" + +#: forms.py:315 forms.py:383 models.py:1232 +#: templates/users/aff_serviceusers.html:34 templates/users/profil.html:179 +msgid "Comment" +msgstr "Commentaire" + +#: forms.py:316 forms.py:384 templates/users/aff_clubs.html:40 +#: templates/users/aff_users.html:41 templates/users/profil.html:171 +msgid "Room" +msgstr "Chambre" + +#: forms.py:317 forms.py:385 +msgid "No room" +msgstr "Pas de chambre" + +#: forms.py:318 forms.py:386 +msgid "Select a school" +msgstr "Sélectionnez un établissement" + +#: forms.py:325 forms.py:653 +msgid "You can't use a {} address." +msgstr "Vous ne pouvez pas utiliser une adresse {}." + +#: forms.py:350 forms.py:408 +msgid "A valid telephone number is required." +msgstr "Un numéro de téléphone valide est requis." + +#: forms.py:361 +msgid "Force the move?" +msgstr "Forcer le déménagement ?" + +#: forms.py:381 templates/users/aff_clubs.html:36 +#: templates/users/aff_serviceusers.html:32 +msgid "Name" +msgstr "Nom" + +#: forms.py:387 +msgid "Use a mailing list" +msgstr "Utiliser une liste de diffusion" + +#: forms.py:501 templates/users/aff_listright.html:38 +msgid "Superuser" +msgstr "Superutilisateur" + +#: forms.py:525 +msgid "Shell name" +msgstr "Nom de l'interface système" + +#: forms.py:544 +msgid "Name of the group of rights" +msgstr "Nom du groupe de droits" + +#: forms.py:555 +msgid "GID. Warning: this field must not be edited after creation." +msgstr "GID. Attention : ce champ ne doit pas être modifié après création." + +#: forms.py:563 +msgid "Current groups of rights" +msgstr "Groupes de droits actuels" + +#: forms.py:580 +msgid "Current schools" +msgstr "Établissements actuels" + +#: forms.py:598 forms.py:612 templates/users/aff_bans.html:41 +#: templates/users/aff_whitelists.html:41 +msgid "End date" +msgstr "Date de fin" + +#: forms.py:626 models.py:1776 +msgid "Local part of the email address" +msgstr "Partie locale de l'adresse mail" + +#: forms.py:627 +msgid "Can't contain @" +msgstr "Ne peut pas contenir @" + +#: forms.py:642 +msgid "Main email address" +msgstr "Adresse mail principale" + +#: forms.py:644 +msgid "Redirect local emails" +msgstr "Rediriger les mails locaux" + +#: forms.py:646 +msgid "Use local emails" +msgstr "Utiliser les mails locaux" + +#: models.py:105 +#, python-format +msgid "The username '%(label)s' contains forbidden characters." +msgstr "Le pseudo '%(label)s' contient des caractères interdits." + +#: models.py:147 +msgid "Users must have an username." +msgstr "Les utilisateurs doivent avoir un pseudo." + +#: models.py:150 +msgid "Username should only contain [a-z0-9-]." +msgstr "Le pseudo devrait seulement contenir [a-z0-9-]" + +#: models.py:199 models.py:1223 +msgid "Must only contain letters, numerals or dashes." +msgstr "Doit seulement contenir des lettres, chiffres ou tirets." + +#: models.py:205 +msgid "External email address allowing us to contact you." +msgstr "Adresse mail externe nous permettant de vous contacter." + +#: models.py:209 +msgid "" +"Enable redirection of the local email messages to the main email address." +msgstr "" +"Activer la redirection des mails locaux vers l'adresse mail principale." + +#: models.py:214 +msgid "Enable the local email account." +msgstr "Activer le compte mail local" + +#: models.py:229 +msgid "Comment, school year" +msgstr "Commentaire, promotion" + +#: models.py:255 models.py:1033 models.py:1106 +msgid "Can change the password of a user" +msgstr "Peut changer le mot de passe d'un utilisateur" + +#: models.py:256 models.py:1034 models.py:1107 +msgid "Can edit the state of a user" +msgstr "Peut changer l'état d'un utilisateur" + +#: models.py:257 models.py:1035 models.py:1108 +msgid "Can force the move" +msgstr "Peut forcer le déménagement" + +#: models.py:258 models.py:1036 models.py:1109 +msgid "Can edit the shell of a user" +msgstr "Peut modifier l'interface système d'un utilisateur" + +#: models.py:260 models.py:1038 models.py:1111 +msgid "Can edit the groups of rights of a user (critical permission)" +msgstr "" +"Peut modifier les groupes de droits d'un utilisateur (permission critique)" + +#: models.py:263 models.py:1041 models.py:1114 +msgid "Can edit all users, including those with rights." +msgstr "" +"Peut modifier tous les utilisateurs, y compris ceux possédant des droits." + +#: models.py:265 models.py:1043 models.py:1116 +msgid "Can view a user object" +msgstr "Peut voir un objet utilisateur" + +#: models.py:267 +msgid "user (member or club)" +msgstr "utilisateur (adhérent ou club)" + +#: models.py:268 +msgid "users (members or clubs)" +msgstr "utilisateurs (adhérents ou clubs)" + +#: models.py:286 models.py:310 +msgid "Unknown type." +msgstr "Type inconnu." + +#: models.py:306 templates/users/aff_listright.html:75 +#: templates/users/aff_listright.html:180 +msgid "Member" +msgstr "Adhérent" + +#: models.py:308 +msgid "Club" +msgstr "Club" + +#: models.py:521 +msgid "IPv4 assigning" +msgstr "Attribution de l'IPv4" + +#: models.py:530 +msgid "IPv4 unassigning" +msgstr "Désattribution de l'IPv4" + +#: models.py:677 +msgid "Maximum number of registered machines reached." +msgstr "Nombre maximum de machines enregistrées atteint." + +#: models.py:679 +msgid "Re2o doesn't know wich machine type to assign." +msgstr "Re2o ne sait pas quel type de machine attribuer." + +#: models.py:775 models.py:812 +msgid "You don't have the right to edit this club." +msgstr "Vous n'avez pas le droit de modifier ce club." + +#: models.py:783 +msgid "User with critical rights, can't be edited." +msgstr "Utilisateur avec des droits critiques, ne peut être modifié." + +#: models.py:786 +msgid "" +"Impossible to edit the organisation's user without the 'change_all_users' " +"right." +msgstr "" +"Impossible de modifier l'utilisateur de l'association sans le droit " +"'change_all_users'." + +#: models.py:794 models.py:823 +msgid "You don't have the right to edit another user." +msgstr "Vous n'avez pas le droit de modifier un autre utilisateur." + +#: models.py:842 +msgid "Permission required to change the state." +msgstr "Permission requise pour changer l'état." + +#: models.py:854 +msgid "Permission required to change the shell." +msgstr "Permission requise pour changer l'interface système." + +#: models.py:868 models.py:881 +msgid "Local email accounts must be enabled." +msgstr "Les comptes mail locaux doivent être activés." + +#: models.py:894 +msgid "Permission required to force the move." +msgstr "Permission requise pour forcer le déménagement." + +#: models.py:907 +msgid "Permission required to edit the user's groups of rights." +msgstr "" +"Permission requise pour modifier les groupes de droits de l'utilisateur." + +#: models.py:919 +msgid "'superuser' right required to edit the superuser flag." +msgstr "Droit 'superuser' requis pour modifier le signalement superuser." + +#: models.py:937 +msgid "You don't have the right to view this club." +msgstr "Vous n'avez pas le droit de voir ce club." + +#: models.py:943 +msgid "You don't have the right to view another user." +msgstr "Vous n'avez pas le droit de voir un autre utilisateur." + +#: models.py:956 models.py:1155 +msgid "You don't have the right to view the list of users." +msgstr "Vous n'avez pas le droit de voir la liste des utilisateurs." + +#: models.py:969 +msgid "You don't have the right to delete this user." +msgstr "Vous n'avez pas le droit de supprimer cet utilisateur." + +#: models.py:993 +msgid "" +"There is neither a local email address nor an external email address for " +"this user." +msgstr "" +"Il n'y a pas d'adresse mail locale ni d'adresse mail externe pour cet " +"utilisateur." + +#: models.py:1000 +msgid "" +"You can't redirect your local emails if no external email address has been " +"set." +msgstr "" +"Vous ne pouvez pas rediriger vos mails locaux si aucune adresse mail externe " +"n'a été définie." + +#: models.py:1025 +msgid "A GPG fingerprint must contain 40 hexadecimal characters." +msgstr "Une empreinte GPG doit contenir 40 caractères hexadécimaux." + +#: models.py:1045 +msgid "member" +msgstr "adhérent" + +#: models.py:1046 +msgid "members" +msgstr "adhérents" + +#: models.py:1075 +msgid "You don't have the right to create a user." +msgstr "Vous n'avez pas le droit de créer un utilisateur." + +#: models.py:1118 +msgid "club" +msgstr "club" + +#: models.py:1119 +msgid "clubs" +msgstr "clubs" + +#: models.py:1137 +msgid "You don't have the right to create a club." +msgstr "Vous n'avez pas le droit de créer un club." + +#: models.py:1242 +msgid "Can view a service user object" +msgstr "Peut voir un objet utilisateur service" + +#: models.py:1244 +msgid "service user" +msgstr "utilisateur service" + +#: models.py:1245 +msgid "service users" +msgstr "utilisateurs service" + +#: models.py:1249 +#, python-brace-format +msgid "Service user <{name}>" +msgstr "Utilisateur service <{name}>" + +#: models.py:1311 +msgid "Can view a school object" +msgstr "Peut voir un objet établissement" + +#: models.py:1313 +msgid "school" +msgstr "établissement" + +#: models.py:1314 +msgid "schools" +msgstr "établissements" + +#: models.py:1332 +msgid "UNIX groups can only contain lower case letters." +msgstr "Les groupes UNIX peuvent seulement contenir des lettres minuscules." + +#: models.py:1338 +msgid "Description" +msgstr "Description" + +#: models.py:1345 +msgid "Can view a group of rights object" +msgstr "Peut voir un objet groupe de droits" + +#: models.py:1347 +msgid "group of rights" +msgstr "groupe de droits" + +#: models.py:1348 +msgid "groups of rights" +msgstr "groupes de droits" + +#: models.py:1395 +msgid "Can view a shell object" +msgstr "Peut voir un objet interface système" + +#: models.py:1397 +msgid "shell" +msgstr "interface système" + +#: models.py:1398 +msgid "shells" +msgstr "interfaces système" + +#: models.py:1417 +msgid "HARD (no access)" +msgstr "HARD (pas d'accès)" + +#: models.py:1418 +msgid "SOFT (local access only)" +msgstr "SOFT (accès local uniquement)" + +#: models.py:1419 +msgid "RESTRICTED (speed limitation)" +msgstr "RESTRICTED (limitation de vitesse)" + +#: models.py:1430 +msgid "Can view a ban object" +msgstr "Peut voir un objet bannissement" + +#: models.py:1432 +msgid "ban" +msgstr "bannissement" + +#: models.py:1433 +msgid "bans" +msgstr "bannissements" + +#: models.py:1467 +msgid "You don't have the right to view bans other than yours." +msgstr "" +"Vous n'avez pas le droit de voir des bannissements autres que les vôtres." + +#: models.py:1515 +msgid "Can view a whitelist object" +msgstr "Peut voir un objet accès gracieux" + +#: models.py:1517 +msgid "whitelist (free of charge access)" +msgstr "Accès gracieux" + +#: models.py:1518 +msgid "whitelists (free of charge access)" +msgstr "Accès gracieux" + +#: models.py:1534 +msgid "You don't have the right to view whitelists other than yours." +msgstr "" +"Vous n'avez pas le droit de voir des accès gracieux autres que les vôtres." + +#: models.py:1771 +msgid "User of the local email account" +msgstr "Utilisateur du compte mail local" + +#: models.py:1781 +msgid "Can view a local email account object" +msgstr "Peut voir un objet compte mail local" + +#: models.py:1783 +msgid "local email account" +msgstr "compte mail local" + +#: models.py:1784 +msgid "local email accounts" +msgstr "comptes mail locaux" + +#: models.py:1808 models.py:1831 models.py:1853 models.py:1875 +msgid "The local email accounts are not enabled." +msgstr "Les comptes mail locaux ne sont pas activés." + +#: models.py:1810 +msgid "You don't have the right to add a local email account to another user." +msgstr "" +"Vous n'avez pas le droit d'ajouter un compte mail local à un autre " +"utilisateur." + +#: models.py:1813 +msgid "You reached the limit of {} local email accounts." +msgstr "Vous avez atteint la limite de {] comptes mail locaux." + +#: models.py:1834 models.py:1878 +msgid "You don't have the right to edit another user's local email account." +msgstr "" +"Vous n'avez pas le droit de modifier le compte mail local d'un autre " +"utilisateur." + +#: models.py:1848 +msgid "" +"You can't delete a local email account whose local part is the same as the " +"username." +msgstr "" +"Vous ne pouvez pas supprimer un compte mail local dont la partie locale est " +"la même que le pseudo." + +#: models.py:1856 +msgid "You don't have the right to delete another user's local email account" +msgstr "" +"Vous n'avez pas le droit de supprimer le compte mail local d'un autre " +"utilisateur." + +#: models.py:1870 +msgid "" +"You can't edit a local email account whose local part is the same as the " +"username." +msgstr "" +"Vous ne pouvez pas modifier un compte mail local dont la partie locale est " +"la même que le pseudo." + +#: models.py:1884 +msgid "The local part must not contain @." +msgstr "La partie locale ne doit pas contenir @." + +#: templates/users/aff_bans.html:36 templates/users/aff_whitelists.html:36 +msgid "User" +msgstr "Utilisateur" + +#: templates/users/aff_bans.html:38 templates/users/aff_whitelists.html:38 +msgid "Reason" +msgstr "Raison" + +#: templates/users/aff_bans.html:39 templates/users/aff_whitelists.html:39 +msgid "Start date" +msgstr "Date de début" + +#: templates/users/aff_clubs.html:42 templates/users/aff_users.html:43 +msgid "End of subscription on" +msgstr "Fin de cotisation le" + +#: templates/users/aff_clubs.html:43 templates/users/aff_users.html:44 +#: templates/users/profil.html:218 +msgid "Internet access" +msgstr "Accès Internet" + +#: templates/users/aff_clubs.html:44 templates/users/aff_users.html:45 +#: templates/users/profil.html:32 +msgid "Profile" +msgstr "Profil" + +#: templates/users/aff_clubs.html:53 templates/users/aff_users.html:54 +#: templates/users/profil.html:193 +msgid "Not a member" +msgstr "Non adhérent" + +#: templates/users/aff_clubs.html:55 templates/users/aff_users.html:57 +#: templates/users/profil.html:210 +msgid "Active" +msgstr "Actif" + +#: templates/users/aff_clubs.html:57 templates/users/aff_users.html:59 +#: templates/users/profil.html:212 templates/users/profil.html:222 +msgid "Disabled" +msgstr "Désactivé" + +#: templates/users/aff_listright.html:40 msgid "Django's specific pre-defined right that supersed any other rights." msgstr "" "Droit prédéfini spécifique à Django qui outrepasse tous les autres droits." -#: templates/users/aff_listright.html:43 +#: templates/users/aff_listright.html:44 msgid "Total: All permissions" msgstr "Total: Toutes les permissions" -#: templates/users/aff_listright.html:55 +#: templates/users/aff_listright.html:56 msgid "Users in Superuser" msgstr "Utilisateurs dans Superuser" -#: templates/users/aff_listright.html:62 templates/users/aff_listright.html:167 -msgid "Username" -msgstr "Pseudo" - -#: templates/users/aff_listright.html:63 templates/users/aff_listright.html:168 +#: templates/users/aff_listright.html:64 templates/users/aff_listright.html:169 msgid "Membership" msgstr "Adhésion" -#: templates/users/aff_listright.html:64 templates/users/aff_listright.html:169 +#: templates/users/aff_listright.html:65 templates/users/aff_listright.html:170 msgid "Last seen" msgstr "Dernière connexion" -#: templates/users/aff_listright.html:65 templates/users/aff_listright.html:170 +#: templates/users/aff_listright.html:66 templates/users/aff_listright.html:171 msgid "Actions" msgstr "Actions" -#: templates/users/aff_listright.html:66 templates/users/aff_listright.html:171 +#: templates/users/aff_listright.html:67 templates/users/aff_listright.html:172 msgid "Last action" msgstr "Dernière action" -#: templates/users/aff_listright.html:74 templates/users/aff_listright.html:179 -msgid "Member" -msgstr "Adhérent" - -#: templates/users/aff_listright.html:76 templates/users/aff_listright.html:181 +#: templates/users/aff_listright.html:77 templates/users/aff_listright.html:182 msgid "No membership records" msgstr "Aucune adhésion" -#: templates/users/aff_listright.html:79 templates/users/aff_listright.html:184 +#: templates/users/aff_listright.html:80 templates/users/aff_listright.html:185 #, python-format msgid "Not since %(end_date)s" msgstr "Plus depuis %(end_date)s" -#: templates/users/aff_listright.html:87 templates/users/aff_listright.html:192 +#: templates/users/aff_listright.html:88 templates/users/aff_listright.html:193 msgid "Never" msgstr "Jamais" -#: templates/users/aff_listright.html:122 +#: templates/users/aff_listright.html:123 #, python-format msgid "%(right_name)s (gid: %(right_gid)s)" msgstr "%(right_name)s (gid: %(right_gid)s)" -#: templates/users/aff_listright.html:131 +#: templates/users/aff_listright.html:132 #, python-format msgid "Total: %(perm_count)s permission" msgid_plural "Total: %(perm_count)s permissions" msgstr[0] "Total: %(perm_count)s permission" msgstr[1] "Total: %(perm_count)s permissions" -#: templates/users/aff_listright.html:157 +#: templates/users/aff_listright.html:158 #, python-format msgid "Users in %(right_name)s" msgstr "Utilisateurs dans %(right_name)s" +#: templates/users/aff_serviceusers.html:33 +msgid "Role" +msgstr "Rôle" + +#: templates/users/aff_shell.html:32 templates/users/profil.html:248 +msgid "Shell" +msgstr "Interface système" + +#: templates/users/aff_users.html:35 templates/users/profil.html:158 +msgid "Firt name" +msgstr "Prénom" + +#: templates/users/delete.html:29 +msgid "Deletion of objects" +msgstr "Suppression d'objets" + +#: templates/users/delete.html:35 +#, python-format +msgid "" +"Warning: are you sure you want to delete this %(objet_name)s object " +"( %(objet)s )?" +msgstr "" +"Attention : voulez-vous vraiment supprimer cet objet %(objet_name)s " +"( %(objet)s ) ?" + +#: templates/users/delete.html:36 templates/users/mass_archive.html:36 +msgid "Confirm" +msgstr "Confirmer" + +#: templates/users/index.html:29 templates/users/index.html:32 +#: templates/users/index_ban.html:29 templates/users/index_clubs.html:29 +#: templates/users/index_emailaddress.html:29 +#: templates/users/index_listright.html:30 templates/users/index_rights.html:29 +#: templates/users/index_schools.html:30 +#: templates/users/index_serviceusers.html:30 +#: templates/users/index_shell.html:30 templates/users/index_whitelist.html:29 +#: templates/users/sidebar.html:52 templates/users/user.html:30 +msgid "Users" +msgstr "Utilisateurs" + +#: templates/users/index_ban.html:32 templates/users/profil.html:373 +#: templates/users/sidebar.html:58 +msgid "Bans" +msgstr "Bannissements" + +#: templates/users/index_clubs.html:32 +msgid "Clubs" +msgstr "Clubs" + +#: templates/users/index_emailaddress.html:32 +msgid "Local email accounts" +msgstr "Comptes mail locaux" + +#: templates/users/index_listright.html:33 +msgid "List of groups of rights" +msgstr "Liste des groupes de droits" + +#: templates/users/index_listright.html:35 +msgid " Add a group of rights" +msgstr " Ajouter un groupe de droits" + +#: templates/users/index_listright.html:37 +msgid " Delete one or several groups of rights" +msgstr " Supprimer un ou plusieurs groupes de droits" + +#: templates/users/index_rights.html:32 +msgid "Rights" +msgstr "Droits" + +#: templates/users/index_schools.html:33 +msgid "List of schools" +msgstr "Liste des établissements" + +#: templates/users/index_schools.html:34 +msgid "List of schools for created users." +msgstr "Liste des établissement pour les utilisateurs créés." + +#: templates/users/index_schools.html:36 +msgid " Add a school" +msgstr " Ajouter un établissement" + +#: templates/users/index_schools.html:38 +msgid " Delete one or several schools" +msgstr " Supprimer un ou plusieurs établissements" + +#: templates/users/index_serviceusers.html:33 +msgid "List of LDAP service users" +msgstr "Liste des utilisateurs service LDAP" + +#: templates/users/index_serviceusers.html:34 +msgid "" +"The LDAP service users are special users having access only to the LDAP for " +"authentication operations. It is recommended to create a service user with a " +"login and a password for any concerned service." +msgstr "" +"Les utilisateurs service sont des utilisateurs spéciaux ayant accès " +"seulement au LDAP pour des opérations d'authentification. Il est recommandé " +"de créer un service utilisateur avec un indentifiant et un mot de passe." + +#: templates/users/index_serviceusers.html:36 +msgid " Add a service user" +msgstr " Ajouter un utilisateur" + +#: templates/users/index_shell.html:33 +msgid "List of shells" +msgstr "Liste des interaces système" + +#: templates/users/index_shell.html:35 +msgid " Add a shell" +msgstr " Ajouter une interface système" + +#: templates/users/index_whitelist.html:32 templates/users/profil.html:398 +#: templates/users/sidebar.html:64 +msgid "Whitelists" +msgstr "Accès gracieux" + +#: templates/users/mass_archive.html:29 +msgid "Users to archive" +msgstr "Utilisateurs à archiver" + +#: templates/users/mass_archive.html:35 +msgid "Search" +msgstr "Rechercher" + +#: templates/users/mass_archive.html:39 +#, python-format +msgid "The following users will be archived (%(to_archive_list|length)s):" +msgstr "" +"Les utilisateus suivants vont être archivés (%(to_archive_list|length)s :" + +#: templates/users/profil.html:37 +#, python-format +msgid "Welcome %(name)s %(surname)s" +msgstr "Bienvenue %(name)s %(surname)s" + +#: templates/users/profil.html:39 +#, python-format +msgid "Profile of %(name)s %(surname)s" +msgstr "Profil de %(name)s %(surname)s" + +#: templates/users/profil.html:47 +msgid "Your account has been banned" +msgstr "Votre compte a été banni" + +#: templates/users/profil.html:49 +#, python-format +msgid "End of the ban: %(end_ban)s" +msgstr "Fin du bannissement : %(end_ban)s" + +#: templates/users/profil.html:54 +msgid "No connection" +msgstr "Pas de connexion" + +#: templates/users/profil.html:58 +msgid "Pay for a connection" +msgstr "Payer une connexion" + +#: templates/users/profil.html:61 +msgid "Ask for someone with the appropriate rights to pay for a connection." +msgstr "" +"Demandez à quelqu'un ayant les droits appropriés de payer une connexion." + +#: templates/users/profil.html:67 +msgid "Connection" +msgstr "Connexion" + +#: templates/users/profil.html:69 +#, python-format +msgid "End of connection: %(end_connection)s" +msgstr "Fin de connexion : %(end_connection)s" + +#: templates/users/profil.html:82 +msgid "Refill the balance" +msgstr "Recharger le solde" + +#: templates/users/profil.html:92 +msgid " Machines" +msgstr " Machines" + +#: templates/users/profil.html:96 templates/users/profil.html:105 +msgid " Add a machine" +msgstr " Ajouter une machine" + +#: templates/users/profil.html:102 templates/users/profil.html:333 +msgid "No machine" +msgstr "Pas de machine" + +#: templates/users/profil.html:118 +msgid " Detailed information" +msgstr " Informations détaillées" + +#: templates/users/profil.html:125 +msgid "Edit" +msgstr "Modifier" + +#: templates/users/profil.html:129 views.py:282 views.py:1079 +msgid "Change the password" +msgstr "Changer le mot de passe" + +#: templates/users/profil.html:134 +msgid "Change the state" +msgstr "Changer l'état" + +#: templates/users/profil.html:140 views.py:260 +msgid "Edit the groups" +msgstr "Modifier les groupes" + +#: templates/users/profil.html:151 +msgid "Mailing" +msgstr "Envoi de mails" + +#: templates/users/profil.html:155 +msgid "Mailing disabled" +msgstr "Envoi de mails désactivé" + +#: templates/users/profil.html:173 +msgid "Telephone number" +msgstr "Numéro de téléphone" + +#: templates/users/profil.html:183 +msgid "Registration date" +msgstr "Date d'inscription" + +#: templates/users/profil.html:185 +msgid "Last login" +msgstr "Dernière connexion" + +#: templates/users/profil.html:189 +msgid "End of membership" +msgstr "Fin d'adhésion" + +#: templates/users/profil.html:195 +msgid "Whitelist" +msgstr "Accès gracieux" + +#: templates/users/profil.html:199 templates/users/profil.html:228 +msgid "None" +msgstr "Aucun" + +#: templates/users/profil.html:202 +msgid "Ban" +msgstr "Bannissement" + +#: templates/users/profil.html:206 +msgid "Not banned" +msgstr "Non banni" + +#: templates/users/profil.html:208 +msgid "State" +msgstr "État" + +#: templates/users/profil.html:214 +msgid "Archived" +msgstr "Archivé" + +#: templates/users/profil.html:220 +#, python-format +msgid "Active (until %(end_access)s)" +msgstr "Actif (jusqu'au %(end_access)s)" + +#: templates/users/profil.html:224 templates/users/sidebar.html:82 +msgid "Groups of rights" +msgstr "Groupes de droits" + +#: templates/users/profil.html:232 +msgid "Balance" +msgstr "Solde" + +#: templates/users/profil.html:237 +msgid "Refill" +msgstr "Rechager" + +#: templates/users/profil.html:242 +msgid "GPG fingerprint" +msgstr "Empreinte GPG" + +#: templates/users/profil.html:261 +msgid " Manage the club" +msgstr " Gérer le club" + +#: templates/users/profil.html:268 +msgid "Manage the admins and members" +msgstr "Gérer les admins et les membres" + +#: templates/users/profil.html:272 +msgid "Club admins" +msgstr "Admins du clubs" + +#: templates/users/profil.html:291 +msgid "Members" +msgstr "Adhérents" + +#: templates/users/profil.html:318 +msgid "Machines" +msgstr "Machines" + +#: templates/users/profil.html:326 +msgid "Add a machine" +msgstr "Ajouter une machine" + +#: templates/users/profil.html:342 +msgid "Subscriptions" +msgstr "Cotisations" + +#: templates/users/profil.html:350 +msgid "Add as subscription" +msgstr "Ajouter une cotisation" + +#: templates/users/profil.html:355 +msgid "Edit the balance" +msgstr "Modifier le solde" + +#: templates/users/profil.html:364 +msgid "No invoice" +msgstr "Pas de facture" + +#: templates/users/profil.html:381 views.py:384 +msgid "Add a ban" +msgstr "Ajouter un bannissement" + +#: templates/users/profil.html:389 +msgid "No ban" +msgstr "Pas de bannissement" + +#: templates/users/profil.html:406 +msgid "Grant a whitelist" +msgstr "Donner un accès gracieux" + +#: templates/users/profil.html:414 +msgid "No whitelist" +msgstr "Pas d'accès gracieux" + +#: templates/users/profil.html:422 +msgid " Email settings" +msgstr " Paramètres mail" + +#: templates/users/profil.html:429 +msgid " Edit email settings" +msgstr " Modifier les paramètres mail" + +#: templates/users/profil.html:438 templates/users/profil.html:464 +msgid "Contact email address" +msgstr "Adresse mail de contact" + +#: templates/users/profil.html:442 +msgid "Enable the local email account" +msgstr "Activer le compte mail local" + +#: templates/users/profil.html:444 +msgid "Enable the local email redirection" +msgstr "Activer la redirection mail locale" + +#: templates/users/profil.html:448 +msgid "" +"The contact email address is the email address where we send emails to " +"contact you. If you would like to use your external email address for that, " +"you can either disable your local email address or enable the local email " +"redirection." +msgstr "" +"L'adresse mail de contact est l'adresse mail où nous envoyons des mails pour " +"vous contacter. Si vous voulez utiliser votre adresse mail externe pour " +"cela, vous pouvez soit désactiver votre adresse mail locale soit activer la " +"redirection des mails locaux." + +#: templates/users/profil.html:453 +msgid " Add an email address" +msgstr " Ajouter une adresse mail" + +#: templates/users/sidebar.html:33 +msgid "Create a club or organisation" +msgstr "Créer un club ou une association" + +#: templates/users/sidebar.html:39 views.py:134 +msgid "Create a user" +msgstr "Créer un utilisateur" + +#: templates/users/sidebar.html:46 +msgid "Clubs and organisations" +msgstr "Clubs et associations" + +#: templates/users/sidebar.html:70 +msgid "Schools" +msgstr "Établissements" + +#: templates/users/sidebar.html:76 +msgid "Shells" +msgstr "Interfaces système" + +#: templates/users/sidebar.html:88 +msgid "Service users" +msgstr "Utilisateurs service" + +#: templates/users/sidebar.html:94 +msgid "Massively archive" +msgstr "Archiver en masse" + +#: templates/users/user.html:45 +msgid "By clicking 'Create or edit', the user commits to respect the " +msgstr "" +"En cliquant sur 'Créer ou modifier', l 'utilisateur s'engage à respecter les" + +#: templates/users/user.html:45 +msgid "General Terms of Use" +msgstr "Conditions Générales d'Utilisation" + +#: templates/users/user.html:46 +msgid "Summary of the General Terms of Use" +msgstr "Résumé des Conditions Générales d'Utilisation" + +#: views.py:122 +#, python-format +msgid "The user %s was created, an email to set the password was sent." +msgstr "" +"L'utilisateur %s a été créé, un mail pour initialiser le mot de passe a été " +"envoyé." + +#: views.py:151 +#, python-format +msgid "The club %s was created, an email to set the password was sent." +msgstr "" +"Le club %s a été créé, un mail pour initialiser le mot de passe a été envoyé." + +#: views.py:158 +msgid "Create a club" +msgstr "Créer un club" + +#: views.py:176 +msgid "The club was edited." +msgstr "Le club a été modifié." + +#: views.py:185 +msgid "Edit the admins and members" +msgstr "Modifier les admins et les membres" + +#: views.py:213 +msgid "The user was edited." +msgstr "L'utilisateur a été modifié." + +#: views.py:219 +msgid "Edit the user" +msgstr "Modifier l'utilisateur" + +#: views.py:233 +msgid "The state was edited." +msgstr "L'état a été modifié." + +#: views.py:239 +msgid "Edit the state" +msgstr "Modifier l'état" + +#: views.py:254 +msgid "The groups were edited." +msgstr "Les groupes ont été modifiés." + +#: views.py:276 views.py:1076 +msgid "The password was changed." +msgstr "Le mot de passe a été changé." + +#: views.py:294 +#, python-format +msgid "%s was removed from the group." +msgstr "%s a été retiré du groupe." + +#: views.py:304 +#, python-format +msgid "%s is no longer superuser." +msgstr "%s n'est plus superutilisateur." + +#: views.py:317 +msgid "The service user was created." +msgstr "L'utilisateur service a été créé." + +#: views.py:321 +msgid "Create a service user" +msgstr "Créer un utilisateur service" + +#: views.py:338 +msgid "The service user was edited." +msgstr "L'utilisateur service a été modifié." + +#: views.py:341 +msgid "Edit a service user" +msgstr "Modifier un utilisateur service" + +#: views.py:353 +msgid "The service user was deleted." +msgstr "L'utilisateur service a été supprimé." + +#: views.py:373 +msgid "The ban was added." +msgstr "Le bannissement a été ajouté." + +#: views.py:381 +msgid "Warning: this user already has an active ban." +msgstr "Attention : cet utilisateur a déjà un bannissement actif." + +#: views.py:400 +msgid "The ban was edited." +msgstr "Le bannissement a été modifié." + +#: views.py:403 +msgid "Edit a ban" +msgstr "Modifier un bannissement" + +#: views.py:415 +msgid "The ban was deleted." +msgstr "Le bannissement a été supprimé." + +#: views.py:442 +msgid "The whitelist was added." +msgstr "L'accès gracieux a été ajouté." + +#: views.py:450 +msgid "Warning: this user already has an active whitelist." +msgstr "Attention : cet utilisateur a déjà un accès gracieux actif." + +#: views.py:453 +msgid "Add a whitelist" +msgstr "Ajouter un accès gracieux" + +#: views.py:473 +msgid "The whitelist was edited." +msgstr "L'accès gracieux a été ajouté." + +#: views.py:476 +msgid "Edit a whitelist" +msgstr "Modifier un accès gracieux" + +#: views.py:488 +msgid "The whitelist was deleted." +msgstr "L'accès gracieux a été supprimé." + +#: views.py:512 +msgid "The local email account was created." +msgstr "Le compte mail local a été créé." + +#: views.py:520 +msgid "Add a local email account" +msgstr "Ajouter un compte mail local" + +#: views.py:537 +msgid "The local email account was edited." +msgstr "Le compte mail local a été modifié." + +#: views.py:545 +msgid "Edit a local email account" +msgstr "Modifier un compte mail local" + +#: views.py:557 +msgid "The local email account was deleted." +msgstr "Le compte mail local a été supprimé." + +#: views.py:581 +msgid "The email settings were edited." +msgstr "Les paramètres mail ont été modifiés." + +#: views.py:590 +msgid "Edit the email settings" +msgstr "Modifier les paramètres mail" + +#: views.py:604 +msgid "The school was added." +msgstr "L'établissement a été ajouté." + +#: views.py:607 +msgid "Add a school" +msgstr "Ajouter un établissement" + +#: views.py:622 +msgid "The school was edited." +msgstr "L'établissement a été modifié." + +#: views.py:625 +msgid "Edit a school" +msgstr "Modifier un établissement" + +#: views.py:644 +msgid "The school was deleted." +msgstr "L'établissement a été supprimé." + +#: views.py:648 +#, python-format +msgid "" +"The school %s is assigned to at least one user, impossible to delete it." +msgstr "" +"L'établissement %s est affecté à au moins un utilisateur, impossible de le " +"supprimer." + +#: views.py:652 views.py:764 +msgid "Delete" +msgstr "Supprimer" + +#: views.py:665 +msgid "The shell was added." +msgstr "L'interface système a été ajoutée." + +#: views.py:668 +msgid "Add a shell" +msgstr "Ajouter une interface système" + +#: views.py:682 +msgid "The shell was edited." +msgstr "L'interface système a été modifiée." + +#: views.py:685 +msgid "Edit a shell" +msgstr "Modifier une interface système" + +#: views.py:697 +msgid "The shell was deleted." +msgstr "L'interface système a été supprimée." + +#: views.py:714 +msgid "The group of rights was added." +msgstr "Le groupe de droits a été ajouté." + +#: views.py:717 +msgid "Add a group of rights" +msgstr "Ajouter un groupe de droits" + +#: views.py:735 +msgid "The group of rights was edited." +msgstr "Le groupe de droits a été modifié." + +#: views.py:738 +msgid "Edit a group of rights" +msgstr "Modifier un groupe de droits" + +#: views.py:755 +msgid "The group of rights was deleted." +msgstr "Le groupe de droits a été supprimé." + +#: views.py:760 +#, python-format +msgid "" +"The group of rights %s is assigned to at least one user, impossible to " +"delete it." +msgstr "" +"Le groupe de droits %s est affecté à au moins un utilisateur, impossible de " +"le supprimer." + +#: views.py:788 +msgid "Archiving" +msgstr "Archivage" + +#: views.py:789 +#, python-format +msgid "%s users were archived." +msgstr "%s utilisateurs ont été archivés." + +#: views.py:1038 +msgid "The user doesn't exist." +msgstr "L'utilisateur n'existe pas." + +#: views.py:1040 views.py:1048 +msgid "Reset" +msgstr "Réinitialiser" + +#: views.py:1045 +msgid "An email to reset the password was sent." +msgstr "Un mail pour réinitialiser le mot de passe a été envoyé." + +#: views.py:1062 +msgid "Error: please contact an admin." +msgstr "Erreur : veuillez contacter un admin." + +#: views.py:1074 +msgid "Password reset" +msgstr "Réinitialisation du mot de passe" + +#: views.py:1114 views.py:1138 views.py:1153 +msgid "The mailing list doesn't exist." +msgstr "La liste de diffusion n'existe pas." + #: widgets.py:35 msgid "Close" -msgstr "" +msgstr "Fermer" #: widgets.py:36 msgid "Today" -msgstr "" +msgstr "Aujourd'hui" #: widgets.py:44 msgid "Next" -msgstr "" +msgstr "Suivant" #: widgets.py:45 msgid "Previous" -msgstr "" +msgstr "Précédent" #: widgets.py:46 msgid "Wk" -msgstr "" +msgstr "Wk" diff --git a/users/migrations/0076_auto_20180818_1321.py b/users/migrations/0076_auto_20180818_1321.py new file mode 100644 index 00000000..45fd1a62 --- /dev/null +++ b/users/migrations/0076_auto_20180818_1321.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-08-18 11:21 +from __future__ import unicode_literals + +from django.conf import settings +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion +import users.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0075_merge_20180815_2202'), + ] + + operations = [ + migrations.AlterModelOptions( + name='adherent', + options={'permissions': (('change_user_password', 'Can change the password of a user'), ('change_user_state', 'Can edit the state of a user'), ('change_user_force', 'Can force the move'), ('change_user_shell', 'Can edit the shell of a user'), ('change_user_groups', 'Can edit the groups of rights of a user (critical permission)'), ('change_all_users', 'Can edit all users, including those with rights.'), ('view_user', 'Can view a user object')), 'verbose_name': 'member', 'verbose_name_plural': 'members'}, + ), + migrations.AlterModelOptions( + name='ban', + options={'permissions': (('view_ban', 'Can view a ban object'),), 'verbose_name': 'ban', 'verbose_name_plural': 'bans'}, + ), + migrations.AlterModelOptions( + name='club', + options={'permissions': (('change_user_password', 'Can change the password of a user'), ('change_user_state', 'Can edit the state of a user'), ('change_user_force', 'Can force the move'), ('change_user_shell', 'Can edit the shell of a user'), ('change_user_groups', 'Can edit the groups of rights of a user (critical permission)'), ('change_all_users', 'Can edit all users, including those with rights.'), ('view_user', 'Can view a user object')), 'verbose_name': 'club', 'verbose_name_plural': 'clubs'}, + ), + migrations.AlterModelOptions( + name='emailaddress', + options={'permissions': (('view_emailaddress', 'Can view a local email account object'),), 'verbose_name': 'local email account', 'verbose_name_plural': 'local email accounts'}, + ), + migrations.AlterModelOptions( + name='listright', + options={'permissions': (('view_listright', 'Can view a group of rights object'),), 'verbose_name': 'group of rights', 'verbose_name_plural': 'groups of rights'}, + ), + migrations.AlterModelOptions( + name='listshell', + options={'permissions': (('view_listshell', 'Can view a shell object'),), 'verbose_name': 'shell', 'verbose_name_plural': 'shells'}, + ), + migrations.AlterModelOptions( + name='school', + options={'permissions': (('view_school', 'Can view a school object'),), 'verbose_name': 'school', 'verbose_name_plural': 'schools'}, + ), + migrations.AlterModelOptions( + name='serviceuser', + options={'permissions': (('view_serviceuser', 'Can view a service user object'),), 'verbose_name': 'service user', 'verbose_name_plural': 'service users'}, + ), + migrations.AlterModelOptions( + name='user', + options={'permissions': (('change_user_password', 'Can change the password of a user'), ('change_user_state', 'Can edit the state of a user'), ('change_user_force', 'Can force the move'), ('change_user_shell', 'Can edit the shell of a user'), ('change_user_groups', 'Can edit the groups of rights of a user (critical permission)'), ('change_all_users', 'Can edit all users, including those with rights.'), ('view_user', 'Can view a user object')), 'verbose_name': 'user (member or club)', 'verbose_name_plural': 'users (members or clubs)'}, + ), + migrations.AlterModelOptions( + name='whitelist', + options={'permissions': (('view_whitelist', 'Can view a whitelist object'),), 'verbose_name': 'whitelist (free of charge access)', 'verbose_name_plural': 'whitelists (free of charge access)'}, + ), + migrations.AlterField( + model_name='adherent', + name='gpg_fingerprint', + field=models.CharField(blank=True, max_length=40, null=True, validators=[django.core.validators.RegexValidator('^[0-9A-F]{40}$', message='A GPG fingerprint must contain 40 hexadecimal characters.')]), + ), + migrations.AlterField( + model_name='ban', + name='state', + field=models.IntegerField(choices=[(0, 'HARD (no access)'), (1, 'SOFT (local access only)'), (2, 'RESTRICTED (speed limitation)')], default=0), + ), + migrations.AlterField( + model_name='emailaddress', + name='user', + field=models.ForeignKey(help_text='User of the local email account', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='listright', + name='unix_name', + field=models.CharField(max_length=255, unique=True, validators=[django.core.validators.RegexValidator('^[a-z]+$', message='UNIX groups can only contain lower case letters.')]), + ), + migrations.AlterField( + model_name='request', + name='type', + field=models.CharField(choices=[('PW', 'Password'), ('EM', 'Email address')], max_length=2), + ), + migrations.AlterField( + model_name='serviceuser', + name='comment', + field=models.CharField(blank=True, help_text='Comment', max_length=255), + ), + migrations.AlterField( + model_name='serviceuser', + name='pseudo', + field=models.CharField(help_text='Must only contain letters, numerals or dashes.', max_length=32, unique=True, validators=[users.models.linux_user_validator]), + ), + migrations.AlterField( + model_name='user', + name='comment', + field=models.CharField(blank=True, help_text='Comment, school year', max_length=255), + ), + migrations.AlterField( + model_name='user', + name='local_email_redirect', + field=models.BooleanField(default=False, help_text='Enable redirection of the local email messages to the main email address.'), + ), + migrations.AlterField( + model_name='user', + name='pseudo', + field=models.CharField(help_text='Must only contain letters, numerals or dashes.', max_length=32, unique=True, validators=[users.models.linux_user_validator]), + ), + ] diff --git a/users/models.py b/users/models.py index f3e786c0..b5874512 100755 --- a/users/models.py +++ b/users/models.py @@ -70,6 +70,8 @@ from django.contrib.auth.models import ( ) from django.core.validators import RegexValidator import traceback +from django.utils.translation import ugettext_lazy as _ + from reversion import revisions as reversion import ldapdb.models @@ -100,7 +102,7 @@ def linux_user_validator(login): pas les contraintes unix (maj, min, chiffres ou tiret)""" if not linux_user_check(login): raise forms.ValidationError( - ", ce pseudo ('%(label)s') contient des carractères interdits", + _("The username '%(label)s' contains forbidden characters."), params={'label': login}, ) @@ -142,10 +144,10 @@ class UserManager(BaseUserManager): su=False ): if not pseudo: - raise ValueError('Users must have an username') + raise ValueError(_("Users must have an username.")) if not linux_user_check(pseudo): - raise ValueError('Username shall only contain [a-z0-9-]') + raise ValueError(_("Username should only contain [a-z0-9-].")) user = Adherent( pseudo=pseudo, @@ -180,7 +182,7 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, """ Definition de l'utilisateur de base. Champs principaux : name, surnname, pseudo, email, room, password Herite du django BaseUser et du système d'auth django""" - PRETTY_NAME = "Utilisateurs (clubs et adhérents)" + STATE_ACTIVE = 0 STATE_DISABLED = 1 STATE_ARCHIVE = 2 @@ -194,21 +196,22 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, pseudo = models.CharField( max_length=32, unique=True, - help_text="Doit contenir uniquement des lettres, chiffres, ou tirets", + help_text=_("Must only contain letters, numerals or dashes."), validators=[linux_user_validator] ) email = models.EmailField( blank=True, null=True, - help_text="External email address allowing us to contact you." + help_text=_("External email address allowing us to contact you.") ) local_email_redirect = models.BooleanField( default=False, - help_text="Enable redirection of the local email messages to the main email." + help_text=_("Enable redirection of the local email messages to the" + " main email address.") ) local_email_enabled = models.BooleanField( default=False, - help_text="Enable the local email account." + help_text=_("Enable the local email account.") ) school = models.ForeignKey( 'School', @@ -223,7 +226,7 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, blank=True ) comment = models.CharField( - help_text="Commentaire, promo", + help_text=_("Comment, school year"), max_length=255, blank=True ) @@ -249,18 +252,20 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, class Meta: permissions = ( ("change_user_password", - "Peut changer le mot de passe d'un user"), - ("change_user_state", "Peut éditer l'etat d'un user"), - ("change_user_force", "Peut forcer un déménagement"), - ("change_user_shell", "Peut éditer le shell d'un user"), + _("Can change the password of a user")), + ("change_user_state", _("Can edit the state of a user")), + ("change_user_force", _("Can force the move")), + ("change_user_shell", _("Can edit the shell of a user")), ("change_user_groups", - "Peut éditer les groupes d'un user ! Permission critique"), + _("Can edit the groups of rights of a user (critical" + " permission)")), ("change_all_users", - "Peut éditer tous les users, y compris ceux dotés de droits. " - "Superdroit"), + _("Can edit all users, including those with rights.")), ("view_user", - "Peut voir un objet user quelquonque"), + _("Can view a user object")), ) + verbose_name = _("user (member or club)") + verbose_name_plural = _("users (members or clubs)") @cached_property def name(self): @@ -278,7 +283,7 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, elif self.is_class_club: return self.club.room else: - raise NotImplementedError("Type inconnu") + raise NotImplementedError(_("Unknown type.")) @cached_property def get_mail_addresses(self): @@ -298,11 +303,11 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, def class_name(self): """Renvoie si il s'agit d'un adhérent ou d'un club""" if hasattr(self, 'adherent'): - return "Adherent" + return _("Member") elif hasattr(self, 'club'): - return "Club" + return _("Club") else: - raise NotImplementedError("Type inconnu") + raise NotImplementedError(_("Unknown type.")) @cached_property def gid_number(self): @@ -513,7 +518,7 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, if not interface.ipv4: with transaction.atomic(), reversion.create_revision(): interface.assign_ipv4() - reversion.set_comment("Assignation ipv4") + reversion.set_comment(_("IPv4 assigning")) interface.save() def unassign_ips(self): @@ -522,7 +527,7 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, for interface in interfaces: with transaction.atomic(), reversion.create_revision(): interface.unassign_ipv4() - reversion.set_comment("Désassignation ipv4") + reversion.set_comment(_("IPv4 unassigning")) interface.save() def archive(self): @@ -669,10 +674,9 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, if all_interfaces.count() > OptionalMachine.get_cached_value( 'max_lambdauser_interfaces' ): - return False, "Maximum de machines enregistrees atteinte" + return False, _("Maximum number of registered machines reached.") if not nas_type: - return False, "Re2o ne sait pas à quel machinetype affecter cette\ - machine" + return False, _("Re2o doesn't know wich machine type to assign.") machine_type_cible = nas_type.machine_type try: machine_parent = Machine() @@ -768,7 +772,7 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, user_request.adherent in self.club.administrators.all()): return True, None else: - return False, u"Vous n'avez pas le droit d'éditer ce club" + return False, _("You don't have the right to edit this club.") else: if self == user_request: return True, None @@ -776,18 +780,19 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, return True, None elif user_request.has_perm('users.change_user'): if self.groups.filter(listright__critical=True): - return False, (u"Utilisateurs avec droits critiques, ne " - "peut etre édité") + return False, (_("User with critical rights, can't be" + " edited.")) elif self == AssoOption.get_cached_value('utilisateur_asso'): - return False, (u"Impossible d'éditer l'utilisateur asso " - "sans droit change_all_users") + return False, (_("Impossible to edit the organisation's" + " user without the 'change_all_users'" + " right.")) else: return True, None elif user_request.has_perm('users.change_all_users'): return True, None else: - return False, (u"Vous ne pouvez éditer un autre utilisateur " - "que vous même") + return False, (_("You don't have the right to edit another" + " user.")) def can_change_password(self, user_request, *_args, **_kwargs): """Check if a user can change a user's password @@ -804,7 +809,7 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, user_request.adherent in self.club.administrators.all()): return True, None else: - return False, u"Vous n'avez pas le droit d'éditer ce club" + return False, _("You don't have the right to edit this club.") else: if (self == user_request or user_request.has_perm('users.change_user_groups')): @@ -815,8 +820,8 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, not self.groups.all()): return True, None else: - return False, (u"Vous ne pouvez éditer un autre utilisateur " - "que vous même") + return False, (_("You don't have the right to edit another" + " user.")) def check_selfpasswd(self, user_request, *_args, **_kwargs): """ Returns (True, None) if user_request is self, else returns @@ -834,7 +839,7 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, """ return ( user_request.has_perm('users.change_user_state'), - "Droit requis pour changer l'état" + _("Permission required to change the state.") ) def can_change_shell(self, user_request, *_args, **_kwargs): @@ -846,7 +851,7 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, """ if not ((self.pk == user_request.pk and OptionalUser.get_cached_value('self_change_shell')) or user_request.has_perm('users.change_user_shell')): - return False, u"Droit requis pour changer le shell" + return False, _("Permission required to change the shell.") else: return True, None @@ -860,12 +865,12 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, """ return ( OptionalUser.get_cached_value('local_email_accounts_enabled'), - "La gestion des comptes mails doit être activée" + _("Local email accounts must be enabled.") ) @staticmethod def can_change_local_email_enabled(user_request, *_args, **_kwargs): - """ Check if a user can change internal address . + """ Check if a user can change internal address. :param user_request: The user who request :returns: a message and a boolean which is True if the user has @@ -873,7 +878,7 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, """ return ( OptionalUser.get_cached_value('local_email_accounts_enabled'), - "La gestion des comptes mails doit être activée" + _("Local email accounts must be enabled.") ) @staticmethod @@ -886,7 +891,7 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, """ return ( user_request.has_perm('users.change_user_force'), - "Droit requis pour forcer le déménagement" + _("Permission required to force the move.") ) @staticmethod @@ -899,7 +904,7 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, """ return ( user_request.has_perm('users.change_user_groups'), - "Droit requis pour éditer les groupes de l'user" + _("Permission required to edit the user's groups of rights.") ) @staticmethod @@ -911,7 +916,7 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, """ return ( user_request.is_superuser, - "Droit superuser requis pour éditer le flag superuser" + _("'superuser' right required to edit the superuser flag.") ) def can_view(self, user_request, *_args, **_kwargs): @@ -929,14 +934,14 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, user_request.adherent in self.club.members.all()): return True, None else: - return False, u"Vous n'avez pas le droit de voir ce club" + return False, _("You don't have the right to view this club.") else: if (self == user_request or user_request.has_perm('users.view_user')): return True, None else: - return False, (u"Vous ne pouvez voir un autre utilisateur " - "que vous même") + return False, (_("You don't have the right to view another" + " user.")) @staticmethod def can_view_all(user_request, *_args, **_kwargs): @@ -948,7 +953,7 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, """ return ( user_request.has_perm('users.view_user'), - u"Vous n'avez pas accès à la liste des utilisateurs." + _("You don't have the right to view the list of users.") ) def can_delete(self, user_request, *_args, **_kwargs): @@ -961,7 +966,7 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, """ return ( user_request.has_perm('users.delete_user'), - u"Vous ne pouvez pas supprimer cet utilisateur." + _("You don't have the right to delete this user.") ) def __init__(self, *args, **kwargs): @@ -985,15 +990,15 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, if not self.local_email_enabled and not self.email: raise ValidationError( {'email': ( - 'There is neither a local email address nor an external' - ' email address for this user.' + _("There is neither a local email address nor an external" + " email address for this user.") ), } ) if self.local_email_redirect and not self.email: raise ValidationError( {'local_email_redirect': ( - 'You cannot redirect your local email if no external email ' - 'has been set.'), } + _("You can't redirect your local emails if no external email" + " address has been set.")), } ) def __str__(self): @@ -1003,7 +1008,7 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, class Adherent(User): """ A class representing a member (it's a user with special informations) """ - PRETTY_NAME = "Adhérents" + name = models.CharField(max_length=255) room = models.OneToOneField( 'topologie.Room', @@ -1017,11 +1022,29 @@ class Adherent(User): null=True, validators=[RegexValidator( '^[0-9A-F]{40}$', - message="Une fingerprint GPG doit contenir 40 " - "caractères hexadécimaux" + message=_("A GPG fingerprint must contain 40 hexadecimal" + " characters.") )] ) + class Meta: + permissions = ( + ("change_user_password", + _("Can change the password of a user")), + ("change_user_state", _("Can edit the state of a user")), + ("change_user_force", _("Can force the move")), + ("change_user_shell", _("Can edit the shell of a user")), + ("change_user_groups", + _("Can edit the groups of rights of a user (critical" + " permission)")), + ("change_all_users", + _("Can edit all users, including those with rights.")), + ("view_user", + _("Can view a user object")), + ) + verbose_name = _("member") + verbose_name_plural = _("members") + @classmethod def get_instance(cls, adherentid, *_args, **_kwargs): """Try to find an instance of `Adherent` with the given id. @@ -1049,14 +1072,14 @@ class Adherent(User): else: return ( user_request.has_perm('users.add_user'), - u"Vous n'avez pas le droit de créer un utilisateur" + _("You don't have the right to create a user.") ) class Club(User): """ A class representing a club (it is considered as a user with special informations) """ - PRETTY_NAME = "Clubs" + room = models.ForeignKey( 'topologie.Room', on_delete=models.PROTECT, @@ -1077,6 +1100,24 @@ class Club(User): default=False ) + class Meta: + permissions = ( + ("change_user_password", + _("Can change the password of a user")), + ("change_user_state", _("Can edit the state of a user")), + ("change_user_force", _("Can force the move")), + ("change_user_shell", _("Can edit the shell of a user")), + ("change_user_groups", + _("Can edit the groups of rights of a user (critical" + " permission)")), + ("change_all_users", + _("Can edit all users, including those with rights.")), + ("view_user", + _("Can view a user object")), + ) + verbose_name = _("club") + verbose_name_plural = _("clubs") + @staticmethod def can_create(user_request, *_args, **_kwargs): """Check if an user can create an user object. @@ -1093,7 +1134,7 @@ class Club(User): else: return ( user_request.has_perm('users.add_user'), - u"Vous n'avez pas le droit de créer un club" + _("You don't have the right to create a club.") ) @staticmethod @@ -1111,7 +1152,7 @@ class Club(User): if (user_request.adherent.club_administrator.all() or user_request.adherent.club_members.all()): return True, None - return False, u"Vous n'avez pas accès à la liste des utilisateurs." + return False, _("You don't have the right to view the list of users.") @classmethod def get_instance(cls, clubid, *_args, **_kwargs): @@ -1176,12 +1217,10 @@ class ServiceUser(RevMixin, AclMixin, AbstractBaseUser): ('usermgmt', 'usermgmt'), ) - PRETTY_NAME = "Utilisateurs de service" - pseudo = models.CharField( max_length=32, unique=True, - help_text="Doit contenir uniquement des lettres, chiffres, ou tirets", + help_text=_("Must only contain letters, numerals or dashes."), validators=[linux_user_validator] ) access_group = models.CharField( @@ -1190,7 +1229,7 @@ class ServiceUser(RevMixin, AclMixin, AbstractBaseUser): max_length=32 ) comment = models.CharField( - help_text="Commentaire", + help_text=_("Comment"), max_length=255, blank=True ) @@ -1200,12 +1239,14 @@ class ServiceUser(RevMixin, AclMixin, AbstractBaseUser): class Meta: permissions = ( - ("view_serviceuser", "Peut voir un objet serviceuser"), + ("view_serviceuser", _("Can view a service user object")), ) + verbose_name = _("service user") + verbose_name_plural = _("service users") def get_full_name(self): """ Renvoie le nom complet du serviceUser formaté nom/prénom""" - return "ServiceUser <{name}>".format(name=self.pseudo) + return _("Service user <{name}>").format(name=self.pseudo) def get_short_name(self): """ Renvoie seulement le nom""" @@ -1262,14 +1303,15 @@ def service_user_post_delete(**kwargs): class School(RevMixin, AclMixin, models.Model): """ Etablissement d'enseignement""" - PRETTY_NAME = "Établissements enregistrés" name = models.CharField(max_length=255) class Meta: permissions = ( - ("view_school", "Peut voir un objet school"), + ("view_school", _("Can view a school object")), ) + verbose_name = _("school") + verbose_name_plural = _("schools") def __str__(self): return self.name @@ -1281,29 +1323,29 @@ class ListRight(RevMixin, AclMixin, Group): Permet de gérer facilement les accès serveurs et autres La clef de recherche est le gid, pour cette raison là il n'est plus modifiable après creation""" - PRETTY_NAME = "Liste des droits existants" unix_name = models.CharField( max_length=255, unique=True, validators=[RegexValidator( '^[a-z]+$', - message=("Les groupes unix ne peuvent contenir que des lettres " - "minuscules") + message=(_("UNIX groups can only contain lower case letters.")) )] ) gid = models.PositiveIntegerField(unique=True, null=True) critical = models.BooleanField(default=False) details = models.CharField( - help_text="Description", + help_text=_("Description"), max_length=255, blank=True ) class Meta: permissions = ( - ("view_listright", "Peut voir un objet Group/ListRight"), + ("view_listright", _("Can view a group of rights object")), ) + verbose_name = _("group of rights") + verbose_name_plural = _("groups of rights") def __str__(self): return self.name @@ -1345,14 +1387,16 @@ def listright_post_delete(**kwargs): class ListShell(RevMixin, AclMixin, models.Model): """Un shell possible. Pas de check si ce shell existe, les admin sont des grands""" - PRETTY_NAME = "Liste des shells disponibles" shell = models.CharField(max_length=255, unique=True) class Meta: permissions = ( - ("view_listshell", "Peut voir un objet shell quelqu'il soit"), + ("view_listshell", _("Can view a shell object")), ) + verbose_name = _("shell") + verbose_name_plural = _("shells") + def get_pretty_name(self): """Return the canonical name of the shell""" @@ -1365,15 +1409,14 @@ class ListShell(RevMixin, AclMixin, models.Model): class Ban(RevMixin, AclMixin, models.Model): """ Bannissement. Actuellement a un effet tout ou rien. Gagnerait à être granulaire""" - PRETTY_NAME = "Liste des bannissements" STATE_HARD = 0 STATE_SOFT = 1 STATE_BRIDAGE = 2 STATES = ( - (0, 'HARD (aucun accès)'), - (1, 'SOFT (accès local seulement)'), - (2, 'BRIDAGE (bridage du débit)'), + (0, _("HARD (no access)")), + (1, _("SOFT (local access only)")), + (2, _("RESTRICTED (speed limitation)")), ) user = models.ForeignKey('User', on_delete=models.PROTECT) @@ -1384,8 +1427,10 @@ class Ban(RevMixin, AclMixin, models.Model): class Meta: permissions = ( - ("view_ban", "Peut voir un objet ban quelqu'il soit"), + ("view_ban", _("Can view a ban object")), ) + verbose_name = _("ban") + verbose_name_plural = _("bans") def notif_ban(self): """ Prend en argument un objet ban, envoie un mail de notification """ @@ -1419,8 +1464,8 @@ class Ban(RevMixin, AclMixin, models.Model): """ if (not user_request.has_perm('users.view_ban') and self.user != user_request): - return False, (u"Vous n'avez pas le droit de voir les " - "bannissements autre que les vôtres") + return False, (_("You don't have the right to view bans other" + " than yours.")) else: return True, None @@ -1459,7 +1504,6 @@ class Whitelist(RevMixin, AclMixin, models.Model): """Accès à titre gracieux. L'utilisateur ne paye pas; se voit accorder un accès internet pour une durée défini. Moins fort qu'un ban quel qu'il soit""" - PRETTY_NAME = "Liste des accès gracieux" user = models.ForeignKey('User', on_delete=models.PROTECT) raison = models.CharField(max_length=255) @@ -1468,8 +1512,10 @@ class Whitelist(RevMixin, AclMixin, models.Model): class Meta: permissions = ( - ("view_whitelist", "Peut voir un objet whitelist"), + ("view_whitelist", _("Can view a whitelist object")), ) + verbose_name = _("whitelist (free of charge access)") + verbose_name_plural = _("whitelists (free of charge access)") def is_active(self): """ Is this whitelisting active ? """ @@ -1485,8 +1531,8 @@ class Whitelist(RevMixin, AclMixin, models.Model): """ if (not user_request.has_perm('users.view_whitelist') and self.user != user_request): - return False, (u"Vous n'avez pas le droit de voir les accès " - "gracieux autre que les vôtres") + return False, (_("You don't have the right to view whitelists" + " other than yours.")) else: return True, None @@ -1529,8 +1575,8 @@ class Request(models.Model): PASSWD = 'PW' EMAIL = 'EM' TYPE_CHOICES = ( - (PASSWD, 'Mot de passe'), - (EMAIL, 'Email'), + (PASSWD, _("Password")), + (EMAIL, _("Email address")), ) type = models.CharField(max_length=2, choices=TYPE_CHOICES) token = models.CharField(max_length=32) @@ -1722,20 +1768,20 @@ class EMailAddress(RevMixin, AclMixin, models.Model): user = models.ForeignKey( User, on_delete=models.CASCADE, - help_text="User of the local email", + help_text=_("User of the local email account") ) local_part = models.CharField( unique=True, max_length=128, - help_text="Local part of the email address" + help_text=_("Local part of the email address") ) class Meta: permissions = ( - ("view_emailaddress", "Can see a local email account object"), + ("view_emailaddress", _("Can view a local email account object")), ) - verbose_name = "Local email account" - verbose_name_plural = "Local email accounts" + verbose_name = _("local email account") + verbose_name_plural = _("local email accounts") def __str__(self): return str(self.local_part) + OptionalUser.get_cached_value('local_email_domain') @@ -1759,11 +1805,12 @@ class EMailAddress(RevMixin, AclMixin, models.Model): if user_request.has_perm('users.add_emailaddress'): return True, None if not OptionalUser.get_cached_value('local_email_accounts_enabled'): - return False, "The local email accounts are not enabled." + return False, _("The local email accounts are not enabled.") if int(user_request.id) != int(userid): - return False, "You don't have the right to add a local email account to another user." + return False, _("You don't have the right to add a local email" + " account to another user.") elif user_request.email_address.count() >= OptionalUser.get_cached_value('max_email_address'): - return False, "You have reached the limit of {} local email account.".format( + return False, _("You reached the limit of {} local email accounts.").format( OptionalUser.get_cached_value('max_email_address') ) return True, None @@ -1781,10 +1828,11 @@ class EMailAddress(RevMixin, AclMixin, models.Model): if user_request.has_perm('users.view_emailaddress'): return True, None if not OptionalUser.get_cached_value('local_email_accounts_enabled'): - return False, "The local email accounts are not enabled." + return False, _("The local email accounts are not enabled.") if user_request == self.user: return True, None - return False, "You don't have the right to edit someone else's local email account." + return False, _("You don't have the right to edit another user's local" + " email account.") def can_delete(self, user_request, *_args, **_kwargs): """Check if a user can delete the alias @@ -1797,16 +1845,16 @@ class EMailAddress(RevMixin, AclMixin, models.Model): the local email account. """ if self.local_part == self.user.pseudo.lower(): - return False, ("You cannot delete a local email account whose " - "local part is the same as the username.") - if user_request.has_perm('users.delete_emailaddress'): + return False, _("You can't delete a local email account whose" + " local part is the same as the username.") + if user_request.has_perm('users.delete_emailaddress'): return True, None if not OptionalUser.get_cached_value('local_email_accounts_enabled'): - return False, "The local email accounts are not enabled." + return False, _("The local email accounts are not enabled.") if user_request == self.user: return True, None - return False, ("You don't have the right to delete someone else's " - "local email account") + return False, _("You don't have the right to delete another user's" + " local email account") def can_edit(self, user_request, *_args, **_kwargs): """Check if a user can edit the alias @@ -1819,19 +1867,20 @@ class EMailAddress(RevMixin, AclMixin, models.Model): the local email account. """ if self.local_part == self.user.pseudo.lower(): - return False, ("You cannot edit a local email account whose " - "local part is the same as the username.") + return False, _("You can't edit a local email account whose local" + " part is the same as the username.") if user_request.has_perm('users.change_emailaddress'): return True, None if not OptionalUser.get_cached_value('local_email_accounts_enabled'): - return False, "The local email accounts are not enabled." + return False, _("The local email accounts are not enabled.") if user_request == self.user: return True, None - return False, ("You don't have the right to edit someone else's " - "local email account") + return False, _("You don't have the right to edit another user's local" + " email account.") def clean(self, *args, **kwargs): self.local_part = self.local_part.lower() if "@" in self.local_part: - raise ValidationError("The local part cannot contain a @") + raise ValidationError(_("The local part must not contain @.")) super(EMailAddress, self).clean(*args, **kwargs) + diff --git a/users/templates/users/aff_bans.html b/users/templates/users/aff_bans.html index ebcbdeee..1284bea8 100644 --- a/users/templates/users/aff_bans.html +++ b/users/templates/users/aff_bans.html @@ -21,8 +21,11 @@ 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 ban_list.paginator %} {% include "pagination.html" with list=ban_list %} {% endif %} @@ -30,36 +33,40 @@ with this program; if not, write to the Free Software Foundation, Inc., - - - - + {% trans "User" as tr_user %} + + + {% trans "Start date" as tr_start %} + + {% trans "End date" as tr_end %} + {% for ban in ban_list %} - {% if ban.is_active %} - + {% if ban.is_active %} + {% else %} - {% endif %} + {% endif %} - {% endfor %} + {% endfor %}
    {% include "buttons/sort.html" with prefix='ban' col="user" text="Utilisateur" %}Raison{% include "buttons/sort.html" with prefix='ban' col="start" text="Date de début" %}{% include "buttons/sort.html" with prefix='ban' col="end" text="Date de fin" %}{% include "buttons/sort.html" with prefix='ban' col="user" text=tr_user %}{% trans "Reason" %}{% include "buttons/sort.html" with prefix='ban' col="start" text=tr_start %}{% include "buttons/sort.html" with prefix='ban' col="end" text=tr_end %}
    {{ ban.user }} {{ ban.raison }} {{ ban.date_start }} {{ ban.date_end }} - {% can_delete ban %} - {% include 'buttons/suppr.html' with href='users:del-ban' id=ban.id %} - {% acl_end %} {% can_edit ban %} {% include 'buttons/edit.html' with href='users:edit-ban' id=ban.id %} {% acl_end %} {% history_button ban %} + {% can_delete ban %} + {% include 'buttons/suppr.html' with href='users:del-ban' id=ban.id %} + {% acl_end %}
    {% if ban_list.paginator %} {% include "pagination.html" with list=ban_list %} {% endif %} + diff --git a/users/templates/users/aff_clubs.html b/users/templates/users/aff_clubs.html index dfb82e10..1903a445 100644 --- a/users/templates/users/aff_clubs.html +++ b/users/templates/users/aff_clubs.html @@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. {% endcomment %} +{% load i18n %} + {% if clubs_list.paginator %} {% include "pagination.html" with list=clubs_list %} {% endif %} @@ -31,29 +33,35 @@ with this program; if not, write to the Free Software Foundation, Inc., - - - - - - + {% trans "Name" as tr_name %} + + {% trans "Username" as tr_username %} + + {% trans "Room" as tr_room %} + + + + {% for club in clubs_list %} {% can_view club %} - - - - - - - + + + + + + + {% acl_end %} {% endfor %} @@ -62,3 +70,4 @@ with this program; if not, write to the Free Software Foundation, Inc., {% if clubs_list.paginator %} {% include "pagination.html" with list=clubs_list %} {% endif %} + diff --git a/users/templates/users/aff_emailaddress.html b/users/templates/users/aff_emailaddress.html index 14156ac9..25048d6c 100644 --- a/users/templates/users/aff_emailaddress.html +++ b/users/templates/users/aff_emailaddress.html @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load acl %} +{% load i18n %} {% load logs_extra %} {% if emailaddress_list.paginator %} @@ -32,20 +33,20 @@ with this program; if not, write to the Free Software Foundation, Inc.,
    {% include "buttons/sort.html" with prefix='club' col="surname" text="Nom" %}{% include "buttons/sort.html" with prefix='club' col="pseudo" text="Pseudo" %}{% include "buttons/sort.html" with prefix='club' col="room" text="Chambre" %}Fin de cotisation leConnexionProfil{% include "buttons/sort.html" with prefix='club' col="surname" text=tr_name %}{% include "buttons/sort.html" with prefix='club' col="pseudo" text=tr_username %}{% include "buttons/sort.html" with prefix='club' col="room" text=tr_room %}{% trans "End of subscription on" %}{% trans "Internet access" %}{% trans "Profile" %}
    {{ club.surname }}{{ club.pseudo }}{{ club.room }}{% if club.is_adherent %}{{ club.end_adhesion }}{% else %}Non adhérent{% endif %}{% if club.has_access == True %} - Active - {% else %} - Désactivée - {% endif %} - -
    {{ club.surname }}{{ club.pseudo }}{{ club.room }}{% if club.is_adherent %}{{ club.end_adhesion }}{% else %}{% trans "Not a member" %}{% endif %}{% if club.has_access == True %} + {% trans "Active" %} + {% else %} + {% trans "Disabled" %} + {% endif %} + + + + +
    - + {% for emailaddress in emailaddress_list %} - - + {% endfor %} @@ -54,3 +55,4 @@ with this program; if not, write to the Free Software Foundation, Inc., {% if emailaddress_list.paginator %} {% include "pagination.html" with list=emailaddress_list %} {% endif %} + diff --git a/users/templates/users/aff_rights.html b/users/templates/users/aff_rights.html index 26ed3c5d..710c1f14 100644 --- a/users/templates/users/aff_rights.html +++ b/users/templates/users/aff_rights.html @@ -33,9 +33,8 @@ with this program; if not, write to the Free Software Foundation, Inc., {% for user_right in user_right_list %} - + {% endfor %}
    Email address{% trans "Email address" %}
    {{ emailaddress.complete_email_address }} - {% can_delete emailaddress %} - {% include 'buttons/suppr.html' with href='users:del-emailaddress' id=emailaddress.id %} - {% acl_end %} - {% can_edit emailaddress %} - {% include 'buttons/edit.html' with href='users:edit-emailaddress' id=emailaddress.id %} - {% acl_end %} - {% history_button emailaddress %} + {{ emailaddress.complete_email_address }} + {% can_delete emailaddress %} + {% include 'buttons/suppr.html' with href='users:del-emailaddress' id=emailaddress.id %} + {% acl_end %} + {% history_button emailaddress %} + {% can_edit emailaddress %} + {% include 'buttons/edit.html' with href='users:edit-emailaddress' id=emailaddress.id %} + {% acl_end %}
    {{ user_right }}{{ user_right }}
    - diff --git a/users/templates/users/aff_schools.html b/users/templates/users/aff_schools.html index 1d32a6eb..2c1e41dd 100644 --- a/users/templates/users/aff_schools.html +++ b/users/templates/users/aff_schools.html @@ -21,20 +21,21 @@ 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 i18n %} {% load acl %} {% load logs_extra %}
    - {% if school_list.paginator %} {% include "pagination.html" with list=school_list %} {% endif %} - - + {% trans "School" as tr_school %} + diff --git a/users/templates/users/aff_serviceusers.html b/users/templates/users/aff_serviceusers.html index 5f43b0d7..b5e0ae62 100644 --- a/users/templates/users/aff_serviceusers.html +++ b/users/templates/users/aff_serviceusers.html @@ -21,32 +21,35 @@ 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 i18n %} {% load acl %} {% load logs_extra %} -
    {% include "buttons/sort.html" with prefix='school' col='name' text='Etablissement' %}{% include "buttons/sort.html" with prefix='school' col='name' text=tr_school %}
    - - - - - - - - - {% for serviceuser in serviceusers_list %} - - - - - - - {% endfor %} + +
    NomRôleCommentaire
    {{ serviceuser.pseudo }}{{ serviceuser.access_group }}{{ serviceuser.comment }} - {% can_delete serviceuser %} - {% include 'buttons/suppr.html' with href='users:del-serviceuser' id=serviceuser.id %} - {% acl_end %} - {% can_edit serviceuser %} - {% include 'buttons/edit.html' with href='users:edit-serviceuser' id=serviceuser.id %} - {% acl_end %} - {% history_button serviceuser %} -
    + + + + + + + + + {% for serviceuser in serviceusers_list %} + + + + + + + {% endfor %}
    {% trans "Name" %}{% trans "Role" %}{% trans "Comment" %}
    {{ serviceuser.pseudo }}{{ serviceuser.access_group }}{{ serviceuser.comment }} + {% can_delete serviceuser %} + {% include 'buttons/suppr.html' with href='users:del-serviceuser' id=serviceuser.id %} + {% acl_end %} + {% history_button serviceuser %} + {% can_edit serviceuser %} + {% include 'buttons/edit.html' with href='users:edit-serviceuser' id=serviceuser.id %} + {% acl_end %} +
    diff --git a/users/templates/users/aff_shell.html b/users/templates/users/aff_shell.html index a968a312..b422ab52 100644 --- a/users/templates/users/aff_shell.html +++ b/users/templates/users/aff_shell.html @@ -21,28 +21,31 @@ 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 i18n %} {% load acl %} {% load logs_extra %} + - + {% for shell in shell_list %} - - - - + + + + {% endfor %}
    Shell{% trans "Shell" %}
    {{ shell.shell }} - {% can_delete shell %} - {% include 'buttons/suppr.html' with href='users:del-shell' id=shell.id %} - {% acl_end %} - {% can_edit shell %} - {% include 'buttons/edit.html' with href='users:edit-shell' id=shell.id %} - {% acl_end %} - {% history_button shell %} -
    {{ shell.shell }} + {% can_delete shell %} + {% include 'buttons/suppr.html' with href='users:del-shell' id=shell.id %} + {% acl_end %} + {% history_button shell %} + {% can_edit shell %} + {% include 'buttons/edit.html' with href='users:edit-shell' id=shell.id %} + {% acl_end %} +
    diff --git a/users/templates/users/aff_users.html b/users/templates/users/aff_users.html index 42dc07d6..f405e2c0 100644 --- a/users/templates/users/aff_users.html +++ b/users/templates/users/aff_users.html @@ -21,6 +21,9 @@ 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 i18n %} +
    {% if users_list.paginator %} {% include "pagination.html" with list=users_list %} @@ -29,31 +32,39 @@ with this program; if not, write to the Free Software Foundation, Inc., - - - - - - - + {% trans "Firt name" as tr_name %} + + {% trans "Surname" as tr_surname %} + + {% trans "Username" as tr_username %} + + {% trans "Room" as tr_room %} + + + + {% for user in users_list %} - - - - - - - - - + + + + + + + + + {% endfor %}
    {% include "buttons/sort.html" with prefix='user' col="name" text="Prénom" %}{% include "buttons/sort.html" with prefix='user' col="surname" text="Nom" %}{% include "buttons/sort.html" with prefix='user' col="pseudo" text="Pseudo" %}{% include "buttons/sort.html" with prefix='user' col="room" text="Chambre" %}Fin de cotisation leConnexionProfil{% include "buttons/sort.html" with prefix='user' col="name" text=tr_name %}{% include "buttons/sort.html" with prefix='user' col="surname" text=tr_surname %}{% include "buttons/sort.html" with prefix='user' col="pseudo" text=tr_username %}{% include "buttons/sort.html" with prefix='user' col="room" text=tr_room %}{% trans "End of subscription on" %}{% trans "Internet access" %}{% trans "Profile" %}
    {{ user.name }}{{ user.surname }}{{ user.pseudo }}{{ user.room }}{% if user.is_adherent %}{{ user.end_adhesion }}{% else %}Non adhérent{% endif %}{% if user.has_access == True %} - Active - {% else %} - Désactivée - {% endif %} - -
    {{ user.name }}{{ user.surname }}{{ user.pseudo }}{{ user.room }}{% if user.is_adherent %}{{ user.end_adhesion }}{% else %}{% trans "Not a member" %}{% endif %} + {% if user.has_access == True %} + {% trans "Active" %} + {% else %} + {% trans "Disabled" %} + {% endif %} + + + + +
    @@ -61,3 +72,4 @@ with this program; if not, write to the Free Software Foundation, Inc., {% include "pagination.html" with list=users_list %} {% endif %}
    + diff --git a/users/templates/users/aff_whitelists.html b/users/templates/users/aff_whitelists.html index 653e31e0..b170c43e 100644 --- a/users/templates/users/aff_whitelists.html +++ b/users/templates/users/aff_whitelists.html @@ -22,27 +22,33 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. {% endcomment %} +{% load i18n %} + {% if white_list.paginator %} {% include "pagination.html" with list=white_list %} {% endif %} {% load acl %} {% load logs_extra %} + - - - - + {% trans "User" as tr_user %} + + + {% trans "Start date" as tr_start %} + + {% trans "End date" as tr_end %} + {% for whitelist in white_list %} {% if whitelist.is_active %} - - {% else %} + + {% else %} - {% endif %} + {% endif %} @@ -51,10 +57,10 @@ with this program; if not, write to the Free Software Foundation, Inc., {% can_delete whitelist %} {% include 'buttons/suppr.html' with href='users:del-whitelist' id=whitelist.id %} {% acl_end %} + {% history_button whitelist %} {% can_edit whitelist %} {% include 'buttons/edit.html' with href='users:edit-whitelist' id=whitelist.id %} {% acl_end %} - {% history_button whitelist %} {% endfor %} @@ -63,3 +69,4 @@ with this program; if not, write to the Free Software Foundation, Inc., {% if white_list.paginator %} {% include "pagination.html" with list=white_list %} {% endif %} + diff --git a/users/templates/users/delete.html b/users/templates/users/delete.html index 928c0001..3f71d0d1 100644 --- a/users/templates/users/delete.html +++ b/users/templates/users/delete.html @@ -24,17 +24,20 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load bootstrap3 %} +{% load i18n %} -{% block title %}Destruction d'objets{% endblock %} +{% block title %}{% trans "Deletion of objects" %}{% endblock %} {% block content %} {% csrf_token %} -

    Attention, voulez-vous vraiment supprimer cet objet {{ objet_name }} ( {{ objet }} ) ?

    - {% bootstrap_button "Confirmer" button_type="submit" icon="trash" %} +

    {% blocktrans %}Warning: are you sure you want to delete this {{ objet_name }} object ( {{ objet }} )?{% endblocktrans %}

    + {% trans "Confirm" as tr_confirm %} + {% bootstrap_button tr_confirm button_type="submit" icon="trash" %}


    {% endblock %} + diff --git a/users/templates/users/index.html b/users/templates/users/index.html index 57a66e56..c53eb768 100644 --- a/users/templates/users/index.html +++ b/users/templates/users/index.html @@ -24,11 +24,12 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load bootstrap3 %} +{% load i18n %} -{% block title %}Utilisateurs{% endblock %} +{% block title %}{% trans "Users" %}{% endblock %} {% block content %} -

    Adhérents

    +

    {% trans "Users" %}

    {% include "users/aff_users.html" with users_list=users_list %}

    diff --git a/users/templates/users/index_ban.html b/users/templates/users/index_ban.html index d7f7184f..3b84947d 100644 --- a/users/templates/users/index_ban.html +++ b/users/templates/users/index_ban.html @@ -24,11 +24,12 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load bootstrap3 %} +{% load i18n %} -{% block title %}Utilisateurs{% endblock %} +{% block title %}{% trans "Users" %}{% endblock %} {% block content %} -

    Bannissements

    +

    {% trans "Bans" %}

    {% include "users/aff_bans.html" with ban_list=ban_list %}

    diff --git a/users/templates/users/index_clubs.html b/users/templates/users/index_clubs.html index 4c3dd609..31e48b5d 100644 --- a/users/templates/users/index_clubs.html +++ b/users/templates/users/index_clubs.html @@ -24,11 +24,12 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load bootstrap3 %} +{% load i18n %} -{% block title %}Utilisateurs{% endblock %} +{% block title %}{% trans "Users" %}{% endblock %} {% block content %} -

    Clubs

    +

    {% trans "Clubs" %}

    {% include "users/aff_clubs.html" with clubs_list=clubs_list %}

    diff --git a/users/templates/users/index_emailaddress.html b/users/templates/users/index_emailaddress.html index 6976e168..33b1c4f3 100644 --- a/users/templates/users/index_emailaddress.html +++ b/users/templates/users/index_emailaddress.html @@ -24,11 +24,12 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load bootstrap3 %} +{% load i18n %} -{% block title %}Local email accounts{% endblock %} +{% block title %}{% trans "Users" %}{% endblock %} {% block content %} -

    Local email accounts

    - {% include "users/aff_emailaddress.html" with emailaddress_list=emailaddress_list %} +

    {% trans "Local email accounts" %}

    + {% include "users/aff_emailaddress.html" with emailaddress_list=emailaddress_list %} {% endblock %} diff --git a/users/templates/users/index_listright.html b/users/templates/users/index_listright.html index 3b8b3e60..4aaa172c 100644 --- a/users/templates/users/index_listright.html +++ b/users/templates/users/index_listright.html @@ -25,15 +25,16 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} {% load acl %} +{% load i18n %} -{% block title %}Utilisateurs{% endblock %} +{% block title %}{% trans "Users" %}{% endblock %} {% block content %} -

    Liste des droits

    +

    {% trans "List of groups of rights" %}

    {% can_create ListRight %} - Ajouter un droit ou groupe + {% trans " Add a group of rights" %} {% acl_end %} - Supprimer un ou plusieurs droits/groupes + {% trans " Delete one or several groups of rights" %}

    {% include "users/aff_listright.html" %} diff --git a/users/templates/users/index_rights.html b/users/templates/users/index_rights.html index a3096f7f..4bfcbd1c 100644 --- a/users/templates/users/index_rights.html +++ b/users/templates/users/index_rights.html @@ -24,11 +24,12 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load bootstrap3 %} +{% load i18n %} -{% block title %}Utilisateurs{% endblock %} +{% block title %}{% trans "Users" %}{% endblock %} {% block content %} -

    Droits

    +

    {% trans "Rights" %}

    {% include "users/aff_rights.html" %}

    diff --git a/users/templates/users/index_schools.html b/users/templates/users/index_schools.html index 0912ffd5..42f6a588 100644 --- a/users/templates/users/index_schools.html +++ b/users/templates/users/index_schools.html @@ -25,16 +25,17 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} {% load acl %} +{% load i18n %} -{% block title %}Utilisateurs{% endblock %} +{% block title %}{% trans "Users" %}{% endblock %} {% block content %} -

    Liste des Établissements

    -
    Ensemble des établissements d'enseignement ou d'activité des utilisateurs crées
    +

    {% trans "List of schools" %}

    +
    {% trans "List of schools for created users." %}
    {% can_create School %} - Ajouter un établissement + {% trans " Add a school" %} {% acl_end %} - Supprimer un ou plusieurs établissements + {% trans " Delete one or several schools" %}
    {% include "users/aff_schools.html" with school_list=school_list %}
    diff --git a/users/templates/users/index_serviceusers.html b/users/templates/users/index_serviceusers.html index 497445b0..9d6ae67d 100644 --- a/users/templates/users/index_serviceusers.html +++ b/users/templates/users/index_serviceusers.html @@ -25,15 +25,15 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} {% load acl %} +{% load i18n %} -{% block title %}Utilisateurs{% endblock %} +{% block title %}{% trans "Users" %}{% endblock %} {% block content %} -

    Liste des service users LDAP

    -
    Les service users LDAP sont des utilisateurs spéciaux qui disposent d'un accès uniquement sur le ldap pour effectuer des opération d'authentification. - Il est recommandé de créer un service-user doté d'un login/mdp par service concerné
    +

    {% trans "List of LDAP service users" %}

    +
    {% trans "The LDAP service users are special users having access only to the LDAP for authentication operations. It is recommended to create a service user with a login and a password for any concerned service." %}
    {% can_create ServiceUser %} - Ajouter un service user + {% trans " Add a service user" %} {% acl_end %} {% include "users/aff_serviceusers.html" with serviceusers_list=serviceusers_list %}
    diff --git a/users/templates/users/index_shell.html b/users/templates/users/index_shell.html index 51fbb551..0f797be0 100644 --- a/users/templates/users/index_shell.html +++ b/users/templates/users/index_shell.html @@ -25,13 +25,14 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} {% load acl %} +{% load i18n %} -{% block title %}Utilisateurs{% endblock %} +{% block title %}{% trans "Users" %}{% endblock %} {% block content %} -

    Liste des Shells

    +

    {% trans "List of shells" %}

    {% can_create ListShell %} - Ajouter un shell + {% trans " Add a shell" %} {% acl_end %} {% include "users/aff_shell.html" with shell_list=shell_list %}
    diff --git a/users/templates/users/index_whitelist.html b/users/templates/users/index_whitelist.html index da03122b..7cab8830 100644 --- a/users/templates/users/index_whitelist.html +++ b/users/templates/users/index_whitelist.html @@ -24,11 +24,12 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load bootstrap3 %} +{% load i18n %} -{% block title %}Utilisateurs{% endblock %} +{% block title %}{% trans "Users" %}{% endblock %} {% block content %} -

    Accès à titre gracieux

    +

    {% trans "Whitelists" %}

    {% include "users/aff_whitelists.html" with white_list=white_list %}

    diff --git a/users/templates/users/mass_archive.html b/users/templates/users/mass_archive.html index 2626ae4e..84d9f72d 100644 --- a/users/templates/users/mass_archive.html +++ b/users/templates/users/mass_archive.html @@ -24,18 +24,19 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load bootstrap3 %} +{% load i18n %} -{% block title %} Utilisateurs à archiver{% endblock %} +{% block title %}{% trans "Users to archive" %}{% endblock %} {% block content %} {% csrf_token %} {% bootstrap_form userform %} - - + + -

    Les utilisateurs suivant seront archivés ({{ to_archive_list|length }}):

    +

    {% blocktrans %}The following users will be archived ({{ to_archive_list|length }}):{% endblocktrans %}

    {% include "users/aff_users.html" with users_list=to_archive_list %}

    diff --git a/users/templates/users/profil.html b/users/templates/users/profil.html index f573941b..0d391551 100644 --- a/users/templates/users/profil.html +++ b/users/templates/users/profil.html @@ -29,14 +29,14 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load design %} {% load i18n %} -{% block title %}Profil{% endblock %} +{% block title %}{% trans "Profile" %}{% endblock %} {% block content %}
    {% if user == users %} -

    Welcome {{ users.name }} {{ users.surname }}

    +

    {% blocktrans with name=users.name surname=users.surname %}Welcome {{ name }} {{ surname }}{% endblocktrans %}

    {% else %} -

    Profil de {{ users.name }} {{ users.surname }}

    +

    {% blocktrans with name=users.name surname=users.surname %}Profile of {{ name }} {{ surname }}{% endblocktrans %}

    {% endif %}
    @@ -44,29 +44,29 @@ with this program; if not, write to the Free Software Foundation, Inc.,
    {% if users.is_ban%}
    -
    Your account has been banned
    +
    {% trans "Your account has been banned" %}
    - End of the ban: {{ users.end_ban | date:"SHORT_DATE_FORMAT" }} + {% blocktrans with end_ban=users.end_ban|date:"SHORT_DATE_FORMAT" %}End of the ban: {{ end_ban }}{% endblocktrans %}
    {% elif not users.is_adherent %}
    -
    Not connected
    -
    +
    {% trans "No connection" %}
    +
    {% can_create Facture %} - Pay for a connexion + {% trans "Pay for a connection" %} {% acl_else %} - Ask for someone with the correct rights to pay for a connexion + {% trans "Ask for someone with the appropriate rights to pay for a connection." %} {% acl_end %}
    {% else %}
    -
    Connected
    +
    {% trans "Connection" %}
    - End of connexion: {{ users.end_adhesion | date:"SHORT_DATE_FORMAT"}} + {% blocktrans with end_connection=user.end_adhesion|date:"SHORT_DATE_FORMAT" %}End of connection: {{ end_connection }}{% endblocktrans %}
    {% endif %} @@ -79,7 +79,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
    @@ -89,20 +89,20 @@ with this program; if not, write to the Free Software Foundation, Inc., {% if nb_machines %}
    - Machines {{ nb_machines }} + {% trans " Machines" %}{{ nb_machines }}
    {% else %}
    -
    No machines
    +
    {% trans "No machine" %}
    @@ -111,147 +111,145 @@ with this program; if not, write to the Free Software Foundation, Inc.,
    - -

    - Informations détaillées + {% trans " Detailed information" %}

    -
    -
    - - - Éditer - - - - Changer le mot de passe - - {% can_change User state %} - - - Changer le statut - - {% acl_end %} - {% can_change User groups %} - - - Gérer les groupes - - {% acl_end %} - {% history_button users text=True %} +
    +
    + + + {% trans "Edit" %} + + + + {% trans "Change the password" %} + + {% can_change User state %} + + + {% trans "Change the state" %} + + {% acl_end %} + {% can_change User groups %} + + + {% trans "Edit the groups" %} + + {% acl_end %} + {% history_button users text=True %}
    -
    {% include "buttons/sort.html" with prefix='white' col="user" text="Utilisateur" %}Raison{% include "buttons/sort.html" with prefix='white' col="start" text="Date de début" %}{% include "buttons/sort.html" with prefix='white' col="end" text="Date de fin" %}{% include "buttons/sort.html" with prefix='white' col="user" text=tr_user %}{% trans "Reason" %}{% include "buttons/sort.html" with prefix='white' col="start" text=tr_start %}{% include "buttons/sort.html" with prefix='white' col="end" text=tr_end %}
    {{ whitelist.user }} {{ whitelist.raison }} {{ whitelist.date_start }}
    - - {% if users.is_class_club %} - - {% if users.club.mailing %} - - {% else %} - - {% endif %} - {% else %} - - - {% endif %} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {% if users.end_adhesion != None %} - - {% else %} - - {% endif %} - - {% if users.end_whitelist != None %} - - {% else %} - - {% endif %} - - - {% if users.end_ban != None %} - - {% else %} - - {% endif %} - - {% if users.state == 0 %} - - {% elif users.state == 1 %} - - {% else %} - - {% endif %} - - - - {% if users.has_access == True %} - - {% else %} - - {% endif %} - - {% if users.groups.all %} - - {% else %} - - {% endif %} - - - - - {% if users.adherent.gpg_fingerprint %} - - - {% endif %} - - - {% if users.shell %} - - - {% endif %} - -
    Mailing{{ users.pseudo }}(-admin)Mailing désactivéePrénom{{ users.name }}Nom{{ users.surname }}
    Pseudo{{ users.pseudo }}E-mail{{ users.email }}
    Chambre{{ users.room }}Téléphone{{ users.telephone }}
    École{{ users.school }}Commentaire{{ users.comment }}
    Date d'inscription{{ users.registered }}Dernière connexion{{ users.last_login }}
    Fin d'adhésion{{ users.end_adhesion }}Non adhérentAccès gracieux{{ users.end_whitelist }}Aucun
    Bannissement{{ users.end_ban }}Non banniStatutActifDésactivéArchivé
    Accès internetActif (jusqu'au {{ users.end_access }})DésactivéGroupes{{ users.groups.all|join:", "}}Aucun
    Solde{{ users.solde }} € - {% if user_solde %} - - - Recharger - - {% endif %} - Empreinte GPG{{ users.adherent.gpg_fingerprint }}
    Shell{{ users.shell }}
    + + + {% if users.is_class_club %} + + {% if users.club.mailing %} + + {% else %} + + {% endif %} + {% else %} + + + {% endif %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% if users.end_adhesion != None %} + + {% else %} + + {% endif %} + + {% if users.end_whitelist != None %} + + {% else %} + + {% endif %} + + + {% if users.end_ban != None %} + + {% else %} + + {% endif %} + + {% if users.state == 0 %} + + {% elif users.state == 1 %} + + {% else %} + + {% endif %} + + + + {% if users.has_access == True %} + + {% else %} + + {% endif %} + + {% if users.groups.all %} + + {% else %} + + {% endif %} + + + + + {% if users.adherent.gpg_fingerprint %} + + + {% endif %} + + + {% if users.shell %} + + + {% endif %} + +
    {% trans "Mailing" %}{{ users.pseudo }}(-admin){% trans "Mailing disabled" %}{% trans "Firt name" %}{{ users.name }}{% trans "Surname" %}{{ users.surname }}
    {% trans "Username" %}{{ users.pseudo }}{% trans "Email address" %}{{ users.email }}
    {% trans "Room" %}{{ users.room }}{% trans "Telephone number" %}{{ users.telephone }}
    {% trans "School" %}{{ users.school }}{% trans "Comment" %}{{ users.comment }}
    {% trans "Registration date" %}{{ users.registered }}{% trans "Last login" %}{{ users.last_login }}
    {% trans "End of membership" %}{{ users.end_adhesion }}{% trans "Not a member" %}{% trans "Whitelist" %}{{ users.end_whitelist }}{% trans "None" %}
    {% trans "Ban" %}{{ users.end_ban }}{% trans "Not banned" %}{% trans "State" %}{% trans "Active" %}{% trans "Disabled" %}{% trans "Archived" %}
    {% trans "Internet access" %}{% blocktrans with end_access=users.end_access %}Active (until {{ end_access }}){% endblocktrans %}{% trans "Disabled" %}{% trans "Groups of rights" %}{{ users.groups.all|join:", "}}{% trans "None" %}
    {% trans "Balance" %}{{ users.solde }} € + {% if user_solde %} + + + {% trans "Refill" %} + + {% endif %} + {% trans "GPG fingerprint" %}{{ users.adherent.gpg_fingerprint }}
    {% trans "Shell" %}{{ users.shell }}
    @@ -260,25 +258,25 @@ with this program; if not, write to the Free Software Foundation, Inc.,

    - Gérer le club + {% trans " Manage the club" %}

    - Gérer admin et membres - + {% trans "Manage the admins and members" %} +
    -
    -

    Administrateurs du club

    +
    +

    {% trans "Club admins" %}

    - - - + + + {% for admin in users.club.administrators.all %} @@ -290,14 +288,14 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endfor %}
    NomPrenomPseudo{% trans "Surname" %}{% trans "First name" %}{% trans "Username" %}
    -

    Membres

    +

    {% trans "Members" %}

    - - - + + + {% for admin in users.club.members.all %} @@ -317,22 +315,22 @@ with this program; if not, write to the Free Software Foundation, Inc.,

    - Machines + {% trans "Machines" %} {{nb_machines}}

    {% if machines_list %} {% include "machines/aff_machines.html" with machines_list=machines_list %} {% else %} -

    Aucune machine

    +

    {% trans "No machine" %}

    {% endif %}
    @@ -341,7 +339,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,

    - Cotisations + {% trans "Subscriptions" %}

    @@ -349,12 +347,12 @@ with this program; if not, write to the Free Software Foundation, Inc., {% can_create Facture %} - Ajouter une cotisation + {% trans "Add as subscription" %} {% if user_solde %} - Modifier le solde + {% trans "Edit the balance" %} {% endif%} {% acl_end %} @@ -363,7 +361,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% if facture_list %} {% include "cotisations/aff_cotisations.html" with facture_list=facture_list %} {% else %} -

    Aucune facture

    +

    {% trans "No invoice" %}

    {% endif %}
    @@ -372,15 +370,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,

    - Bannissements + {% trans "Bans" %}

    -
    - {% can_create Ban %} - - - Ajouter un bannissement + @@ -388,7 +386,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% if ban_list %} {% include "users/aff_bans.html" with ban_list=ban_list %} {% else %} -

    Aucun bannissement

    +

    {% trans "No ban" %}

    {% endif %}
    @@ -397,23 +395,23 @@ with this program; if not, write to the Free Software Foundation, Inc.,

    - Accès à titre gracieux + {% trans "Whitelists" %}

    {% if white_list %} {% include "users/aff_whitelists.html" with white_list=white_list %} {% else %} -

    Aucun accès gracieux

    +

    {% trans "No whitelist" %}

    {% endif %}
    @@ -421,14 +419,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,

    - Email settings + {% trans " Email settings" %}

    @@ -437,22 +435,22 @@ with this program; if not, write to the Free Software Foundation, Inc.,
    NomPrenomPseudo{% trans "Surname" %}{% trans "First name" %}{% trans "Username" %}
    - + - + - +
    Contact email address{% trans "Contact email address" %} {{ users.get_mail }}
    Enable the local email account{% trans "Enable the local email account" %} {{ users.local_email_enabled | tick }}Enable the local email redirection{% trans "Enable the local email redirection" %} {{ users.local_email_redirect | tick }}
    -

    {% blocktrans %}The Contact email is the email address where we send email to contact you. If you would like to use your external email address for that, you can either disable your local email address or enable the local email redirection.{% endblocktrans %}

    +

    {% trans "The contact email address is the email address where we send emails to contact you. If you would like to use your external email address for that, you can either disable your local email address or enable the local email redirection." %}

    {% if users.local_email_enabled %} {% can_create EMailAddress users.id %} - Add an email address + {% trans " Add an email address" %} {% acl_end %} {% if emailaddress_list %} @@ -463,7 +461,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
    - +
    Contact email address{% trans "Contact email address" %} {{ users.get_mail }}
    @@ -474,3 +472,4 @@ with this program; if not, write to the Free Software Foundation, Inc.,
    {% endblock %} + diff --git a/users/templates/users/sidebar.html b/users/templates/users/sidebar.html index 0b21afa7..ab20365e 100644 --- a/users/templates/users/sidebar.html +++ b/users/templates/users/sidebar.html @@ -23,74 +23,76 @@ 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 i18n %} {% block sidebar %} {% if request.user.is_authenticated%} {% can_create Club %} - Créer un club/association + {% trans "Create a club or organisation" %} {% acl_end %} {% can_create Adherent %} - Créer un adhérent + {% trans "Create a user" %} {% acl_end %} {% endif %} {% can_view_all Club %} - Clubs et assos + {% trans "Clubs and organisations" %} {% acl_end %} {% can_view_all Adherent %} - Adherents + {% trans "Users" %} {% acl_end %} {% can_view_all Ban %} - Bannissements + {% trans "Bans" %} {% acl_end %} {% can_view_all Whitelist %} - Accès à titre gracieux + {% trans "Whitelists" %} {% acl_end %} {% can_view_all School %} - Établissements + {% trans "Schools" %} {% acl_end %} {% can_view_all ListShell %} - Liste des shells + {% trans "Shells" %} {% acl_end %} {% can_view_all ListRight %} - Groupes de droits + {% trans "Groups of rights" %} {% acl_end %} {% can_view_all ServiceUser %} - Gérer les service users - + {% trans "Service users" %} + {% acl_end %} {% can_change User state %} - Archiver en masse + {% trans "Massively archive" %} {% acl_end %} {% endblock %} + diff --git a/users/templates/users/user.html b/users/templates/users/user.html index 5e4048a3..1ed1a299 100644 --- a/users/templates/users/user.html +++ b/users/templates/users/user.html @@ -26,7 +26,8 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} {% load massive_bootstrap_form %} {% load static %} -{% block title %}Création et modification d'utilisateur{% endblock %} +{% load i18n %} +{% block title %}{% trans "Users" %}{% endblock %} {% block content %} {% bootstrap_form_errors userform %} @@ -41,11 +42,12 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endif %}
    {% if showCGU %} -

    En cliquant sur Créer ou modifier, l'utilisateur s'engage à respecter les règles d'utilisation du réseau.

    -

    Résumé des règles d'utilisations

    +

    {% trans "By clicking 'Create or edit', the user commits to respect the " %}{% trans "General Terms of Use" %}.

    +

    {% trans "Summary of the General Terms of Use" %}

    {{ GTU_sum_up }}

    {% endif %}


    {% endblock %} + diff --git a/users/views.py b/users/views.py index 5279dbb0..af311ceb 100644 --- a/users/views.py +++ b/users/views.py @@ -46,6 +46,7 @@ from django.db import transaction from django.http import HttpResponse from django.http import HttpResponseRedirect from django.views.decorators.csrf import csrf_exempt +from django.utils.translation import ugettext as _ from rest_framework.renderers import JSONRenderer from reversion import revisions as reversion @@ -118,8 +119,8 @@ def new_user(request): if user.is_valid(): user = user.save() user.reset_passwd_mail(request) - messages.success(request, "L'utilisateur %s a été crée, un mail\ - pour l'initialisation du mot de passe a été envoyé" % user.pseudo) + messages.success(request, _("The user %s was created, an email to set" + " the password was sent.") % user.pseudo) return redirect(reverse( 'users:profil', kwargs={'userid': str(user.id)} @@ -130,7 +131,7 @@ def new_user(request): 'GTU_sum_up': GTU_sum_up, 'GTU': GTU, 'showCGU': True, - 'action_name': 'Créer un utilisateur' + 'action_name': _("Create a user") }, 'users/user.html', request @@ -147,14 +148,14 @@ def new_club(request): club = club.save(commit=False) club.save() club.reset_passwd_mail(request) - messages.success(request, "L'utilisateur %s a été crée, un mail\ - pour l'initialisation du mot de passe a été envoyé" % club.pseudo) + messages.success(request, _("The club %s was created, an email to set" + " the password was sent.") % club.pseudo) return redirect(reverse( 'users:profil', kwargs={'userid': str(club.id)} )) return form( - {'userform': club, 'showCGU': False, 'action_name': 'Créer un club'}, + {'userform': club, 'showCGU': False, 'action_name': _("Create a club")}, 'users/user.html', request ) @@ -172,7 +173,7 @@ def edit_club_admin_members(request, club_instance, **_kwargs): if club.is_valid(): if club.changed_data: club.save() - messages.success(request, "Le club a bien été modifié") + messages.success(request, _("The club was edited.")) return redirect(reverse( 'users:profil', kwargs={'userid': str(club_instance.id)} @@ -181,7 +182,7 @@ def edit_club_admin_members(request, club_instance, **_kwargs): { 'userform': club, 'showCGU': False, - 'action_name': 'Editer les admin et membres' + 'action_name': _("Edit the admins and members") }, 'users/user.html', request @@ -209,13 +210,13 @@ def edit_info(request, user, userid): if user_form.is_valid(): if user_form.changed_data: user_form.save() - messages.success(request, "L'user a bien été modifié") + messages.success(request, _("The user was edited.")) return redirect(reverse( 'users:profil', kwargs={'userid': str(userid)} )) return form( - {'userform': user_form, 'action_name': "Editer l'utilisateur"}, + {'userform': user_form, 'action_name': _("Edit the user")}, 'users/user.html', request ) @@ -229,13 +230,13 @@ def state(request, user, userid): if state_form.is_valid(): if state_form.changed_data: state_form.save() - messages.success(request, "Etat changé avec succès") + messages.success(request, _("The state was edited.")) return redirect(reverse( 'users:profil', kwargs={'userid': str(userid)} )) return form( - {'userform': state_form, 'action_name': "Editer l'état"}, + {'userform': state_form, 'action_name': _("Edit the state")}, 'users/user.html', request ) @@ -250,13 +251,13 @@ def groups(request, user, userid): if group_form.is_valid(): if group_form.changed_data: group_form.save() - messages.success(request, "Groupes changés avec succès") + messages.success(request, _("The groups were edited.")) return redirect(reverse( 'users:profil', kwargs={'userid': str(userid)} )) return form( - {'userform': group_form, 'action_name': 'Editer les groupes'}, + {'userform': group_form, 'action_name': _("Edit the groups")}, 'users/user.html', request ) @@ -272,13 +273,13 @@ def password(request, user, userid): if u_form.is_valid(): if u_form.changed_data: u_form.save() - messages.success(request, "Le mot de passe a changé") + messages.success(request, _("The password was changed.")) return redirect(reverse( 'users:profil', kwargs={'userid': str(userid)} )) return form( - {'userform': u_form, 'action_name': 'Changer le mot de passe'}, + {'userform': u_form, 'action_name': _("Change the password")}, 'users/user.html', request ) @@ -290,7 +291,7 @@ def del_group(request, user, listrightid, **_kwargs): """ View used to delete a group """ user.groups.remove(ListRight.objects.get(id=listrightid)) user.save() - messages.success(request, "Droit supprimé à %s" % user) + messages.success(request, _("%s was removed from the group.") % user) return HttpResponseRedirect(request.META.get('HTTP_REFERER')) @@ -300,7 +301,7 @@ def del_superuser(request, user, **_kwargs): """Remove the superuser right of an user.""" user.is_superuser = False user.save() - messages.success(request, "%s n'est plus superuser" % user) + messages.success(request, _("%s is no longer superuser.") % user) return HttpResponseRedirect(request.META.get('HTTP_REFERER')) @@ -313,11 +314,11 @@ def new_serviceuser(request): user.save() messages.success( request, - "L'utilisateur a été crée" + _("The service user was created.") ) return redirect(reverse('users:index-serviceusers')) return form( - {'userform': user, 'action_name': 'Créer un serviceuser'}, + {'userform': user, 'action_name': _("Create a service user")}, 'users/user.html', request ) @@ -334,10 +335,10 @@ def edit_serviceuser(request, serviceuser, **_kwargs): if serviceuser.is_valid(): if serviceuser.changed_data: serviceuser.save() - messages.success(request, "L'user a bien été modifié") + messages.success(request, _("The service user was edited.")) return redirect(reverse('users:index-serviceusers')) return form( - {'userform': serviceuser, 'action_name': 'Editer un serviceuser'}, + {'userform': serviceuser, 'action_name': _("Edit a service user")}, 'users/user.html', request ) @@ -349,10 +350,10 @@ def del_serviceuser(request, serviceuser, **_kwargs): """Suppression d'un ou plusieurs serviceusers""" if request.method == "POST": serviceuser.delete() - messages.success(request, "L'user a été détruit") + messages.success(request, _("The service user was deleted.")) return redirect(reverse('users:index-serviceusers')) return form( - {'objet': serviceuser, 'objet_name': 'serviceuser'}, + {'objet': serviceuser, 'objet_name': 'service user'}, 'users/delete.html', request ) @@ -369,7 +370,7 @@ def add_ban(request, user, userid): ban = BanForm(request.POST or None, instance=ban_instance) if ban.is_valid(): ban.save() - messages.success(request, "Bannissement ajouté") + messages.success(request, _("The ban was added.")) return redirect(reverse( 'users:profil', kwargs={'userid': str(userid)} @@ -377,10 +378,10 @@ def add_ban(request, user, userid): if user.is_ban(): messages.error( request, - "Attention, cet utilisateur a deja un bannissement actif" + _("Warning: this user already has an active ban.") ) return form( - {'userform': ban, 'action_name': 'Ajouter un ban'}, + {'userform': ban, 'action_name': _("Add a ban")}, 'users/user.html', request ) @@ -396,10 +397,10 @@ def edit_ban(request, ban_instance, **_kwargs): if ban.is_valid(): if ban.changed_data: ban.save() - messages.success(request, "Bannissement modifié") + messages.success(request, _("The ban was edited.")) return redirect(reverse('users:index')) return form( - {'userform': ban, 'action_name': 'Editer un ban'}, + {'userform': ban, 'action_name': _("Edit a ban")}, 'users/user.html', request ) @@ -411,7 +412,7 @@ def del_ban(request, ban, **_kwargs): """ Supprime un banissement""" if request.method == "POST": ban.delete() - messages.success(request, "Le banissement a été supprimé") + messages.success(request, _("The ban was deleted.")) return redirect(reverse( 'users:profil', kwargs={'userid': str(ban.user.id)} @@ -438,7 +439,7 @@ def add_whitelist(request, user, userid): ) if whitelist.is_valid(): whitelist.save() - messages.success(request, "Accès à titre gracieux accordé") + messages.success(request, _("The whitelist was added.")) return redirect(reverse( 'users:profil', kwargs={'userid': str(userid)} @@ -446,10 +447,10 @@ def add_whitelist(request, user, userid): if user.is_whitelisted(): messages.error( request, - "Attention, cet utilisateur a deja un accès gracieux actif" + _("Warning: this user already has an active whitelist.") ) return form( - {'userform': whitelist, 'action_name': 'Ajouter une whitelist'}, + {'userform': whitelist, 'action_name': _("Add a whitelist")}, 'users/user.html', request ) @@ -469,10 +470,10 @@ def edit_whitelist(request, whitelist_instance, **_kwargs): if whitelist.is_valid(): if whitelist.changed_data: whitelist.save() - messages.success(request, "Whitelist modifiée") + messages.success(request, _("The whitelist was edited.")) return redirect(reverse('users:index')) return form( - {'userform': whitelist, 'action_name': 'Editer une whitelist'}, + {'userform': whitelist, 'action_name': _("Edit a whitelist")}, 'users/user.html', request ) @@ -484,7 +485,7 @@ def del_whitelist(request, whitelist, **_kwargs): """ Supprime un acces gracieux""" if request.method == "POST": whitelist.delete() - messages.success(request, "L'accés gracieux a été supprimé") + messages.success(request, _("The whitelist was deleted.")) return redirect(reverse( 'users:profil', kwargs={'userid': str(whitelist.user.id)} @@ -508,7 +509,7 @@ def add_emailaddress(request, user, userid): ) if emailaddress.is_valid(): emailaddress.save() - messages.success(request, "Local email account created") + messages.success(request, _("The local email account was created.")) return redirect(reverse( 'users:profil', kwargs={'userid': str(userid)} @@ -516,7 +517,7 @@ def add_emailaddress(request, user, userid): return form( {'userform': emailaddress, 'showCGU': False, - 'action_name': 'Add a local email account'}, + 'action_name': _("Add a local email account")}, 'users/user.html', request ) @@ -533,7 +534,7 @@ def edit_emailaddress(request, emailaddress_instance, **_kwargs): if emailaddress.is_valid(): if emailaddress.changed_data: emailaddress.save() - messages.success(request, "Local email account modified") + messages.success(request, _("The local email account was edited.")) return redirect(reverse( 'users:profil', kwargs={'userid': str(emailaddress_instance.user.id)} @@ -541,8 +542,7 @@ def edit_emailaddress(request, emailaddress_instance, **_kwargs): return form( {'userform': emailaddress, 'showCGU': False, - 'action_name': 'Edit a local email account', - }, + 'action_name': _("Edit a local email account")}, 'users/user.html', request ) @@ -554,7 +554,7 @@ def del_emailaddress(request, emailaddress, **_kwargs): """Delete a local email account""" if request.method == "POST": emailaddress.delete() - messages.success(request, "Local email account deleted") + messages.success(request, _("The local email account was deleted.")) return redirect(reverse( 'users:profil', kwargs={'userid': str(emailaddress.user.id)} @@ -578,7 +578,7 @@ def edit_email_settings(request, user_instance, **_kwargs): if email_settings.is_valid(): if email_settings.changed_data: email_settings.save() - messages.success(request, "Email settings updated") + messages.success(request, _("The email settings were edited.")) return redirect(reverse( 'users:profil', kwargs={'userid': str(user_instance.id)} @@ -587,7 +587,7 @@ def edit_email_settings(request, user_instance, **_kwargs): {'userform': email_settings, 'showCGU': False, 'load_js_file': '/static/js/email_address.js', - 'action_name': 'Edit the email settings'}, + 'action_name': _("Edit the email settings")}, 'users/user.html', request ) @@ -601,10 +601,10 @@ def add_school(request): school = SchoolForm(request.POST or None) if school.is_valid(): school.save() - messages.success(request, "L'établissement a été ajouté") + messages.success(request, _("The school was added.")) return redirect(reverse('users:index-school')) return form( - {'userform': school, 'action_name': 'Ajouter'}, + {'userform': school, 'action_name': _("Add a school")}, 'users/user.html', request ) @@ -619,10 +619,10 @@ def edit_school(request, school_instance, **_kwargs): if school.is_valid(): if school.changed_data: school.save() - messages.success(request, "Établissement modifié") + messages.success(request, _("The school was edited.")) return redirect(reverse('users:index-school')) return form( - {'userform': school, 'action_name': 'Editer'}, + {'userform': school, 'action_name': _("Edit a school")}, 'users/user.html', request ) @@ -641,15 +641,15 @@ def del_school(request, instances): for school_del in school_dels: try: school_del.delete() - messages.success(request, "L'établissement a été supprimé") + messages.success(request, _("The school was deleted.")) except ProtectedError: messages.error( request, - "L'établissement %s est affecté à au moins un user, \ - vous ne pouvez pas le supprimer" % school_del) + _("The school %s is assigned to at least one user," + " impossible to delete it.") % school_del) return redirect(reverse('users:index-school')) return form( - {'userform': school, 'action_name': 'Supprimer'}, + {'userform': school, 'action_name': _("Delete")}, 'users/user.html', request ) @@ -662,10 +662,10 @@ def add_shell(request): shell = ShellForm(request.POST or None) if shell.is_valid(): shell.save() - messages.success(request, "Le shell a été ajouté") + messages.success(request, _("The shell was added.")) return redirect(reverse('users:index-shell')) return form( - {'userform': shell, 'action_name': 'Ajouter'}, + {'userform': shell, 'action_name': _("Add a shell")}, 'users/user.html', request ) @@ -679,10 +679,10 @@ def edit_shell(request, shell_instance, **_kwargs): if shell.is_valid(): if shell.changed_data: shell.save() - messages.success(request, "Le shell a été modifié") + messages.success(request, _("The shell was edited.")) return redirect(reverse('users:index-shell')) return form( - {'userform': shell, 'action_name': 'Editer'}, + {'userform': shell, 'action_name': _("Edit a shell")}, 'users/user.html', request ) @@ -694,7 +694,7 @@ def del_shell(request, shell, **_kwargs): """Destruction d'un shell""" if request.method == "POST": shell.delete() - messages.success(request, "Le shell a été détruit") + messages.success(request, _("The shell was deleted.")) return redirect(reverse('users:index-shell')) return form( {'objet': shell, 'objet_name': 'shell'}, @@ -711,10 +711,10 @@ def add_listright(request): listright = NewListRightForm(request.POST or None) if listright.is_valid(): listright.save() - messages.success(request, "Le droit/groupe a été ajouté") + messages.success(request, _("The group of rights was added.")) return redirect(reverse('users:index-listright')) return form( - {'userform': listright, 'action_name': 'Ajouter'}, + {'userform': listright, 'action_name': _("Add a group of rights")}, 'users/user.html', request ) @@ -732,10 +732,10 @@ def edit_listright(request, listright_instance, **_kwargs): if listright.is_valid(): if listright.changed_data: listright.save() - messages.success(request, "Droit modifié") + messages.success(request, _("The group of rights was edited.")) return redirect(reverse('users:index-listright')) return form( - {'userform': listright, 'action_name': 'Editer'}, + {'userform': listright, 'action_name': _("Edit a group of rights")}, 'users/user.html', request ) @@ -752,15 +752,16 @@ def del_listright(request, instances): for listright_del in listright_dels: try: listright_del.delete() - messages.success(request, "Le droit/groupe a été supprimé") + messages.success(request, _("The group of rights was" + " deleted.")) except ProtectedError: messages.error( request, - "Le groupe %s est affecté à au moins un user, \ - vous ne pouvez pas le supprimer" % listright_del) + _("The group of rights %s is assigned to at least one" + " user, impossible to delete it.") % listright_del) return redirect(reverse('users:index-listright')) return form( - {'userform': listright, 'action_name': 'Supprimer'}, + {'userform': listright, 'action_name': _("Delete")}, 'users/user.html', request ) @@ -784,8 +785,8 @@ def mass_archive(request): with transaction.atomic(), reversion.create_revision(): user.archive() user.save() - reversion.set_comment("Archivage") - messages.success(request, "%s users ont été archivés" % len( + reversion.set_comment(_("Archiving")) + messages.success(request, _("%s users were archived.") % len( to_archive_list )) return redirect(reverse('users:index')) @@ -1034,18 +1035,17 @@ def reset_password(request): email=userform.cleaned_data['email'] ) except User.DoesNotExist: - messages.error(request, "Cet utilisateur n'existe pas") + messages.error(request, _("The user doesn't exist.")) return form( - {'userform': userform, 'action_name': 'Réinitialiser'}, + {'userform': userform, 'action_name': _("Reset")}, 'users/user.html', request ) user.reset_passwd_mail(request) - messages.success(request, "Un mail pour l'initialisation du mot\ - de passe a été envoyé") + messages.success(request, _("An email to reset the password was sent.")) redirect(reverse('index')) return form( - {'userform': userform, 'action_name': 'Réinitialiser'}, + {'userform': userform, 'action_name': _("Reset")}, 'users/user.html', request ) @@ -1059,7 +1059,7 @@ def process(request, token): if req.type == Request.PASSWD: return process_passwd(request, req) else: - messages.error(request, "Entrée incorrecte, contactez un admin") + messages.error(request, _("Error: please contact an admin.")) redirect(reverse('index')) @@ -1071,12 +1071,12 @@ def process_passwd(request, req): if u_form.is_valid(): with transaction.atomic(), reversion.create_revision(): u_form.save() - reversion.set_comment("Réinitialisation du mot de passe") + reversion.set_comment(_("Password reset")) req.delete() - messages.success(request, "Le mot de passe a changé") + messages.success(request, _("The password was changed.")) return redirect(reverse('index')) return form( - {'userform': u_form, 'action_name': 'Changer le mot de passe'}, + {'userform': u_form, 'action_name': _("Change the password")}, 'users/user.html', request ) @@ -1111,7 +1111,7 @@ def ml_std_members(request, ml_name): members = all_has_access().values('email').distinct() # Unknown mailing else: - messages.error(request, "Cette mailing n'existe pas") + messages.error(request, _("The mailing list doesn't exist.")) return redirect(reverse('index')) seria = MailingMemberSerializer(members, many=True) return JSONResponse(seria.data) @@ -1135,7 +1135,7 @@ def ml_club_admins(request, ml_name): try: club = Club.objects.get(mailing=True, pseudo=ml_name) except Club.DoesNotExist: - messages.error(request, "Cette mailing n'existe pas") + messages.error(request, _("The mailing list doesn't exist.")) return redirect(reverse('index')) members = club.administrators.all().values('email').distinct() seria = MailingMemberSerializer(members, many=True) @@ -1150,7 +1150,7 @@ def ml_club_members(request, ml_name): try: club = Club.objects.get(mailing=True, pseudo=ml_name) except Club.DoesNotExist: - messages.error(request, "Cette mailing n'existe pas") + messages.error(request, _("The mailing list doesn't exist.")) return redirect(reverse('index')) members = ( club.administrators.all().values('email').distinct() | @@ -1158,3 +1158,4 @@ def ml_club_members(request, ml_name): ) seria = MailingMemberSerializer(members, many=True) return JSONResponse(seria.data) + From 34bf50e7a9e81619edc384f1f01179f8f0832447 Mon Sep 17 00:00:00 2001 From: Laouen Fernet Date: Sun, 5 Aug 2018 18:48:22 +0200 Subject: [PATCH 143/171] Translation of machines/ (front) --- machines/acl.py | 5 +- machines/forms.py | 56 +- machines/locale/fr/LC_MESSAGES/django.mo | Bin 0 -> 32489 bytes machines/locale/fr/LC_MESSAGES/django.po | 1748 +++++++++++++++++ .../migrations/0094_auto_20180815_1918.py | 221 +++ machines/models.py | 427 ++-- machines/templates/machines/aff_alias.html | 21 +- machines/templates/machines/aff_dname.html | 26 +- .../templates/machines/aff_extension.html | 14 +- machines/templates/machines/aff_iptype.html | 19 +- machines/templates/machines/aff_ipv6.html | 33 +- machines/templates/machines/aff_machines.html | 119 +- .../templates/machines/aff_machinetype.html | 25 +- machines/templates/machines/aff_mx.html | 31 +- machines/templates/machines/aff_nas.html | 37 +- machines/templates/machines/aff_ns.html | 27 +- machines/templates/machines/aff_role.html | 14 +- machines/templates/machines/aff_servers.html | 29 +- machines/templates/machines/aff_service.html | 37 +- machines/templates/machines/aff_soa.html | 43 +- machines/templates/machines/aff_srv.html | 51 +- machines/templates/machines/aff_sshfp.html | 35 +- machines/templates/machines/aff_txt.html | 29 +- machines/templates/machines/aff_vlan.html | 34 +- machines/templates/machines/delete.html | 9 +- .../templates/machines/edit_portlist.html | 11 +- machines/templates/machines/index.html | 5 +- machines/templates/machines/index_alias.html | 17 +- .../templates/machines/index_extension.html | 47 +- machines/templates/machines/index_iptype.html | 9 +- machines/templates/machines/index_ipv6.html | 7 +- .../templates/machines/index_machinetype.html | 9 +- machines/templates/machines/index_nas.html | 12 +- .../templates/machines/index_portlist.html | 28 +- machines/templates/machines/index_role.html | 8 +- .../templates/machines/index_service.html | 11 +- machines/templates/machines/index_sshfp.html | 17 +- machines/templates/machines/index_vlan.html | 9 +- machines/templates/machines/machine.html | 40 +- machines/templates/machines/sidebar.html | 17 +- machines/views.py | 276 ++- 41 files changed, 2814 insertions(+), 799 deletions(-) create mode 100644 machines/locale/fr/LC_MESSAGES/django.mo create mode 100644 machines/locale/fr/LC_MESSAGES/django.po create mode 100644 machines/migrations/0094_auto_20180815_1918.py diff --git a/machines/acl.py b/machines/acl.py index 1b74760c..45cb6ec2 100644 --- a/machines/acl.py +++ b/machines/acl.py @@ -25,6 +25,7 @@ Here are defined some functions to check acl on the application. """ +from django.utils.translation import ugettext as _ def can_view(user): @@ -38,4 +39,6 @@ def can_view(user): viewing is granted and msg is a message (can be None). """ can = user.has_module_perms('machines') - return can, None if can else "Vous ne pouvez pas voir cette application." + return can, None if can else _("You don't have the right to view this" + " application.") + diff --git a/machines/forms.py b/machines/forms.py index b68cb203..4af060d3 100644 --- a/machines/forms.py +++ b/machines/forms.py @@ -37,7 +37,7 @@ from __future__ import unicode_literals from django.forms import ModelForm, Form from django import forms -from django.utils.translation import ugettext_lazy as _l +from django.utils.translation import ugettext_lazy as _ from re2o.field_permissions import FieldPermissionFormMixin from re2o.mixins import FormRevMixin @@ -75,7 +75,7 @@ class EditMachineForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(EditMachineForm, self).__init__(*args, prefix=prefix, **kwargs) - self.fields['name'].label = 'Nom de la machine' + self.fields['name'].label = _("Machine name") class NewMachineForm(EditMachineForm): @@ -94,12 +94,11 @@ class EditInterfaceForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): prefix = kwargs.pop('prefix', self.Meta.model.__name__) user = kwargs.get('user') super(EditInterfaceForm, self).__init__(*args, prefix=prefix, **kwargs) - self.fields['mac_address'].label = 'Adresse mac' - self.fields['type'].label = 'Type de machine' - self.fields['type'].empty_label = "Séléctionner un type de machine" + self.fields['mac_address'].label = _("MAC address") + self.fields['type'].label = _("Machine type") + self.fields['type'].empty_label = _("Select a machine type") if "ipv4" in self.fields: - self.fields['ipv4'].empty_label = ("Assignation automatique de " - "l'ipv4") + self.fields['ipv4'].empty_label = _("Automatic IPv4 assignment") self.fields['ipv4'].queryset = IpList.objects.filter( interface__isnull=True ) @@ -170,7 +169,7 @@ class DelAliasForm(FormRevMixin, Form): """Suppression d'un ou plusieurs objets alias""" alias = forms.ModelMultipleChoiceField( queryset=Domain.objects.all(), - label="Alias actuels", + label=_("Current aliases"), widget=forms.CheckboxSelectMultiple ) @@ -191,15 +190,15 @@ class MachineTypeForm(FormRevMixin, ModelForm): def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(MachineTypeForm, self).__init__(*args, prefix=prefix, **kwargs) - self.fields['type'].label = 'Type de machine à ajouter' - self.fields['ip_type'].label = "Type d'ip relié" + self.fields['type'].label = _("Machine type to add") + self.fields['ip_type'].label = _("Related IP type") class DelMachineTypeForm(FormRevMixin, Form): """Suppression d'un ou plusieurs machinetype""" machinetypes = forms.ModelMultipleChoiceField( queryset=MachineType.objects.none(), - label="Types de machines actuelles", + label=_("Current machine types"), widget=forms.CheckboxSelectMultiple ) @@ -222,7 +221,7 @@ class IpTypeForm(FormRevMixin, ModelForm): def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(IpTypeForm, self).__init__(*args, prefix=prefix, **kwargs) - self.fields['type'].label = 'Type ip à ajouter' + self.fields['type'].label = _("IP type to add") class EditIpTypeForm(IpTypeForm): @@ -239,7 +238,7 @@ class DelIpTypeForm(FormRevMixin, Form): """Suppression d'un ou plusieurs iptype""" iptypes = forms.ModelMultipleChoiceField( queryset=IpType.objects.none(), - label="Types d'ip actuelles", + label=_("Current IP types"), widget=forms.CheckboxSelectMultiple ) @@ -261,17 +260,17 @@ class ExtensionForm(FormRevMixin, ModelForm): def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(ExtensionForm, self).__init__(*args, prefix=prefix, **kwargs) - self.fields['name'].label = 'Extension à ajouter' - self.fields['origin'].label = 'Enregistrement A origin' - self.fields['origin_v6'].label = 'Enregistrement AAAA origin' - self.fields['soa'].label = 'En-tête SOA à utiliser' + self.fields['name'].label = _("Extension to add") + self.fields['origin'].label = _("A record origin") + self.fields['origin_v6'].label = _("AAAA record origin") + self.fields['soa'].label = _("SOA record to use") class DelExtensionForm(FormRevMixin, Form): """Suppression d'une ou plusieurs extensions""" extensions = forms.ModelMultipleChoiceField( queryset=Extension.objects.none(), - label="Extensions actuelles", + label=_("Current extensions"), widget=forms.CheckboxSelectMultiple ) @@ -310,7 +309,7 @@ class DelSOAForm(FormRevMixin, Form): """Suppression d'un ou plusieurs SOA""" soa = forms.ModelMultipleChoiceField( queryset=SOA.objects.none(), - label="SOA actuels", + label=_("Current SOA records"), widget=forms.CheckboxSelectMultiple ) @@ -341,7 +340,7 @@ class DelMxForm(FormRevMixin, Form): """Suppression d'un ou plusieurs MX""" mx = forms.ModelMultipleChoiceField( queryset=Mx.objects.none(), - label="MX actuels", + label=_("Current MX records"), widget=forms.CheckboxSelectMultiple ) @@ -374,7 +373,7 @@ class DelNsForm(FormRevMixin, Form): """Suppresion d'un ou plusieurs NS""" ns = forms.ModelMultipleChoiceField( queryset=Ns.objects.none(), - label="Enregistrements NS actuels", + label=_("Current NS records"), widget=forms.CheckboxSelectMultiple ) @@ -402,7 +401,7 @@ class DelTxtForm(FormRevMixin, Form): """Suppression d'un ou plusieurs TXT""" txt = forms.ModelMultipleChoiceField( queryset=Txt.objects.none(), - label="Enregistrements Txt actuels", + label=_("Current TXT records"), widget=forms.CheckboxSelectMultiple ) @@ -430,7 +429,7 @@ class DelDNameForm(FormRevMixin, Form): """Delete a set of DNAME entries""" dnames = forms.ModelMultipleChoiceField( queryset=Txt.objects.none(), - label="Existing DNAME entries", + label=_("Current DNAME records"), widget=forms.CheckboxSelectMultiple ) @@ -458,7 +457,7 @@ class DelSrvForm(FormRevMixin, Form): """Suppression d'un ou plusieurs Srv""" srv = forms.ModelMultipleChoiceField( queryset=Srv.objects.none(), - label="Enregistrements Srv actuels", + label=_("Current SRV records"), widget=forms.CheckboxSelectMultiple ) @@ -487,7 +486,7 @@ class DelNasForm(FormRevMixin, Form): """Suppression d'un ou plusieurs nas""" nas = forms.ModelMultipleChoiceField( queryset=Nas.objects.none(), - label="Enregistrements Nas actuels", + label=_("Current NAS devices"), widget=forms.CheckboxSelectMultiple ) @@ -519,7 +518,7 @@ class DelRoleForm(FormRevMixin, Form): """Deletion of one or several roles.""" role = forms.ModelMultipleChoiceField( queryset=Role.objects.none(), - label=_l("Current roles"), + label=_("Current roles"), widget=forms.CheckboxSelectMultiple ) @@ -560,7 +559,7 @@ class DelServiceForm(FormRevMixin, Form): """Suppression d'un ou plusieurs service""" service = forms.ModelMultipleChoiceField( queryset=Service.objects.none(), - label="Services actuels", + label=_("Current services"), widget=forms.CheckboxSelectMultiple ) @@ -588,7 +587,7 @@ class DelVlanForm(FormRevMixin, Form): """Suppression d'un ou plusieurs vlans""" vlan = forms.ModelMultipleChoiceField( queryset=Vlan.objects.none(), - label="Vlan actuels", + label=_("Current VLANs"), widget=forms.CheckboxSelectMultiple ) @@ -646,3 +645,4 @@ class SshFpForm(FormRevMixin, ModelForm): prefix=prefix, **kwargs ) + diff --git a/machines/locale/fr/LC_MESSAGES/django.mo b/machines/locale/fr/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..c9696d928833fc70692673eb34d0416314ae069c GIT binary patch literal 32489 zcmchf37lM2o$oI~2!sFN5CSfh?&?k@)m2U1s!qa= zg2=!V2Si65lo3Y_vW?=dCI=_x+vaF12)b@O@r=a{7DE zzW>kvoO^EmZQ5S91^jN_BM4p(-!nA`j(klJOr0UuAh`ONAUGIa0}q3@!Gqx4a6kAU zJOn-l748?#{kQM{?61HB;Q`0ma7RJKU+B0TPQ!kI;}$p_`xS71cpFssJK^#0Gw^8m zQ%KUm%dic;{y1x225|}o9dB^F8>)W)0*`{vLdD;YM3rs_f8^f-m3|pcf;Yn>;BC(T zeyDnW8!Fr{q1t7SnL#ic9uAf6*--T!fCLGuQ1yI2RJ+{`C&SOfDe!Ah?e#4<3H}ty z|KA}+4SogH&Qnhaf=+l0R2Qv5a~y&S zcP%^|eh@1B15n|=1C{=BP~oP{vi`FnMGKZfwNn9-RdB82C!z9r-0?T?NbFN-Eb&CB zc03m<+-Aq?;AHF{fohk#oc#+>{reC+6g~;nA1^!m0Vi7f@lfOEWcXUR0xJ9k&VAUq zUj{Y4Zh|V$hoI8?vg3E5+VM%pKSDE}=h$#ZIWB-I=L#6Ybx`?@LgjxGRR7-&mHz$C z{)n^x*zp<1SD?y&aJ$X#7^wL3q0(Ij<-ZXs{mpPcxC2gw*FpK;;&=yC`R;}Lz^_2n z>z|?0`x)FDz5vz#e}F3IK^@k8Cfo~qJ5>MAhp&U@Iu1ah1Xn}4BDe?ARKbs({WT0) z+1nj2aC{roxVi^w96j#re}D|N;K;c)|K)IB?A=iHT@O|MA*gY3IeZPg7EXcJLxsP^ z`TspU0Q*Ow>h)=;_>V$`e*#X0PeX(9^q+(b zo#6LS?Rv}tTfY@hUalKzuW`W9^Z#5&(l!j==V_L z;xADBzvn_*j(IS|-UX?0u*unPb@m6K+WjebAp9?=@=2JfTvMU^4|6;Qn(+rEFXltZ z#q;4wum)BBPeP5)Ps2mtWAGsO3#jy7fE(bSp~}CG$Y;SfLqt>XVW@n*0W}W34HfPQ zI0~O~_KO$WcF04?y9jFjd;lH-{{gBzpM{6P2jQXchfwu>2Cj#Hf|Fp+8*IHdK-o7z zwZ~?ta#x}1ag}rbTgTg-|4zpTocmXy#>2Ou>hT0rzdi%ie}90=fA5on;9~eDcmez( zR5_=eV&(oUsCu0P6@N8Uy)JU>ckWx^4BWRvrGE=ldGCOdtDl0Z&v&8HdBXA6a8K+n zLDlnRsBn{(*nAIyO7Cze_Y$2PkRo?AT{cxM(y-@w| zC{#QD0;)cHo^5$FRQjhvyn^*m_4+%1@JiST zzXwAYEVJY97`PYKPN;FU(7B)K+`FOX;rUSW=mMzpHbedeU*eDEMX=nKV=_d91tFXR z``{9IH9Qf139f+u3D1KaSeW|3cF0f&?t@Id;5oPwo^TFr0Y@PJg1h;n@%uZ+HU_)= zH#)u-%Kvewc|D0lHo}V`OIh$axHo(nPKM9Jec?+`^G=028tex{I0q_RH`ILXgPMmq zxDPBl_wCO8IyepYTj9a*V^DJEi%@dln^5iiGpO{QhR4I-!I^Nz8*O>cgp2l|4Pc1- zmEBg~`!GBT`@>NE@k^-j@*>nY+MmIwd}c%Ce=5}YSpzkW^HAgTD#zQQ()*I*PodKL zBUCzvthV#!1gQ1vbf|KcU79teL1)n3oQN$?fNNj*0Heo*bZ7@B%P&F^cV=Glj!^7#pz1^4W=^=XHy&uZ8P zD~=y<{trX7=aW$7oziE^d935DxCu^u;YFX zoQ8b~R6nkQN^b}r1h>J1;SDf^w?nn_gN{FiO7A7dgE!dt^WY)4pXIm?DxWP-^}7OU zoZso(AA;)tAHeDGH_kr!LYv>Qj%PS-gc>KKj@LrvcMF^WKMoIo55d>M@4~6@mr&uJ zhlj#FH`;V&K!tCEli^~haA!cZV=q+uR^Sova;W;e4=Vg!Q1a$ea0dJmRQ-Mk75}%8 zr7M_r5o-|aflJ`c&i)vbe4Tu;<#Kom_P4`%@GFkb!}-{cev_5s=ff%3uY)SzyP?Yc z0jPev&-w3yufhHuXa645_;~^<-mjqIy$IDVlizIR%8^j%FM?{nGojkO8!FsJco-ak zIe0zP`1v(dx_^W!$DVJo{W={gpSe)&v6Y{ZQletIqxc)VK)-Z95$X569XD z)h=f__YDw{8x)}0{VP!8@9S_9eB9Z80@V&rLG|Y!pvK$uA=_SapvLd1P~lcU`CkZC zzG0~Lyd0`M-UU^@J7EOB07)|F*vy#)d=Cua0aSJloC_zzi=fi!hid1VbH5TQpLalw z=i8vd-3L{UhoIW$hfwos@)ldqLmiKGoC{U{)1cz5fog|-sCWfu-v&>>eg&KjKMH5T z@4)r&zoF*W>aBL(ZG~#L8=>UUUGQ}H6jc1V;MW}T4tJ(UsLAA%(&b}2Y-FHBh@1yWc_z+Y&dyLw3q!-S`em$HDKL_`M zk3fyDZ#wrMJNGByB;0=u4}iacz3?xv5B6-c{vU_xk9%Mj{2@FWPTy|#3mc*G{Rr%V zkHZV#OeWh|@CtYyd=M^&`(A3(Ujxs@{!yrWpMz(>uFLE>%hgbF>$8q8!-d$-xZK9C zzzeZI3f};axx&ul3*kQ4b8ug{1x|rgcqF_Es=scBli`=)f$$OdTKEH~_)kH#+w*W5 z{4+cP9(bkguM^?H*jGULZ-k0J3=e`=LiPK*p~mrN;ePPva1W^8M=_dPN8r#Q2Yxd! z%P=Z)4s);%%(H%%I{Q(OCNpExc(^}19nW{+R0s73%@@1}b0Wt7KF&jla3^7Y#`A|U zAHkfBeF5esjDD)mS1>0y`$aroi}@$aCowl;euU9)0cM>Gr}5gy^9}Og_XeIn4gbT~ z?0L{++%!&~!OZ9ROYkcAIQ&UR7d!{E zp65p}w_)@{RGHt=j{gilfZ0k|&8auT55jerJkR>wgL$KSmi-mXmDtzH!fy@Fd)vq0 z5*O}x_;%brgE^2seazf#=(e3%VZm#XSEC^KOjB+8;3f_c|V? z6Xq?L8!-C)9}Ba8`rkai4|5TIuXFxCbpHD|`!M!1c>WF4T+?r{^E(jE!MqD|7w)?- zt9b5*T^P;Xjo5F58sAT0p2g_*HrzjJALGB8JLkkU)?~uy_Ymef%-=YB2qnMtn`dGE zzSjAl2=Bv1bN54-0qk=zU*}o-`SfoymKtU~?!6ez>$UJS%skAOu^$4z1~+2-uZY{D zSZ2V};VkEIIre{a&xiA@-y1RA=E=(Ci*bM1*@Y1EOU#Y9OMcD7=qFjB-yN9a@EgUv zljmbF-^1vKs4%iq&x+CPVYuxjn;J(B=3p2>`_3%08 zHl94EexY-d(-xjzkJ-X=#f8bk$1nwF_gG4} zYhB!&qx{a~c?0GNo-c!|;i;J4Vg3g5517@s`(M9%ew%anj`x6vrk?fKM%WR|fjrNI z?U)C7{!e-G`#t8rFkh9${g(I#*g^O=VGiK=EX>uI4VYi+8Ta&W8kS1R?E;>^h}$$c zTxy z;ooEQy99F(Zui2IF$;M<1&(0O<5|D$m}_|cn6cTl`g)#k#hivIW4?j;Z_NFe!!X~+ zJb+n+xe)UXjD9_s_ha6R`7`EQnD1irtCGjQ@C?jyp0C4vljjwfPhf7r6fpS|wL4luO=mIM=_q zP|7A#DHr1;qkKi#xZtI5<=S+PvX3qfa{~jFd=$ljR&B@@qZF?0t#|2N)79YAv%bNp z_uO!>P#VftMk<9;HO?VdETGrh7Al4L?bUoKGPPQkFXpTHuuPT8l`zVW<}0~krVWYK zJdmqeOaHi5Eko3GOY3mHHCl#9TBmi8xV2gaNybCVAQ~VoJxwd;TYAUM-ZDgbbhQkm z;nvdAjX(UC4;1MCRk>1bh~c_9pBZ{eAm!DY9X46dxK^nMBjrjp3bQk$txy`QC4Q8iD%PD0vQ8|I>@++WC5 z^8?|wLUpq#{iO`iBt)4^3Zn3%ZI!1gvfz z4r`HWU?z|GOS^TY=1(|Wi>l!!>QJ&J&4mNyDm96=hJ(x}YL_oDql^UUs#P~D*<7WN zD}~I?d}T0)L{dQY53Sjsuau~?t!u4X9wuhLI&P8Lq%c$(&NHc(61{(Oj#+Nv+2$S1 z6>H4l!4S{1kjY13kru9xRNh9pT=2Lk^?|6p4HLS7X#(I%_Z8Kl5~fkcF*U|jOdmEo zu;nva$ZxBY)?sAbLU|-# zLhhQjZW2gywu!Twtz-Bo1f5VUl(sg8ikFw>UK%xarE!CxMX-258Y5UN%$ubVuU5@G z6Vcq%H#&q%ca zAp@}?ZWS)xxS8qW#=wki{>Ud5oDC#w1r@FW1LmiW<5)};k?{?moH_? zks5M-bkW?=#lf=tV6IlIh8aIcxcA(L@6e$aThjTh2sV_3*~r?WcqL1q|7QeUQ%w{wn%LI2ufqHJl>$hPhqtz znVfi`Ln0F-mZRVtr1G}hj$q}oAW<{*Sdo?#>B)#qN1v8etTIa^K^vA$#mCX4rN?-^ z%vek|iC^C7UFq=7jr0b?9pzdWjpX|agF9GHoLW-KSGScbtn`(T$KhPGwLN5#7%da^ zuqxF>s-g|v7KOfwox#ddf3Y@z@@ORid3XhrCzQhi^3%6!XVAT@Yi+nG*T1zk;zfsX zB1_cL*a=%!hwVm{l>AN4uqg1Zu6YxOcy1+5$s|g-`Pq^m3^RS864>CQDm7tdoaL0+ok+>#t(dkJC=P z6X#(&l(LT67j_0~N}*=jtY|im)Fe4RqDPaB$6%#AYzk%MXPBc`bAyBI^YQ}-x8ad8 z8(S^-j4!)9CxOYsl{r|OuM8Imi7Mz%78GN`+u&9SNXs-C#uZ(w69D~*Lbs`lg0+=` zcJ4dk2j6dO=7;iCH)F$DRHjfZjM`bxAzWy7|Upddo^|4iMN> zqomNf17U%U{3vruy3lO0EmzC^7-NHOz|7!C~`AQCR)fYs4%MIm#njIcwCF-aK_JEV^_l4^5*Qv9(`#1 zN67(Bq~Xq~q-n@-md0D$LR7oDEuSxi^Ni$Je9}n^Pnu0O)yj^bM_Xqj!OTHN(7P&` zN69J?B##;wv|-96GS4`hJw@;N-C=*Rpp%qDfloaWy8bv!4kNWqMGj-O=6A?H+0&&h zM5V1uRNKif+r4tiAr;l`tm(ef3kbX1kk)km_2*Exb$p{X8up~M2B`6>(+`t8BgxQR#A@(3LhF@PmuG!F6+^CMO4bbd*pi!QhH zp?NPlxGh7AJ;wSYsYpuT08W zuXK8eHep*v3RR}5ax5M?6{Nqzm0DCJ^9E~0qb{^k7sE|ELc4^tn-1U@x4bQ~3u(EG z0BYzFBT{totUxJWhE%@SotX7X zrwDanzh$fm-;0Qv^{V+C!8 zvo1Sa>tZmgs^xN6EVJs(D(fJA@xoM`*)~$V!1G&0 zHREJA*-lDY6zrjZtwF5(Hx7ZUS0=}dJo>BGnM`U86>souT@uAjSKmi%nWj9NCRd+Q zlU*dQoY*x}kk~a{gvcmu=nsCmtsg;=)xsJxNqc1SNqc0nvRWH?#r4+V!9XF}Qf4rQ zWY8FhuAwih!m198=kj`Vb{!7AF#bPW1*4Mkd{S24}AS1~0N z1O17D^?FaLY(^^O!2){h1-XjeG%hhaX>*FmIgnA`w&iFZGO*1|B(k>e#lo4h^iX|^ z_EED@mAwPcv(HSDR6gdcvtU}wZh7o()z@j8QPIqHBX73xYKGB8oxufW?-yLixu(@4 zX)x_H`~gDZ5s%oVsjE6P9lh(+i9(eDH`tjGn;D?z5)_BXc=RTHGAoS zQy|Sl7jGg_e9+JiY9sx3lsW8~Kz24toWgF!H4-K_GQzotIZ3eDv4Le*&3Uq^1ZTRn z;jmO2-lVmOPPDUQGrLfVXpaN63$c4sH_c7qkpSVWnbGXd&@|fE$;N65GjDg&4mN7N zlvs2J)%BNo!dad9?YZHRV!pFn8JZnf4bdSA;(U{*C@{A*{E?Pl=d=vzIbSjUIwG=X zUQGhH?g$)>!kDo{-p0cY9ca0fGgB_e*w`&{4XDv6%DTxKRfj71RH`;{dyJBLjsjnF zB$PWo%r_pxxIjiEwhk39-a72mHK2}_10Cy8%sKL15-#XGX-ZFCh3{Au4HX7D&aMqb z9ew2`;gq#&`Z|{C(1d^;%Q)j9^t|~ecg#DvWBy6u{F9f=pEqaTf_d`@*wMosP$ifN zx42`$!f^iLB@0io;kr2+>F7iGU}5KF#*(l*SFWKzuh8YJYIs`FSUU%e^{l8iBHgHS zuyV$fZj_N4*X=s`^0{G(=Iu*RTHdmvXUeLTtClBqn%_BZ%2Hm5a1hxcb%1x*=zIyt)z7-uOC*f2qa_(4O>gNbgYQV{x3e_oG?c9R$pcRcfW4K59+N?CP-ZxZ1ytVMnOsCX>PA2XK0zQJ~uz8Q_;f8t_RHB z*`)Apv@}VfZROguE7H~6qBR=J;)9$+wT>UWd;AS|t4sJE?G14=OST;ir?=!vTyOz7Otuu<7HJ%%- z!*Ul0> zX5Y)K^fx&4%HLic=5h?}T{FS;4nb#Hx-?Atap_p+v?i*#BQVS(FKWz&j@O?x4%XeU zP-eWDM7+2z8EC7KmJ|e2dn;GnKru1xMuKeHY=v27iEN`KD$uw={V+EHZAw(VTCQQTn!=g?nYtacIBeMooScA6VM#}}$?q(mH47~IbY0}Gvt6L$PgviR#P?Dud zD6?KRaktuJzFCw^TV+e7c4C+Bw+PK*`@SsKP&jo{MB(jBZBh)o3^(!ct8o)Ay+Ji< zr@pFDI7URXlGws(HEKwN!x>*rCne07ThtiR2Gq=K9Gls;sp}7xwT5iD;+vH;EKTDF z(uy>JP%K0f2&Du0rs4du(abZMXiM!)=A7=znGH-z44_i#I&;3l7Y+K#NW+Bt%yd72 z%mwGP0O5u$vZJ-yn0VJsg~X$-G0pq%*2!vuByr8Wh^@(pj-8B5NRRcz#}3-W?#F84 zk_^4rt(BCE1JsWV22e{s#!X8<4VTuQjFQCDY7?HPoyNqUd!UWLu9Uu`Bdto=*ol3px%on zYVF4xv{vyLLb0Dw)nZ$hIb4Y2`N^c0m8zq@n{u^`hnn|`f*$Qf5^Jx&SKyPAgH}CV zGYIMiy?Kpa=jc`Sc*R0>?+s#`ynDH8HEhVU?p^a%CUx(6O@r_@+5^fH_pz%%tSbgx zgIwm&d8VmAMnq=4j5fE-T*Bt=pw|-pgY*J~L~0+`rJF#cOY!2e8S$Z0Vxu3``J&%Q zJuxaKm#o!ZOVApcHJ9F&p>>RC6(8Zmbk|+zZee6MLc4aFOWG~sGY%&nnWZltt7fQ9 zJZgQpX@yrGS;PIWh|Pp}^|2=&GjA^S?M_zqL~nO;Fp_&WvatGv^o((H(fx6&JummO zKe;lleMHXc@`)oVWz}6HU(0@hPZ5z5x=HSrPiZbiPIato43b4fqQuI}RJz?wl;}l) zaa96o{RKTo=~@Ln$&JmKvp5aBS(1yJC%HK2Erg@er}-)-;*Crxf2odySTUk}uBJUI zSG=_=Kp%(**WrA)iiwV3cPcuPLRm%_a293C7Y^tJ33^0{ZJLvVdWFoD^YlgYxXC3e z7#0Ib=+Im*SEk%#<5*xyY6N3vm4^w)~-(=1}Ii$FXY;QPRNQ9FJu#c^^fbS z#IZgSt9s&@hzGNjrF`n6A+VEZ^`2Vs$M9vzZ|y6sDDk!D4~aclGg5(EJ>!Ke<&}}) zw1wCFlN8Miw71(e6e6$4r=8M6b+HmDvb*PqQ6H_QGRv7Yx6a$ImBsRqAI!Rdz^9_- z+InVxYNEQ#OrUJT>SQYGVh*cT+4qWO0aXg|%PO$EI$h?tsz zX8w7B95I^QC3-e*TqjRmqi29Q6-w%$i{UTQQ+_s<*_-ri5RJ+fAMrFj8}jp0id|>R zH2i)fH6018BePhQO^Ktm_A)q!XYrFCVAA{g&1V6#c!g1*D(*!^yEz%xVx?omO*tyB zqjD5qe+D7u;&Zt$D%go$QfkRfdhP=|b>7~G47zjrk25mOY-X?3N^>I>4g{48%J-(4 zxmLeCvpI^>ld8;gHFxa?gPt7!Cjz=T-!7P=CJnmu!ls=`VN}O=^&aN7J=fS1F@a`+ z=%PIfTrkIio-;^+8<#|X+Bv-IuH?==LeIA)O^T?RCt(#|wJ+v4NVF5JM)~zx#@)Af z?WBA6b#pb;l9z1;IZ4g($8vQsIAA7<3*$#iu+-ef4|(HQ7d+Jam8`@d1>BbV*DUtL zdTEi`ncFH-ZS>|P8%a(axMUqoR9x-#cbd_PMWsMLs~e@OAd@B62kbem8)v#s&qOQf zbWJOXBm!}`!;gxGy2T*jEuPf9s$TCAy=D?Lx8!wbvs-zRgG%Egr^;X^E}7|8KXXKf zxVBLFZ(gGBUcdbqXt(w&?UWJ3+aRR9E zHgUl?{w9FaMYltjh_i#zPdckk>i^x*emYtBj6cpzJ`ONx@dYL`l`kRa^kCNwd?6t5 zWVIngP@<)hW&O&dMSXazF$Tb4upFb0C`$R34y7yt&K;5S^1XbhyT<{Ys`{q@UCEwYqX)ykumV zM2jCN+1i+jW}O<_6&ag7iSjLBsfOS=8${5+NJ}IRois9 zRG#jvuc3N%?&*~4yi$p^*@FO<0H0nup7ZryNlV&T-aVC`)GO9rRev;%-Wn zAU@xTJu@X}6qC zMsJ_4^{v+v(Q))Gxvxz&uv_G{yrl!B3)3Ph%I0Q|LE^bE#AU((?!B7x;;b^p znx`_jJ%yPrHgtGNO&c8ZWeGFQY!u_VrFtss5f|3{j_wO4^XsG;zJMnMj$@+v4V$F zf}MPSf>OWO71@7;g8Grs3608&Zfvv(^bY>NL#r>N;<$0!8LTXlzy5m|lYdI}OdV;& zdo%YL%F;Yr&RBVrdY!%gVajE9=hV-i47z}h}mv)$8HOlFSxrOR_ff3bDTi9DTV||qpo|#Frn5sQTb4j=O zU4J{r(@AYhZ8`qqR@ZL&PDOV<%x5a*^dwlXS0vdX?_Lb0_bHvt!o~A~6f^g1?dK?Z ze;XESg${H0vU@3@g3|qisHlA5LXl01K8ZF?b>f|py=})9Ilh?|@2t(~3!g94bjD*l z+H6sGH=PNFcNh9~#n0*&NN~&8zly|EgL4A!q%i*ydl{\n" +"Language-Team: \n" +"Language: fr_FR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: acl.py:42 +msgid "You don't have the right to view this application." +msgstr "Vous n'avez pas le droit de voir cette application." + +#: forms.py:78 +msgid "Machine name" +msgstr "Nom de la machine" + +#: forms.py:97 templates/machines/aff_machines.html:46 +msgid "MAC address" +msgstr "Adresse MAC" + +#: forms.py:98 templates/machines/aff_machinetype.html:32 +#: templates/machines/machine.html:112 +msgid "Machine type" +msgstr "Type de machine" + +#: forms.py:99 +msgid "Select a machine type" +msgstr "Sélectionnez un type de machine" + +#: forms.py:101 +msgid "Automatic IPv4 assignment" +msgstr "Assignation automatique IPv4" + +#: forms.py:172 +msgid "Current aliases" +msgstr "Alias actuels" + +#: forms.py:193 +msgid "Machine type to add" +msgstr "Type de machine à ajouter" + +#: forms.py:194 +msgid "Related IP type" +msgstr "Type d'IP relié" + +#: forms.py:201 +msgid "Current machine types" +msgstr "Types de machines actuels" + +#: forms.py:224 +msgid "IP type to add" +msgstr "Type d'IP à ajouter" + +#: forms.py:241 +msgid "Current IP types" +msgstr "Types d'IP actuels" + +#: forms.py:263 +msgid "Extension to add" +msgstr "Extension à ajouter" + +#: forms.py:264 templates/machines/aff_extension.html:37 +msgid "A record origin" +msgstr "Enregistrement A origin" + +#: forms.py:265 templates/machines/aff_extension.html:39 +msgid "AAAA record origin" +msgstr "Enregistrement AAAA origin" + +#: forms.py:266 +msgid "SOA record to use" +msgstr "Enregistrement SOA à utiliser" + +#: forms.py:273 +msgid "Current extensions" +msgstr "Extensions actuelles" + +#: forms.py:312 +msgid "Current SOA records" +msgstr "Enregistrements SOA actuels" + +#: forms.py:343 +msgid "Current MX records" +msgstr "Enregistrements MX actuels" + +#: forms.py:376 +msgid "Current NS records" +msgstr "Enregistrements NS actuels" + +#: forms.py:404 +msgid "Current TXT records" +msgstr "Enregistrements TXT actuels" + +#: forms.py:432 +msgid "Current DNAME records" +msgstr "Enregistrements DNAME actuels" + +#: forms.py:460 +msgid "Current SRV records" +msgstr "Enregistrements SRV actuels" + +#: forms.py:489 +msgid "Current NAS devices" +msgstr "Dispositifs NAS actuels" + +#: forms.py:521 +msgid "Current roles" +msgstr "Rôles actuels" + +#: forms.py:562 +msgid "Current services" +msgstr "Services actuels" + +#: forms.py:590 +msgid "Current VLANs" +msgstr "VLANs actuels" + +#: models.py:63 +msgid "Optional" +msgstr "Optionnel" + +#: models.py:71 +msgid "Can view a machine object" +msgstr "Peut voir un objet machine" + +#: models.py:73 +msgid "Can change the user of a machine" +msgstr "Peut changer l'utilisateur d'une machine" + +#: models.py:75 +msgid "machine" +msgstr "machine" + +#: models.py:76 +msgid "machines" +msgstr "machines" + +#: models.py:109 +msgid "You don't have the right to change the machine's user." +msgstr "Vous n'avez pas le droit de changer l'utilisateur de la machine." + +#: models.py:118 +msgid "You don't have the right to view all the machines." +msgstr "Vous n'avez pas le droit de voir toutes les machines." + +#: models.py:132 +msgid "Nonexistent user." +msgstr "Utilisateur inexistant." + +#: models.py:140 +msgid "You don't have the right to add a machine." +msgstr "Vous n'avez pas le droit d'ajouter une machine." + +#: models.py:142 +msgid "You don't have the right to add a machine to another user." +msgstr "Vous n'avez pas le droit d'ajouter une machine à un autre utilisateur." + +#: models.py:145 models.py:1152 +#, python-format +msgid "" +"You reached the maximum number of interfaces that you are allowed to create " +"yourself (%s)." +msgstr "" +"Vous avez atteint le nombre maximal d'interfaces que vous pouvez créer vous-" +"mêmes (%s)." + +#: models.py:164 models.py:1177 models.py:1194 models.py:1296 models.py:1313 +msgid "You don't have the right to edit a machine of another user." +msgstr "" +"Vous n'avez pas le droit de modifier une machine d'un autre utilisateur." + +#: models.py:182 +msgid "You don't have the right to delete a machine of another user." +msgstr "" +"Vous n'avez pas le droit de supprimer une machine d'une autre utilisateur." + +#: models.py:194 +msgid "You don't have the right to view other machines than yours." +msgstr "Vous n'avez pas le droit de voir d'autres machines que les vôtres." + +#: models.py:241 +msgid "Can view a machine type object" +msgstr "Peut voir un objet type de machine" + +#: models.py:242 +msgid "Can use all machine types" +msgstr "Peut utiliser tous les types de machine" + +#: models.py:244 +msgid "machine type" +msgstr "type de machine" + +#: models.py:245 +msgid "machine types" +msgstr "types de machine" + +#: models.py:263 +msgid "You don't have the right to use all machine types." +msgstr "Vous n'avez pas le droit d'utiliser tous les types de machine." + +#: models.py:282 +msgid "Network containing the domain's IPv4 range (optional)" +msgstr "Réseau contenant la plage IPv4 du domaine (optionnel)" + +#: models.py:290 +msgid "Netmask for the domain's IPv4 range" +msgstr "Masque de sous-réseau pour la plage IPv4 du domaine" + +#: models.py:294 +msgid "Enable reverse DNS for IPv4" +msgstr "Activer DNS inverse pour IPv4" + +#: models.py:310 +msgid "Enable reverse DNS for IPv6" +msgstr "Activer DNS inverser pour IPv6" + +#: models.py:326 +msgid "Can view an IP type object" +msgstr "Peut voir un objet type d'IP" + +#: models.py:327 +msgid "Can use all IP types" +msgstr "Peut utiliser tous les types d'IP" + +#: models.py:329 templates/machines/aff_iptype.html:35 +#: templates/machines/machine.html:108 +msgid "IP type" +msgstr "type d'IP" + +#: models.py:433 +msgid "" +"One or several IP addresses from the range are affected, impossible to " +"delete the range." +msgstr "" +"Une ou plusieurs adresses IP de la plage sont affectées, impossible de " +"supprimer la plage." + +#: models.py:475 +msgid "Range end must be after range start..." +msgstr "La fin de la plage doit être après le début..." + +#: models.py:478 +msgid "The range is too large, you can't create a larger one than a /16." +msgstr "" +"La plage est trop grande, vous ne pouvez pas en créer une plus grande " +"qu'un /16." + +#: models.py:483 +msgid "The specified range is not disjoint from existing ranges." +msgstr "La plage renseignée n'est pas disjointe des plages existantes." + +#: models.py:491 +msgid "" +"If you specify a domain network or netmask, it must contain the domain's IP " +"range." +msgstr "" +"Si vous renseignez un réseau ou masque de sous-réseau, il doit contenir" +" la plage IP du domaine." + +#: models.py:521 +msgid "Can view a VLAN object" +msgstr "Peut voir un objet VLAN" + +#: models.py:523 templates/machines/machine.html:160 +msgid "VLAN" +msgstr "VLAN" + +#: models.py:524 templates/machines/sidebar.html:57 +msgid "VLANs" +msgstr "VLANs" + +#: models.py:560 +msgid "Can view a NAS device object" +msgstr "Peut voir un objet dispositif NAS" + +#: models.py:562 templates/machines/machine.html:164 +msgid "NAS device" +msgstr "dispositif NAS" + +#: models.py:563 templates/machines/sidebar.html:63 +msgid "NAS devices" +msgstr "dispositifs NAS" + +#: models.py:577 +msgid "Contact email address for the zone" +msgstr "Adresse mail de contact pour la zone" + +#: models.py:581 +msgid "" +"Seconds before the secondary DNS have to ask the primary DNS serial to " +"detect a modification" +msgstr "" +"Secondes avant que le DNS secondaire demande au DNS primaire le serial pour " +"détecter une modification" + +#: models.py:586 +msgid "" +"Seconds before the secondary DNS ask the serial again in case of a primary " +"DNS timeout" +msgstr "" +"Secondes avant que le DNS secondaire demande le serial de nouveau dans le " +"cas d'un délai d'attente du DNS primaire" + +#: models.py:591 +msgid "" +"Seconds before the secondary DNS stop answering requests in case of primary " +"DNS timeout" +msgstr "" +"Secondes avant que le DNS secondaire arrête de répondre aux requêtes dans le " +"cas d'un délai d'attente du DNS primaire" + +#: models.py:596 models.py:846 +msgid "Time to Live" +msgstr "Temps de vie" + +#: models.py:601 +msgid "Can view an SOA record object" +msgstr "Peut voir un objet enregistrement SOA" + +#: models.py:603 templates/machines/aff_extension.html:36 +#: templates/machines/machine.html:120 +msgid "SOA record" +msgstr "enregistrement SOA" + +#: models.py:604 +msgid "SOA records" +msgstr "enregistrements SOA" + +#: models.py:643 +msgid "SOA to edit" +msgstr "SOA à modifier" + +#: models.py:654 +msgid "Zone name, must begin with a dot (.example.org)" +msgstr "Nom de zone, doit commencer par un point (.example.org)" + +#: models.py:662 +msgid "A record associated with the zone" +msgstr "Enregistrement A associé à la zone" + +#: models.py:668 +msgid "AAAA record associated with the zone" +msgstr "Enregristrement AAAA associé avec la zone" + +#: models.py:677 +msgid "Can view an extension object" +msgstr "Peut voir un objet extension" + +#: models.py:678 +msgid "Can use all extensions" +msgstr "Peut utiliser toutes les extensions" + +#: models.py:680 +msgid "DNS extension" +msgstr "extension DNS" + +#: models.py:681 +msgid "DNS extensions" +msgstr "extensions DNS" + +#: models.py:732 +msgid "An extension must begin with a dot." +msgstr "Une extension doit commencer par un point." + +#: models.py:746 +msgid "Can view an MX record object" +msgstr "Peut voir un objet enregistrement MX" + +#: models.py:748 templates/machines/machine.html:124 +msgid "MX record" +msgstr "enregistrement MX" + +#: models.py:749 +msgid "MX records" +msgstr "enregistrements MX" + +#: models.py:771 +msgid "Can view an NS record object" +msgstr "Peut voir un objet enregistrement NS" + +#: models.py:773 templates/machines/machine.html:128 +msgid "NS record" +msgstr "enregistrement NS" + +#: models.py:774 +msgid "NS records" +msgstr "enregistrements NS" + +#: models.py:793 +msgid "Can view a TXT record object" +msgstr "Peut voir un objet enregistrement TXT" + +#: models.py:795 templates/machines/machine.html:132 +msgid "TXT record" +msgstr "enregistrement TXT" + +#: models.py:796 +msgid "TXT records" +msgstr "enregistrements TXT" + +#: models.py:815 +msgid "Can view a DNAME record object" +msgstr "Peut voir un objet enregistrement DNAME" + +#: models.py:817 templates/machines/machine.html:136 +msgid "DNAME record" +msgstr "enregistrement DNAME" + +#: models.py:818 +msgid "DNAME records" +msgstr "enregistrements DNAME" + +#: models.py:851 +msgid "" +"Priority of the target server (positive integer value, the lower it is, the " +"more the server will be used if available)" +msgstr "" +"Priorité du serveur cible (entier positif, plus il est bas, plus le serveur " +"sera utilisé si disponible)" + +#: models.py:858 +msgid "" +"Relative weight for records with the same priority (integer value between 0 " +"and 65535)" +msgstr "" +"Poids relatif des enregistrements avec la même priorité (entier entre 0 et " +"65535)" + +#: models.py:863 +msgid "TCP/UDP port" +msgstr "Port TCP/UDP" + +#: models.py:868 +msgid "Target server" +msgstr "Serveur cible" + +#: models.py:873 +msgid "Can view an SRV record object" +msgstr "Peut voir un objet enregistrement SRV" + +#: models.py:875 templates/machines/machine.html:140 +msgid "SRV record" +msgstr "enregistrement SRV" + +#: models.py:876 +msgid "SRV records" +msgstr "enregistrements SRV" + +#: models.py:940 +msgid "Can view an SSHFP record object" +msgstr "Peut voir un objet enregistrement SSHFP" + +#: models.py:942 templates/machines/machine.html:144 +msgid "SSHFP record" +msgstr "enregistrement SSHFP" + +#: models.py:943 +msgid "SSHFP records" +msgstr "enregistrements SSHFP" + +#: models.py:981 +msgid "Can view an interface object" +msgstr "Peut voir un objet interface" + +#: models.py:983 +msgid "Can change the owner of an interface" +msgstr "Peut changer l'utilisateur d'une interface" + +#: models.py:985 +msgid "interface" +msgstr "interface" + +#: models.py:986 +msgid "interfaces" +msgstr "interfaces" + +#: models.py:1080 +msgid "The given MAC address is invalid." +msgstr "L'adresse MAC indiquée est invalide." + +#: models.py:1093 +msgid "The selected IP type is invalid." +msgstr "Le type d'IP sélectionné est invalide." + +#: models.py:1106 +msgid "There is no IP address available in the slash." +msgstr "Il n'y a pas d'adresse IP disponible dans le slash." + +#: models.py:1124 +msgid "The IPv4 address and the machine type don't match." +msgstr "L'adresse IPv4 et le type de machine ne correspondent pas." + +#: models.py:1138 +msgid "Nonexistent machine." +msgstr "Machine inexistante." + +#: models.py:1142 +msgid "You can't add a machine." +msgstr "Vous ne pouvez pas ajouter une machine." + +#: models.py:1148 +msgid "" +"You don't have the right to add an interface to a machine of another user." +msgstr "" +"Vous n'avez pas le droit d'ajouter une interface à une machine d'un autre " +"utilisateur." + +#: models.py:1162 +msgid "Permission required to edit the machine." +msgstr "Permission requise pour modifier la machine." + +#: models.py:1206 models.py:1325 models.py:1532 +msgid "You don't have the right to view machines other than yours." +msgstr "Vous n'avez pas le droit de voir d'autres machines que les vôtres." + +#: models.py:1252 +msgid "Can view an IPv6 addresses list object" +msgstr "Peut voir un objet list d'adresses IPv6" + +#: models.py:1253 +msgid "Can change the SLAAC value of an IPv6 addresses list" +msgstr "Peut modifier la valeur SLAAC d'une liste d'adresses IPv6" + +#: models.py:1256 +msgid "IPv6 addresses list" +msgstr "Liste d'adresses IPv6" + +#: models.py:1257 +msgid "IPv6 addresses lists" +msgstr "Listes d'adresses IPv6" + +#: models.py:1269 models.py:1480 +msgid "Nonexistent interface." +msgstr "Interface inexistante." + +#: models.py:1272 models.py:1487 +msgid "You don't have the right to add an alias to a machine of another user." +msgstr "" +"Vous n'avez pas le droit d'ajouter un alias à une machine d'un autre " +"utilisateur." + +#: models.py:1280 +msgid "Permission required to change the SLAAC value of an IPv6 address" +msgstr "Permission requise pour changer la valeur SLAAC d'une adresse IPv6." + +#: models.py:1352 +msgid "A SLAAC IP address is already registered." +msgstr "Une adresse IP SLAAC est déjà enregistrée." + +#: models.py:1357 +msgid "" +"The v6 prefix is incorrect and doesn't match the type associated with the " +"machine." +msgstr "" +"Le préfixe v6 est incorrect et ne correspond pas au type associé à la " +"machine." + +#: models.py:1383 +msgid "Mandatory and unique, must not contain dots." +msgstr "Obligatoire et unique, ne doit pas contenir de points." + +#: models.py:1397 +msgid "Can view a domain object" +msgstr "Peut voir un objet domaine" + +#: models.py:1399 +msgid "domain" +msgstr "domaine" + +#: models.py:1400 +msgid "domains" +msgstr "domaines" + +#: models.py:1422 +msgid "You can't create a both A and CNAME record." +msgstr "Vous ne pouvez pas créer un enregistrement à la fois A et CNAME." + +#: models.py:1425 +msgid "You can't create a CNAME record pointing to itself." +msgstr "Vous ne pouvez pas créer un enregistrement CNAME vers lui-même." + +#: models.py:1433 +#, python-format +msgid "The domain name %s is too long (over 63 characters)." +msgstr "Le nom de domaine %s est trop long (plus de 63 caractères)." + +#: models.py:1436 +#, python-format +msgid "The domain name %s contains forbidden characters." +msgstr "Le nom de domaine %s contient des caractères interdits." + +#: models.py:1454 +msgid "Invalid extension." +msgstr "Extension invalide." + +#: models.py:1495 +#, python-format +msgid "" +"You reached the maximum number of alias that you are allowed to create " +"yourself (%s). " +msgstr "" +"Vous avez atteint le nombre maximal d'alias que vous pouvez créer vous-mêmes " +"(%s)." + +#: models.py:1508 +msgid "You don't have the right to edit an alias of a machine of another user." +msgstr "" +"Vous n'avez pas le droit de modifier un alias d'une machine d'un autre " +"utilisateur." + +#: models.py:1520 +msgid "" +"You don't have the right to delete an alias of a machine of another user." +msgstr "" +"Vous n'avez pas le droit de supprimer un alias d'une machine d'un autre " +"utilisateur." + +#: models.py:1548 +msgid "Can view an IPv4 addresses list object" +msgstr "Peut voir un object liste d'adresses IPv4" + +#: models.py:1550 +msgid "IPv4 addresses list" +msgstr "Liste d'adresses IPv4" + +#: models.py:1551 +msgid "IPv4 addresses lists" +msgstr "Listes d'adresses IPv4" + +#: models.py:1562 +msgid "The IPv4 address and the range of the IP type don't match." +msgstr "L'adresse IPv4 et la plage du type d'IP ne correspondent pas." + +#: models.py:1580 +msgid "DHCP server" +msgstr "Serveur DHCP" + +#: models.py:1581 +msgid "Switches configuration server" +msgstr "Serveur de configuration des commutateurs réseau" + +#: models.py:1582 +msgid "Recursive DNS server" +msgstr "Serveur DNS récursif" + +#: models.py:1583 +msgid "NTP server" +msgstr "Serveur NTP" + +#: models.py:1584 +msgid "RADIUS server" +msgstr "Serveur RADIUS" + +#: models.py:1585 +msgid "Log server" +msgstr "Serveur log" + +#: models.py:1586 +msgid "LDAP master server" +msgstr "Serveur LDAP maître" + +#: models.py:1587 +msgid "LDAP backup server" +msgstr "Serveur LDAP de secours" + +#: models.py:1588 +msgid "SMTP server" +msgstr "Serveur SMTP" + +#: models.py:1589 +msgid "postgreSQL server" +msgstr "Serveur postgreSQL" + +#: models.py:1590 +msgid "mySQL server" +msgstr "Serveur mySQL" + +#: models.py:1591 +msgid "SQL client" +msgstr "Client SQL" + +#: models.py:1592 +msgid "Gateway" +msgstr "Passerelle" + +#: models.py:1606 +msgid "Can view a role object" +msgstr "Peut voir un objet rôle" + +#: models.py:1608 +msgid "server role" +msgstr "rôle de serveur" + +#: models.py:1609 +msgid "server roles" +msgstr "rôles de serveur" + +#: models.py:1650 +msgid "Minimal time before regeneration of the service." +msgstr "Temps minimal avant régénération du service." + +#: models.py:1654 +msgid "Maximal time before regeneration of the service." +msgstr "Temps maximal avant régénération du service." + +#: models.py:1660 +msgid "Can view a service object" +msgstr "Peut voir un objet service" + +#: models.py:1662 +msgid "service to generate (DHCP, DNS, ...)" +msgstr "service à générer (DHCP, DNS, ...)" + +#: models.py:1663 +msgid "services to generate (DHCP, DNS, ...)" +msgstr "services à générer (DHCP, DNS, ...)" + +#: models.py:1709 +msgid "Can view a service server link object" +msgstr "Peut voir un objet lien service serveur" + +#: models.py:1711 +msgid "link between service and server" +msgstr "lien entre service et serveur" + +#: models.py:1712 +msgid "links between service and server" +msgstr "liens entre service et serveur" + +#: models.py:1754 +msgid "Name of the ports configuration" +msgstr "Nom de la configuration de ports" + +#: models.py:1760 +msgid "Can view a ports opening list object" +msgstr "Peut voir un objet liste d'ouverture de ports" + +#: models.py:1763 +msgid "ports opening list" +msgstr "liste d'ouverture de ports" + +#: models.py:1764 +msgid "ports opening lists" +msgstr "listes d'ouverture de ports" + +#: models.py:1773 +msgid "You don't have the right to delete a ports opening list." +msgstr "Vous n'avez pas le droit de supprimer une liste d'ouverture de ports." + +#: models.py:1776 +msgid "This ports opening list is used." +msgstr "Cette liste d'ouverture de ports est utilisée." + +#: models.py:1849 +msgid "ports opening" +msgstr "ouverture de ports" + +#: models.py:1850 +msgid "ports openings" +msgstr "ouvertures de ports" + +#: templates/machines/aff_alias.html:32 +msgid "Aliases" +msgstr "Alias" + +#: templates/machines/aff_dname.html:30 +msgid "Target zone" +msgstr "Cible" + +#: templates/machines/aff_dname.html:31 templates/machines/aff_mx.html:34 +#: templates/machines/aff_txt.html:33 +msgid "Record" +msgstr "Enregistrement" + +#: templates/machines/aff_extension.html:34 +#: templates/machines/aff_iptype.html:36 templates/machines/aff_srv.html:34 +#: templates/machines/machine.html:116 +msgid "Extension" +msgstr "Extension" + +#: templates/machines/aff_extension.html:35 +#: templates/machines/aff_iptype.html:37 +msgid "'infra' right required" +msgstr "droit 'infra' requis" + +#: templates/machines/aff_iptype.html:38 +msgid "IPv4 range" +msgstr "Plage IPv4" + +#: templates/machines/aff_iptype.html:39 +msgid "v6 prefix" +msgstr "Préfixe v6" + +#: templates/machines/aff_iptype.html:40 +msgid "DNSSEC reverse v4/v6" +msgstr "DNSSEC inverse v4/v6" + +#: templates/machines/aff_iptype.html:41 +msgid "On VLAN(s)" +msgstr "Sur VLAN(s)" + +#: templates/machines/aff_iptype.html:42 +msgid "Default ports opening" +msgstr "Ouverture de ports par défaut" + +#: templates/machines/aff_ipv6.html:32 +msgid "IPv6 addresses" +msgstr "Adresses IPv6" + +#: templates/machines/aff_ipv6.html:33 +msgid "SLAAC" +msgstr "SLAAC" + +#: templates/machines/aff_machines.html:43 +msgid "DNS name" +msgstr "Nom DNS" + +#: templates/machines/aff_machines.html:45 +msgid "Type" +msgstr "Type" + +#: templates/machines/aff_machines.html:47 +msgid "IP address" +msgstr "Adresse IP" + +#: templates/machines/aff_machines.html:48 +msgid "Actions" +msgstr "Actions" + +#: templates/machines/aff_machines.html:53 +msgid "No name" +msgstr "Sans nom" + +#: templates/machines/aff_machines.html:54 +msgid "View the profile" +msgstr "Voir le profil" + +#: templates/machines/aff_machines.html:62 views.py:375 +msgid "Create an interface" +msgstr "Créer une interface" + +#: templates/machines/aff_machines.html:77 +msgid "Display the aliases" +msgstr "Afficher les alias" + +#: templates/machines/aff_machines.html:95 +msgid "Display the IPv6 address" +msgstr "Afficher les adresses IPv6" + +#: templates/machines/aff_machines.html:110 +msgid " Edit" +msgstr " Modifier" + +#: templates/machines/aff_machines.html:118 +msgid " Manage the aliases" +msgstr " Gérer les alias" + +#: templates/machines/aff_machines.html:126 +msgid " Manage the IPv6 addresses" +msgstr " Gérer les adresses IPv6" + +#: templates/machines/aff_machines.html:134 +msgid " Manage the SSH fingerprints" +msgstr " Gérer les empreintes SSH" + +#: templates/machines/aff_machines.html:142 +msgid " Manage the ports configuration" +msgstr " Gérer les configuration de ports" + +#: templates/machines/aff_machinetype.html:33 +msgid "Matching IP type" +msgstr "Type d'IP correspondant" + +#: templates/machines/aff_mx.html:32 templates/machines/aff_ns.html:32 +#: templates/machines/aff_txt.html:32 +msgid "Concerned zone" +msgstr "Zone concernée" + +#: templates/machines/aff_mx.html:33 templates/machines/aff_srv.html:36 +msgid "Priority" +msgstr "Priorité" + +#: templates/machines/aff_nas.html:33 templates/machines/aff_soa.html:32 +#: templates/machines/aff_vlan.html:34 +#: templates/machines/index_portlist.html:18 +msgid "Name" +msgstr "Nom" + +#: templates/machines/aff_nas.html:34 +msgid "NAS device type" +msgstr "Type de dispositif NAS" + +#: templates/machines/aff_nas.html:35 +msgid "Machine type linked to the NAS device" +msgstr "Type de machine lié au dispositif NAS" + +#: templates/machines/aff_nas.html:36 +msgid "Access mode" +msgstr "Mode d'accès" + +#: templates/machines/aff_nas.html:37 +msgid "MAC address auto capture" +msgstr "Capture automatique de l'adresse MAC" + +#: templates/machines/aff_ns.html:33 +msgid "Authoritarian interface for the concerned zone" +msgstr "Interface authoritaire pour la zone concernée" + +#: templates/machines/aff_role.html:33 +msgid "Role name" +msgstr "Nom du rôle" + +#: templates/machines/aff_role.html:34 +msgid "Specific role" +msgstr "Rôle spécifique" + +#: templates/machines/aff_role.html:35 +msgid "Servers" +msgstr "Serveurs" + +#: templates/machines/aff_servers.html:31 +#: templates/machines/aff_service.html:32 +msgid "Service name" +msgstr "Nom du service" + +#: templates/machines/aff_servers.html:32 +msgid "Server" +msgstr "Serveur" + +#: templates/machines/aff_servers.html:33 +msgid "Last regeneration" +msgstr "Dernière régénération" + +#: templates/machines/aff_servers.html:34 +msgid "Regeneration required" +msgstr "Régénération requise" + +#: templates/machines/aff_servers.html:35 +msgid "Regeneration activated" +msgstr "Régénération activée" + +#: templates/machines/aff_service.html:33 +msgid "Minimal time before regeneration" +msgstr "Temps minimal avant régénération" + +#: templates/machines/aff_service.html:34 +msgid "Maximal time before regeneration" +msgstr "Temps maximal avant régénération" + +#: templates/machines/aff_service.html:35 +msgid "Included servers" +msgstr "Serveurs inclus" + +#: templates/machines/aff_service.html:36 +msgid "Ask for regeneration" +msgstr "Demander la régénération" + +#: templates/machines/aff_soa.html:33 +msgid "Mail" +msgstr "Mail" + +#: templates/machines/aff_soa.html:34 +msgid "Refresh" +msgstr "Rafraichissement" + +#: templates/machines/aff_soa.html:35 +msgid "Retry" +msgstr "Relance" + +#: templates/machines/aff_soa.html:36 +msgid "Expire" +msgstr "Expiration" + +#: templates/machines/aff_soa.html:37 templates/machines/aff_srv.html:35 +msgid "TTL" +msgstr "Temps de vie" + +#: templates/machines/aff_srv.html:32 templates/machines/machine.html:152 +msgid "Service" +msgstr "Service" + +#: templates/machines/aff_srv.html:33 +msgid "Protocol" +msgstr "Protocole" + +#: templates/machines/aff_srv.html:37 +msgid "Weight" +msgstr "Poids" + +#: templates/machines/aff_srv.html:38 +msgid "Port" +msgstr "Port" + +#: templates/machines/aff_srv.html:39 +msgid "Target" +msgstr "Cible" + +#: templates/machines/aff_sshfp.html:31 +msgid "SSH public key" +msgstr "Clé publique SSH" + +#: templates/machines/aff_sshfp.html:32 +msgid "Algorithm used" +msgstr "Algorithme utilisé" + +#: templates/machines/aff_sshfp.html:33 templates/machines/aff_vlan.html:35 +msgid "Comment" +msgstr "Commentaire" + +#: templates/machines/aff_vlan.html:33 +msgid "ID" +msgstr "ID" + +#: templates/machines/aff_vlan.html:36 templates/machines/sidebar.html:51 +msgid "IP ranges" +msgstr "Plages d'IP" + +#: templates/machines/delete.html:29 +msgid "Creation and editing of machines" +msgstr "Création et modification de machines" + +#: templates/machines/delete.html:35 +#, python-format +msgid "" +"Warning: are you sure you want to delete this object %(objet_name)s " +"( %(objet)s )?" +msgstr "" +"Attention : voulez-vous vraiment supprimer cet objet %(objet_name)s " +"( %(objet)s ) ?" + +#: templates/machines/delete.html:36 +msgid "Confirm" +msgstr "Confirmer" + +#: templates/machines/edit_portlist.html:29 templates/machines/index.html:29 +#: templates/machines/index.html:32 templates/machines/index_alias.html:29 +#: templates/machines/index_extension.html:31 +#: templates/machines/index_iptype.html:31 +#: templates/machines/index_ipv6.html:30 +#: templates/machines/index_machinetype.html:31 +#: templates/machines/index_nas.html:31 +#: templates/machines/index_portlist.html:8 +#: templates/machines/index_portlist.html:23 +#: templates/machines/index_role.html:30 +#: templates/machines/index_service.html:30 +#: templates/machines/index_sshfp.html:28 templates/machines/index_vlan.html:31 +#: templates/machines/machine.html:31 templates/machines/sidebar.html:33 +msgid "Machines" +msgstr "Machines" + +#: templates/machines/edit_portlist.html:50 +msgid "Add a port" +msgstr "Ajouter un port" + +#: templates/machines/edit_portlist.html:53 +msgid "Create or edit" +msgstr "Créer ou modifier" + +#: templates/machines/index_alias.html:32 +msgid "List of the aliases of the interface" +msgstr "Liste des alias de l'interface" + +#: templates/machines/index_alias.html:33 +msgid " Add an alias" +msgstr " Ajouter un alias" + +#: templates/machines/index_alias.html:34 +msgid " Delete one or several aliases" +msgstr " Supprimer un ou plusieurs alias" + +#: templates/machines/index_extension.html:34 +msgid "List of extensions" +msgstr "Liste des extensions" + +#: templates/machines/index_extension.html:36 +msgid " Add an extension" +msgstr " Ajouter une extension" + +#: templates/machines/index_extension.html:38 +msgid " Delete one or several extensions" +msgstr " Supprimer une ou plusieurs extensions" + +#: templates/machines/index_extension.html:41 +msgid "List of SOA records" +msgstr "Liste des enregistrements SOA" + +#: templates/machines/index_extension.html:43 +msgid " Add an SOA record" +msgstr " Ajouter un enregistrement SOA" + +#: templates/machines/index_extension.html:45 +msgid " Delete one or several SOA records" +msgstr " Supprimer un ou plusieurs enregistrements SOA" + +#: templates/machines/index_extension.html:48 +msgid "List of MX records" +msgstr "Liste des enregistrements MX" + +#: templates/machines/index_extension.html:50 +msgid " Add an MX record" +msgstr " Ajouter un enregistrement MX" + +#: templates/machines/index_extension.html:52 +msgid " Delete one or several MX records" +msgstr " Supprimer un ou plusieurs enregistrements MX" + +#: templates/machines/index_extension.html:55 +msgid "List of NS records" +msgstr "Liste des enregistrements NS" + +#: templates/machines/index_extension.html:57 +msgid " Add an NS record" +msgstr " Ajouter un enregistrement NS" + +#: templates/machines/index_extension.html:59 +msgid " Delete one or several NS records" +msgstr " Supprimer un ou plusieurs enregistrements NS" + +#: templates/machines/index_extension.html:62 +msgid "List of TXT records" +msgstr "Liste des enregistrements TXT" + +#: templates/machines/index_extension.html:64 +msgid " Add a TXT record" +msgstr " Ajouter un enregistrement TXT" + +#: templates/machines/index_extension.html:66 +msgid " Delete one or several TXT records" +msgstr " Supprimer un ou plusieurs enregistrements TXT" + +#: templates/machines/index_extension.html:69 +msgid "List of DNAME records" +msgstr "Liste des enregistrements DNAME" + +#: templates/machines/index_extension.html:72 +msgid " Add a DNAME record" +msgstr " Ajouter un enregistrement DNAME" + +#: templates/machines/index_extension.html:76 +msgid " Delete one or several DNAME records" +msgstr " Supprimer un ou plusieurs enregistrements DNAME" + +#: templates/machines/index_extension.html:80 +msgid "List of SRV records" +msgstr "Liste des enregistrements SRV" + +#: templates/machines/index_extension.html:82 +msgid " Add an SRV record" +msgstr " Ajouter un enregistrement SRV" + +#: templates/machines/index_extension.html:84 +msgid " Delete one or several SRV records" +msgstr " Supprimer un ou plusieurs enregistrements SRV" + +#: templates/machines/index_iptype.html:34 +msgid "List of IP types" +msgstr "Liste des types d'IP" + +#: templates/machines/index_iptype.html:36 +msgid " Add an IP type" +msgstr " Ajouter un type d'IP" + +#: templates/machines/index_iptype.html:38 +msgid " Delete one or several IP types" +msgstr " Supprimer un ou plusieurs types d'IP" + +#: templates/machines/index_ipv6.html:33 +msgid "List of the IPv6 addresses of the interface" +msgstr "Liste des adresses IPv6 de l'interface" + +#: templates/machines/index_ipv6.html:35 +msgid " Add an IPv6 address" +msgstr " Ajouter une adresse IPv6" + +#: templates/machines/index_machinetype.html:34 +msgid "List of machine types" +msgstr "Liste des types de machine" + +#: templates/machines/index_machinetype.html:36 +msgid " Add a machine type" +msgstr " Ajouter un type de machine" + +#: templates/machines/index_machinetype.html:38 +msgid " Delete one or several machine types" +msgstr " Supprimer un ou plusieurs types de machine" + +#: templates/machines/index_nas.html:34 +msgid "List of NAS devices" +msgstr "Liste des dispositifs NAS" + +#: templates/machines/index_nas.html:35 +msgid "" +"The NAS device type and machine type are linked. It is useful for MAC " +"address auto capture by RADIUS, and allows to choose the machine type to " +"assign to the machines according to the NAS device type." +msgstr "" +"Le type de dispositif NAS et le type de machine sont liés. C'est utile pour " +"la capture automatique de l'adresse MAC par RADIUS, et permet de choisir le " +"type de machine à assigner aux machines en fonction du type de dispositif " +"NAS." + +#: templates/machines/index_nas.html:37 +msgid " Add a NAS device type" +msgstr " Ajouter un type de dispositif NAS" + +#: templates/machines/index_nas.html:39 +msgid " Delete one or several NAS device types" +msgstr " Supprimer un ou plusieurs types de dispositif NAS" + +#: templates/machines/index_portlist.html:11 +msgid "List of ports configurations" +msgstr "Liste des configurations de ports" + +#: templates/machines/index_portlist.html:13 +msgid " Add a configuration" +msgstr " Ajouter une configuration" + +#: templates/machines/index_portlist.html:19 +msgid "TCP (input)" +msgstr "TCP (entrée)" + +#: templates/machines/index_portlist.html:20 +msgid "TCP (output)" +msgstr "TCP (sortie)" + +#: templates/machines/index_portlist.html:21 +msgid "UDP (input)" +msgstr "UDP (entrée)" + +#: templates/machines/index_portlist.html:22 +msgid "UDP (output)" +msgstr "UDP (sortie)" + +#: templates/machines/index_role.html:33 +msgid "List of roles" +msgstr "Liste des rôles" + +#: templates/machines/index_role.html:35 +msgid " Add a role" +msgstr " Ajouter un rôle" + +#: templates/machines/index_role.html:37 +msgid " Delete one or several roles" +msgstr " Supprimer un ou plusieurs rôles" + +#: templates/machines/index_service.html:33 +msgid "List of services" +msgstr "Liste des services" + +#: templates/machines/index_service.html:35 +msgid " Add a service" +msgstr " Ajouter un service" + +#: templates/machines/index_service.html:37 +msgid " Delete one or several services" +msgstr " Supprimer un ou plusieurs services" + +#: templates/machines/index_service.html:39 +msgid "States of servers" +msgstr "États des serveurs" + +#: templates/machines/index_sshfp.html:31 +msgid "SSH fingerprints" +msgstr "Empreintes SSH" + +#: templates/machines/index_sshfp.html:34 +msgid " Add an SSH fingerprint" +msgstr " Ajouter une empreinte SSH" + +#: templates/machines/index_vlan.html:34 +msgid "List of VLANs" +msgstr "Liste des VLANs" + +#: templates/machines/index_vlan.html:36 +msgid " Add a VLAN" +msgstr " Ajouter un VLAN" + +#: templates/machines/index_vlan.html:38 +msgid " Delete one or several VLANs" +msgstr " Supprimer un ou plusieurs VLANs" + +#: templates/machines/machine.html:92 +msgid "Machine" +msgstr "Machine" + +#: templates/machines/machine.html:96 +msgid "Interface" +msgstr "Interface" + +#: templates/machines/machine.html:104 +msgid "Domain" +msgstr "Domaine" + +#: templates/machines/machine.html:148 +msgid "Alias" +msgstr "Alias" + +#: templates/machines/machine.html:168 +msgid "IPv6 address" +msgstr "Adresse IPv6" + +#: templates/machines/sidebar.html:39 +msgid "Machine types" +msgstr "Types de machine" + +#: templates/machines/sidebar.html:45 +msgid "Extensions and zones" +msgstr "Extensions et zones" + +#: templates/machines/sidebar.html:69 +msgid "Services (DHCP, DNS, ...)" +msgstr "Services (DHCP, DNS, ...)" + +#: templates/machines/sidebar.html:75 +msgid "Server roles" +msgstr "Rôles de serveur" + +#: templates/machines/sidebar.html:81 +msgid "Ports openings" +msgstr "Ouvertures de ports" + +#: views.py:156 +msgid "Select a machine type first.}," +msgstr "Sélectionnez un type de machine d'abord.}," + +#: views.py:258 +msgid "The machine was created." +msgstr "La machine a été créée." + +#: views.py:270 +msgid "Create a machine" +msgstr "Créer une machine" + +#: views.py:310 +msgid "The machine was edited." +msgstr "La machine a été modifiée." + +#: views.py:322 views.py:446 views.py:512 views.py:568 views.py:630 +#: views.py:691 views.py:749 views.py:806 views.py:863 views.py:919 +#: views.py:977 views.py:1034 views.py:1106 views.py:1169 views.py:1226 +#: views.py:1292 views.py:1349 +msgid "Edit" +msgstr "Modifier" + +#: views.py:335 +msgid "The machine was deleted." +msgstr "La machine a été supprimée." + +#: views.py:364 +msgid "The interface was created." +msgstr "L'interface a été créée." + +#: views.py:391 +msgid "The interface was deleted." +msgstr "L'interface a été supprimée." + +#: views.py:416 +msgid "The IPv6 addresses list was created." +msgstr "La liste d'adresses IPv6 a été créée." + +#: views.py:422 +msgid "Create an IPv6 addresses list" +msgstr "Créer une liste d'adresses IPv6" + +#: views.py:440 +msgid "The IPv6 addresses list was edited." +msgstr "La liste d'adresses IPv6 a été modifiée." + +#: views.py:459 +msgid "The IPv6 addresses list was deleted." +msgstr "La liste d'adresses IPv6 a été supprimée." + +#: views.py:483 +msgid "The SSHFP record was created." +msgstr "L'enregistrement SSHFP a été créé." + +#: views.py:489 +msgid "Create a SSHFP record" +msgstr "Créer un enregistrement SSHFP" + +#: views.py:506 +msgid "The SSHFP record was edited." +msgstr "L'enregistrement SSHFP a été modifié." + +#: views.py:525 +msgid "The SSHFP record was deleted." +msgstr "L'enregistrement SSHFP a été supprimé." + +#: views.py:546 +msgid "The IP type was created." +msgstr "Le type d'IP a été créé." + +#: views.py:549 +msgid "Create an IP type" +msgstr "Créer un type d'IP" + +#: views.py:565 +msgid "The IP type was edited." +msgstr "Le type d'IP a été modifié." + +#: views.py:584 +msgid "The IP type was deleted." +msgstr "Le type d'IP a été supprimé." + +#: views.py:588 +#, python-format +msgid "" +"The IP type %s is assigned to at least one machine, you can't delete it." +msgstr "" +"Le type d'IP %s est assigné à au moins une machine, vous ne pouvez pas le " +"supprimer." + +#: views.py:593 views.py:655 views.py:716 views.py:773 views.py:830 +#: views.py:887 views.py:944 views.py:1001 views.py:1058 views.py:1136 +#: views.py:1193 views.py:1250 views.py:1316 views.py:1373 +msgid "Delete" +msgstr "Supprimer" + +#: views.py:606 +msgid "The machine type was created." +msgstr "Le type de machine a été créé." + +#: views.py:609 +msgid "Create a machine type" +msgstr "Créer un type de machine" + +#: views.py:627 +msgid "The machine type was edited." +msgstr "Le type de machine a été modifié." + +#: views.py:646 +msgid "The machine type was deleted." +msgstr "Le type de machine a été supprimé." + +#: views.py:650 +#, python-format +msgid "" +"The machine type %s is assigned to at least one machine, you can't delete it." +msgstr "" +"Le type de machine %s est assigné à au moins un machine, vous ne pouvez pas " +"le supprimer." + +#: views.py:668 +msgid "The extension was created." +msgstr "L'extension a été créée." + +#: views.py:671 +msgid "Create an extension" +msgstr "Créer une extension" + +#: views.py:688 +msgid "The extension was edited." +msgstr "L'extension a été modifiée." + +#: views.py:707 +msgid "The extension was deleted." +msgstr "L'extension a été supprimée." + +#: views.py:711 +#, python-format +msgid "" +"The extension %s is assigned to at least one machine type, you can't delete " +"it." +msgstr "" +"L'extension %s est assignée à au moins un type de machine, vous ne pouvez " +"pas le supprimer." + +#: views.py:729 +msgid "The SOA record was created." +msgstr "L'enregistrement SOA a été créé." + +#: views.py:732 +msgid "Create an SOA record" +msgstr "Créer un enregistrement SOA" + +#: views.py:746 +msgid "The SOA record was edited." +msgstr "L'enregistrement SOA a été modifié." + +#: views.py:765 +msgid "The SOA record was deleted." +msgstr "L'enregistrement SOA a été supprimé." + +#: views.py:769 +#, python-format +msgid "Error: the SOA record %s can't be deleted." +msgstr "Erreur : l'enregistrement SOA %s ne peut pas être supprimé." + +#: views.py:786 +msgid "The MX record was created." +msgstr "L'enregistrement MX a été créé." + +#: views.py:789 +msgid "Create an MX record" +msgstr "Créer un enregistrement MX" + +#: views.py:803 +msgid "The MX record was edited." +msgstr "L'enregistrement MX a été modifié." + +#: views.py:822 +msgid "The MX record was deleted." +msgstr "L'enregistrement MX a été supprimé." + +#: views.py:826 +#, python-format +msgid "Error: the MX record %s can't be deleted." +msgstr "Erreur : l'enregistrement MX %s ne peut pas être supprimé." + +#: views.py:843 +msgid "The NS record was created." +msgstr "L'enregistrement NS a été créé." + +#: views.py:846 +msgid "Create an NS record" +msgstr "Créer un enregistrement NS" + +#: views.py:860 +msgid "The NS record was edited." +msgstr "L'enregistrement NS a été modifié." + +#: views.py:879 +msgid "The NS record was deleted." +msgstr "L'enregistrement NS a été supprimé." + +#: views.py:883 +#, python-format +msgid "Error: the NS record %s can't be deleted." +msgstr "Erreur : l'enregistrement NS %s ne peut pas être supprimé." + +#: views.py:899 +msgid "The DNAME record was created." +msgstr "L'enregistrement DNAME a été créé." + +#: views.py:902 +msgid "Create a DNAME record" +msgstr "Créer un enregistrement DNAME" + +#: views.py:916 +msgid "The DNAME record was edited." +msgstr "L'enregistrement DNAME a été modifié." + +#: views.py:935 +msgid "The DNAME record was deleted." +msgstr "L'enregistrement DNAME a été supprimé." + +#: views.py:939 +#, python-format +msgid "Error: the DNAME record %s can't be deleted." +msgstr "Erreur : l'enregistrement DNAME %s ne peut pas être supprimé." + +#: views.py:957 +msgid "The TXT record was created." +msgstr "L'enregistrement TXT a été créé." + +#: views.py:960 +msgid "Create a TXT record" +msgstr "Créer un enregistrement TXT" + +#: views.py:974 +msgid "The TXT record was edited." +msgstr "L'enregistrement TXT a été modifié." + +#: views.py:993 +msgid "The TXT record was deleted." +msgstr "L'enregistrement TXT a été supprimé." + +#: views.py:997 +#, python-format +msgid "Error: the TXT record %s can't be deleted." +msgstr "Erreur : l'enregistrement %s ne peut pas être supprimé." + +#: views.py:1014 +msgid "The SRV record was created." +msgstr "L'enregistrement SRV a été créé." + +#: views.py:1017 +msgid "Create an SRV record" +msgstr "Créer un enregistrement SRV" + +#: views.py:1031 +msgid "The SRV record was edited." +msgstr "L'enregistrement SRV a été modifié." + +#: views.py:1050 +msgid "The SRV record was deleted." +msgstr "L'enregistrement SRV a été supprimé." + +#: views.py:1054 +#, python-format +msgid "Error: the SRV record %s can't be deleted." +msgstr "Erreur : l'enregistrement SRV %s ne peut pas être supprimé." + +#: views.py:1074 +msgid "The alias was created." +msgstr "L'alias a été créé." + +#: views.py:1080 +msgid "Create an alias" +msgstr "Créer un alias" + +#: views.py:1098 +msgid "The alias was edited." +msgstr "L'alias a été modifié." + +#: views.py:1124 +#, python-format +msgid "The alias %s was deleted." +msgstr "L'alias %s a été supprimé." + +#: views.py:1129 +#, python-format +msgid "Error: the alias %s can't be deleted." +msgstr "Erreur : l'alias %s ne peut pas être supprimé." + +#: views.py:1149 +msgid "The role was created." +msgstr "Le rôle a été créé." + +#: views.py:1152 +msgid "Create a role" +msgstr "Créer un rôle" + +#: views.py:1166 +msgid "The role was edited." +msgstr "Le rôle a été modifié." + +#: views.py:1185 +msgid "The role was deleted." +msgstr "Le rôle a été supprimé." + +#: views.py:1189 +#, python-format +msgid "Error: the role %s can't be deleted." +msgstr "Erreur : le rôle %s ne peut pas être supprimé." + +#: views.py:1206 +msgid "The service was created." +msgstr "Le service a été créé." + +#: views.py:1209 +msgid "Create a service" +msgstr "Créer un service" + +#: views.py:1223 +msgid "The service was edited." +msgstr "Le service a été modifié." + +#: views.py:1242 +msgid "The service was deleted." +msgstr "Le service a été supprimé." + +#: views.py:1246 +#, python-format +msgid "Error: the service %s can't be deleted." +msgstr "Erreur : le service %s ne peut pas être supprimé." + +#: views.py:1272 +msgid "The VLAN was created." +msgstr "Le VLAN a été créé." + +#: views.py:1275 +msgid "Create a VLAN" +msgstr "Créer un VLAN" + +#: views.py:1289 +msgid "The VLAN was edited." +msgstr "Le VLAN a été modifié." + +#: views.py:1308 +msgid "The VLAN was deleted." +msgstr "Le VLAN a été supprimé." + +#: views.py:1312 +#, python-format +msgid "Error: the VLAN %s can't be deleted." +msgstr "Erreur : le VLAN %s ne peut pas être supprimé." + +#: views.py:1329 +msgid "The NAS device was created." +msgstr "Le dispositif NAS a été créé." + +#: views.py:1332 +msgid "Create a NAS device" +msgstr "Créer un dispositif NAS" + +#: views.py:1346 +msgid "The NAS device was edited." +msgstr "Le dispositif NAS a été modifié." + +#: views.py:1365 +msgid "The NAS device was deleted." +msgstr "Le dispositif NAS a été supprimé." + +#: views.py:1369 +#, python-format +msgid "Error: the NAS device %s can't be deleted." +msgstr "Erreur : le dispositif NAS %s ne peut pas être supprimé." + +#: views.py:1625 +msgid "The ports list was edited." +msgstr "La liste de ports a été modifiée." + +#: views.py:1639 +msgid "The ports list was deleted." +msgstr "La liste de ports a été supprimée." + +#: views.py:1664 +msgid "The ports list was created." +msgstr "La liste de ports a été créée." + +#: views.py:1682 +msgid "Warning: the IPv4 isn't public, the opening won't have effect in v4." +msgstr "" +"Attention : l'adresse IPv4 n'est pas publique, l'ouverture n'aura pas " +"d'effet en v4." + +#: views.py:1692 +msgid "The ports configuration was edited." +msgstr "La configuration de ports a été modifiée." + +#: views.py:1695 +msgid "Edit the configuration" +msgstr "Modifier la configuration" diff --git a/machines/migrations/0094_auto_20180815_1918.py b/machines/migrations/0094_auto_20180815_1918.py new file mode 100644 index 00000000..775ac2c7 --- /dev/null +++ b/machines/migrations/0094_auto_20180815_1918.py @@ -0,0 +1,221 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-08-15 17:18 +from __future__ import unicode_literals + +import datetime +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('machines', '0093_auto_20180807_1115'), + ] + + operations = [ + migrations.AlterModelOptions( + name='dname', + options={'permissions': (('view_dname', 'Can view a DNAME record object'),), 'verbose_name': 'DNAME record', 'verbose_name_plural': 'DNAME records'}, + ), + migrations.AlterModelOptions( + name='domain', + options={'permissions': (('view_domain', 'Can view a domain object'),), 'verbose_name': 'domain', 'verbose_name_plural': 'domains'}, + ), + migrations.AlterModelOptions( + name='extension', + options={'permissions': (('view_extension', 'Can view an extension object'), ('use_all_extension', 'Can use all extensions')), 'verbose_name': 'DNS extension', 'verbose_name_plural': 'DNS extensions'}, + ), + migrations.AlterModelOptions( + name='interface', + options={'permissions': (('view_interface', 'Can view an interface object'), ('change_interface_machine', 'Can change the owner of an interface')), 'verbose_name': 'interface', 'verbose_name_plural': 'interfaces'}, + ), + migrations.AlterModelOptions( + name='iplist', + options={'permissions': (('view_iplist', 'Can view an IPv4 addresses list object'),), 'verbose_name': 'IPv4 addresses list', 'verbose_name_plural': 'IPv4 addresses lists'}, + ), + migrations.AlterModelOptions( + name='iptype', + options={'permissions': (('view_iptype', 'Can view an IP type object'), ('use_all_iptype', 'Can use all IP types')), 'verbose_name': 'IP type', 'verbose_name_plural': 'IP types'}, + ), + migrations.AlterModelOptions( + name='ipv6list', + options={'permissions': (('view_ipv6list', 'Can view an IPv6 addresses list object'), ('change_ipv6list_slaac_ip', 'Can change the SLAAC value of an IPv6 addresses list')), 'verbose_name': 'IPv6 addresses list', 'verbose_name_plural': 'IPv6 addresses lists'}, + ), + migrations.AlterModelOptions( + name='machine', + options={'permissions': (('view_machine', 'Can view a machine object'), ('change_machine_user', 'Can change the user of a machine')), 'verbose_name': 'machine', 'verbose_name_plural': 'machines'}, + ), + migrations.AlterModelOptions( + name='machinetype', + options={'permissions': (('view_machinetype', 'Can view a machine type object'), ('use_all_machinetype', 'Can use all machine types')), 'verbose_name': 'machine type', 'verbose_name_plural': 'machine types'}, + ), + migrations.AlterModelOptions( + name='mx', + options={'permissions': (('view_mx', 'Can view an MX record object'),), 'verbose_name': 'MX record', 'verbose_name_plural': 'MX records'}, + ), + migrations.AlterModelOptions( + name='nas', + options={'permissions': (('view_nas', 'Can view a NAS device object'),), 'verbose_name': 'NAS device', 'verbose_name_plural': 'NAS devices'}, + ), + migrations.AlterModelOptions( + name='ns', + options={'permissions': (('view_ns', 'Can view an NS record object'),), 'verbose_name': 'NS record', 'verbose_name_plural': 'NS records'}, + ), + migrations.AlterModelOptions( + name='ouvertureport', + options={'verbose_name': 'ports openings'}, + ), + migrations.AlterModelOptions( + name='ouvertureportlist', + options={'permissions': (('view_ouvertureportlist', 'Can view a ports opening list object'),), 'verbose_name': 'ports opening list', 'verbose_name_plural': 'ports opening lists'}, + ), + migrations.AlterModelOptions( + name='role', + options={'permissions': (('view_role', 'Can view a role object'),), 'verbose_name': 'server role', 'verbose_name_plural': 'server roles'}, + ), + migrations.AlterModelOptions( + name='service', + options={'permissions': (('view_service', 'Can view a service object'),), 'verbose_name': 'service to generate (DHCP, DNS, ...)', 'verbose_name_plural': 'services to generate (DHCP, DNS, ...)'}, + ), + migrations.AlterModelOptions( + name='service_link', + options={'permissions': (('view_service_link', 'Can view a service server link object'),), 'verbose_name': 'link between service and server', 'verbose_name_plural': 'links between service and server'}, + ), + migrations.AlterModelOptions( + name='soa', + options={'permissions': (('view_soa', 'Can view an SOA record object'),), 'verbose_name': 'SOA record', 'verbose_name_plural': 'SOA records'}, + ), + migrations.AlterModelOptions( + name='srv', + options={'permissions': (('view_srv', 'Can view an SRV record object'),), 'verbose_name': 'SRV record', 'verbose_name_plural': 'SRV records'}, + ), + migrations.AlterModelOptions( + name='sshfp', + options={'permissions': (('view_sshfp', 'Can view an SSHFP record object'),), 'verbose_name': 'SSHFP record', 'verbose_name_plural': 'SSHFP records'}, + ), + migrations.AlterModelOptions( + name='txt', + options={'permissions': (('view_txt', 'Can view a TXT record object'),), 'verbose_name': 'TXT record', 'verbose_name_plural': 'TXT records'}, + ), + migrations.AlterModelOptions( + name='vlan', + options={'permissions': (('view_vlan', 'Can view a VLAN object'),), 'verbose_name': 'VLAN', 'verbose_name_plural': 'VLANs'}, + ), + migrations.AlterField( + model_name='domain', + name='name', + field=models.CharField(help_text='Mandatory and unique, must not contain dots.', max_length=255), + ), + migrations.AlterField( + model_name='extension', + name='name', + field=models.CharField(help_text='Zone name, must begin with a dot (.example.org)', max_length=255, unique=True), + ), + migrations.AlterField( + model_name='extension', + name='origin', + field=models.ForeignKey(blank=True, help_text='A record associated with the zone', null=True, on_delete=django.db.models.deletion.PROTECT, to='machines.IpList'), + ), + migrations.AlterField( + model_name='extension', + name='origin_v6', + field=models.GenericIPAddressField(blank=True, help_text='AAAA record associated with the zone', null=True, protocol='IPv6'), + ), + migrations.AlterField( + model_name='iptype', + name='domaine_ip_netmask', + field=models.IntegerField(default=24, help_text="Netmask for the domain's IPv4 range", validators=[django.core.validators.MaxValueValidator(31), django.core.validators.MinValueValidator(8)]), + ), + migrations.AlterField( + model_name='iptype', + name='domaine_ip_network', + field=models.GenericIPAddressField(blank=True, help_text="Network containing the domain's IPv4 range (optional)", null=True, protocol='IPv4'), + ), + migrations.AlterField( + model_name='iptype', + name='reverse_v4', + field=models.BooleanField(default=False, help_text='Enable reverse DNS for IPv4'), + ), + migrations.AlterField( + model_name='iptype', + name='reverse_v6', + field=models.BooleanField(default=False, help_text='Enable reverse DNS for IPv6'), + ), + migrations.AlterField( + model_name='machine', + name='name', + field=models.CharField(blank=True, help_text='Optional', max_length=255, null=True), + ), + migrations.AlterField( + model_name='ouvertureportlist', + name='name', + field=models.CharField(help_text='Name of the ports configuration', max_length=255), + ), + migrations.AlterField( + model_name='role', + name='specific_role', + field=models.CharField(blank=True, choices=[('dhcp-server', 'DHCP server'), ('switch-conf-server', 'Switches configuration server'), ('dns-recursif-server', 'Recursive DNS server'), ('ntp-server', 'NTP server'), ('radius-server', 'RADIUS server'), ('log-server', 'Log server'), ('ldap-master-server', 'LDAP master server'), ('ldap-backup-server', 'LDAP backup server'), ('smtp-server', 'SMTP server'), ('postgresql-server', 'postgreSQL server'), ('mysql-server', 'mySQL server'), ('sql-client', 'SQL client'), ('gateway', 'Gateway')], max_length=32, null=True), + ), + migrations.AlterField( + model_name='service', + name='min_time_regen', + field=models.DurationField(default=datetime.timedelta(0, 60), help_text='Minimal time before regeneration of the service.'), + ), + migrations.AlterField( + model_name='service', + name='regular_time_regen', + field=models.DurationField(default=datetime.timedelta(0, 3600), help_text='Maximal time before regeneration of the service.'), + ), + migrations.AlterField( + model_name='soa', + name='expire', + field=models.PositiveIntegerField(default=3600000, help_text='Seconds before the secondary DNS stop answering requests in case of primary DNS timeout'), + ), + migrations.AlterField( + model_name='soa', + name='mail', + field=models.EmailField(help_text='Contact email address for the zone', max_length=254), + ), + migrations.AlterField( + model_name='soa', + name='refresh', + field=models.PositiveIntegerField(default=86400, help_text='Seconds before the secondary DNS have to ask the primary DNS serial to detect a modification'), + ), + migrations.AlterField( + model_name='soa', + name='retry', + field=models.PositiveIntegerField(default=7200, help_text='Seconds before the secondary DNS ask the serial again in case of a primary DNS timeout'), + ), + migrations.AlterField( + model_name='soa', + name='ttl', + field=models.PositiveIntegerField(default=172800, help_text='Time to Live'), + ), + migrations.AlterField( + model_name='srv', + name='port', + field=models.PositiveIntegerField(help_text='TCP/UDP port', validators=[django.core.validators.MaxValueValidator(65535)]), + ), + migrations.AlterField( + model_name='srv', + name='priority', + field=models.PositiveIntegerField(default=0, help_text='Priority of the target server (positive integer value, the lower it is, the more the server will be used if available)', validators=[django.core.validators.MaxValueValidator(65535)]), + ), + migrations.AlterField( + model_name='srv', + name='target', + field=models.ForeignKey(help_text='Target server', on_delete=django.db.models.deletion.PROTECT, to='machines.Domain'), + ), + migrations.AlterField( + model_name='srv', + name='ttl', + field=models.PositiveIntegerField(default=172800, help_text='Time to Live'), + ), + migrations.AlterField( + model_name='srv', + name='weight', + field=models.PositiveIntegerField(default=0, help_text='Relative weight for records with the same priority (integer value between 0 and 65535)', validators=[django.core.validators.MaxValueValidator(65535)]), + ), + ] diff --git a/machines/models.py b/machines/models.py index f201f667..4de0b012 100644 --- a/machines/models.py +++ b/machines/models.py @@ -42,8 +42,8 @@ from django.dispatch import receiver from django.forms import ValidationError from django.utils.functional import cached_property from django.utils import timezone -from django.utils.translation import ugettext_lazy as _l from django.core.validators import MaxValueValidator, MinValueValidator +from django.utils.translation import ugettext_lazy as _ from macaddress.fields import MACAddressField @@ -57,12 +57,10 @@ import preferences.models class Machine(RevMixin, FieldPermissionModelMixin, models.Model): """ Class définissant une machine, object parent user, objets fils interfaces""" - PRETTY_NAME = "Machine" - user = models.ForeignKey('users.User', on_delete=models.PROTECT) name = models.CharField( max_length=255, - help_text="Optionnel", + help_text=_("Optional"), blank=True, null=True ) @@ -70,10 +68,12 @@ class Machine(RevMixin, FieldPermissionModelMixin, models.Model): class Meta: permissions = ( - ("view_machine", "Peut voir un objet machine quelquonque"), + ("view_machine", _("Can view a machine object")), ("change_machine_user", - "Peut changer le propriétaire d'une machine"), + _("Can change the user of a machine")), ) + verbose_name = _("machine") + verbose_name_plural = _("machines") @classmethod def get_instance(cls, machineid, *_args, **_kwargs): @@ -106,7 +106,7 @@ class Machine(RevMixin, FieldPermissionModelMixin, models.Model): explanation message. """ return (user_request.has_perm('machines.change_machine_user'), - "Vous ne pouvez pas modifier l'utilisateur de la machine.") + _("You don't have the right to change the machine's user.")) @staticmethod def can_view_all(user_request, *_args, **_kwargs): @@ -115,8 +115,8 @@ class Machine(RevMixin, FieldPermissionModelMixin, models.Model): :param user_request: instance user qui fait l'edition :return: True ou False avec la raison de l'échec le cas échéant""" if not user_request.has_perm('machines.view_machine'): - return False, (u"Vous ne pouvez pas afficher l'ensemble des " - "machines sans permission") + return False, _("You don't have the right to view all the" + " machines.") return True, None @staticmethod @@ -129,7 +129,7 @@ class Machine(RevMixin, FieldPermissionModelMixin, models.Model): try: user = users.models.User.objects.get(pk=userid) except users.models.User.DoesNotExist: - return False, u"Utilisateur inexistant" + return False, _("Nonexistent user.") max_lambdauser_interfaces = (preferences.models.OptionalMachine .get_cached_value( 'max_lambdauser_interfaces' @@ -137,14 +137,14 @@ class Machine(RevMixin, FieldPermissionModelMixin, models.Model): if not user_request.has_perm('machines.add_machine'): if not (preferences.models.OptionalMachine .get_cached_value('create_machine')): - return False, u"Vous ne pouvez pas ajouter une machine" + return False, (_("You don't have the right to add a machine.")) if user != user_request: - return False, (u"Vous ne pouvez pas ajouter une machine à un " - "autre user que vous sans droit") + return False, (_("You don't have the right to add a machine" + " to another user.")) if user.user_interfaces().count() >= max_lambdauser_interfaces: - return False, (u"Vous avez atteint le maximum d'interfaces " - "autorisées que vous pouvez créer vous même " - "(%s) " % max_lambdauser_interfaces) + return False, (_("You reached the maximum number of interfaces" + " that you are allowed to create yourself" + " (%s)." % max_lambdauser_interfaces)) return True, None def can_edit(self, user_request, *args, **kwargs): @@ -160,9 +160,9 @@ class Machine(RevMixin, FieldPermissionModelMixin, models.Model): user_request, *args, **kwargs - )[0]): - return False, (u"Vous ne pouvez pas éditer une machine " - "d'un autre user que vous sans droit") + )[0]): + return False, (_("You don't have the right to edit a machine" + " of another user.")) return True, None def can_delete(self, user_request, *args, **kwargs): @@ -178,9 +178,9 @@ class Machine(RevMixin, FieldPermissionModelMixin, models.Model): user_request, *args, **kwargs - )[0]): - return False, (u"Vous ne pouvez pas éditer une machine " - "d'un autre user que vous sans droit") + )[0]): + return False, _("You don't have the right to delete a machine" + " of another user.") return True, None def can_view(self, user_request, *_args, **_kwargs): @@ -191,8 +191,8 @@ class Machine(RevMixin, FieldPermissionModelMixin, models.Model): :return: True ou False avec la raison de l'échec le cas échéant""" if (not user_request.has_perm('machines.view_machine') and self.user != user_request): - return False, (u"Vous n'avez pas droit de voir les machines autre " - "que les vôtres") + return False, _("You don't have the right to view other machines" + " than yours.") return True, None @cached_property @@ -228,8 +228,6 @@ class Machine(RevMixin, FieldPermissionModelMixin, models.Model): class MachineType(RevMixin, AclMixin, models.Model): """ Type de machine, relié à un type d'ip, affecté aux interfaces""" - PRETTY_NAME = "Type de machine" - type = models.CharField(max_length=255) ip_type = models.ForeignKey( 'IpType', @@ -240,10 +238,11 @@ class MachineType(RevMixin, AclMixin, models.Model): class Meta: permissions = ( - ("view_machinetype", "Peut voir un objet machinetype"), - ("use_all_machinetype", - "Peut utiliser n'importe quel type de machine"), + ("view_machinetype", _("Can view a machine type object")), + ("use_all_machinetype", _("Can use all machine types")), ) + verbose_name = _("machine type") + verbose_name_plural = _("machine types") def all_interfaces(self): """ Renvoie toutes les interfaces (cartes réseaux) de type @@ -261,8 +260,8 @@ class MachineType(RevMixin, AclMixin, models.Model): message is acces is not allowed. """ if not user_request.has_perm('machines.use_all_machinetype'): - return False, (u"Vous n'avez pas le droit d'utiliser tout types " - "de machines") + return False, (_("You don't have the right to use all machine" + " types.")) return True, None def __str__(self): @@ -271,8 +270,6 @@ class MachineType(RevMixin, AclMixin, models.Model): class IpType(RevMixin, AclMixin, models.Model): """ Type d'ip, définissant un range d'ip, affecté aux machine types""" - PRETTY_NAME = "Type d'ip" - type = models.CharField(max_length=255) extension = models.ForeignKey('Extension', on_delete=models.PROTECT) need_infra = models.BooleanField(default=False) @@ -282,7 +279,7 @@ class IpType(RevMixin, AclMixin, models.Model): protocol='IPv4', null=True, blank=True, - help_text="Network containing the ipv4 range domain ip start/stop. Optional" + help_text=_("Network containing the domain's IPv4 range (optional)") ) domaine_ip_netmask = models.IntegerField( default=24, @@ -290,11 +287,11 @@ class IpType(RevMixin, AclMixin, models.Model): MaxValueValidator(31), MinValueValidator(8) ], - help_text="Netmask for the ipv4 range domain" + help_text=_("Netmask for the domain's IPv4 range") ) reverse_v4 = models.BooleanField( default=False, - help_text="Enable reverse DNS for IPv4", + help_text=_("Enable reverse DNS for IPv4"), ) prefix_v6 = models.GenericIPAddressField( protocol='IPv6', @@ -310,7 +307,7 @@ class IpType(RevMixin, AclMixin, models.Model): ) reverse_v6 = models.BooleanField( default=False, - help_text="Enable reverse DNS for IPv6", + help_text=_("Enable reverse DNS for IPv6"), ) vlan = models.ForeignKey( 'Vlan', @@ -326,9 +323,11 @@ class IpType(RevMixin, AclMixin, models.Model): class Meta: permissions = ( - ("view_iptype", "Peut voir un objet iptype"), - ("use_all_iptype", "Peut utiliser tous les iptype"), + ("view_iptype", _("Can view an IP type object")), + ("use_all_iptype", _("Can use all IP types")), ) + verbose_name = _("IP type") + verbose_name_plural = ("IP types") @cached_property def ip_range(self): @@ -431,8 +430,9 @@ class IpType(RevMixin, AclMixin, models.Model): """ Methode dépréciée, IpList est en mode cascade et supprimé automatiquement""" if Interface.objects.filter(ipv4__in=self.ip_objects()): - raise ValidationError("Une ou plusieurs ip du range sont\ - affectées, impossible de supprimer le range") + raise ValidationError(_("One or several IP addresses from the" + " range are affected, impossible to delete" + " the range.")) for ip in self.ip_objects(): ip.delete() @@ -472,24 +472,25 @@ class IpType(RevMixin, AclMixin, models.Model): - Que le range crée ne recoupe pas un range existant - Formate l'ipv6 donnée en /64""" if IPAddress(self.domaine_ip_start) > IPAddress(self.domaine_ip_stop): - raise ValidationError("Domaine end doit être après start...") + raise ValidationError(_("Range end must be after range start...")) # On ne crée pas plus grand qu'un /16 if self.ip_range.size > 65536: - raise ValidationError("Le range est trop gros, vous ne devez\ - pas créer plus grand qu'un /16") + raise ValidationError(_("The range is too large, you can't create" + " a larger one than a /16.")) # On check que les / ne se recoupent pas for element in IpType.objects.all().exclude(pk=self.pk): if not self.ip_set.isdisjoint(element.ip_set): - raise ValidationError("Le range indiqué n'est pas disjoint\ - des ranges existants") + raise ValidationError(_("The specified range is not disjoint" + " from existing ranges.")) # On formate le prefix v6 if self.prefix_v6: self.prefix_v6 = str(IPNetwork(self.prefix_v6 + '/64').network) # On vérifie qu'un domaine network/netmask contiens bien le domaine ip start-stop if self.domaine_ip_network: if not self.domaine_ip_start in self.ip_network or not self.domaine_ip_stop in self.ip_network: - raise ValidationError("If you specify a domaine ip network/netmask, it\ - must contain domaine ipstart-stop range") + raise ValidationError(_("If you specify a domain network or" + " netmask, it must contain the" + " domain's IP range.")) return def save(self, *args, **kwargs): @@ -511,16 +512,16 @@ class IpType(RevMixin, AclMixin, models.Model): class Vlan(RevMixin, AclMixin, models.Model): """ Un vlan : vlan_id et nom On limite le vlan id entre 0 et 4096, comme défini par la norme""" - PRETTY_NAME = "Vlans" - vlan_id = models.PositiveIntegerField(validators=[MaxValueValidator(4095)]) name = models.CharField(max_length=256) comment = models.CharField(max_length=256, blank=True) class Meta: permissions = ( - ("view_vlan", "Peut voir un objet vlan"), + ("view_vlan", _("Can view a VLAN object")), ) + verbose_name = _("VLAN") + verbose_name_plural = _("VLANs") def __str__(self): return self.name @@ -530,8 +531,6 @@ class Nas(RevMixin, AclMixin, models.Model): """ Les nas. Associé à un machine_type. Permet aussi de régler le port_access_mode (802.1X ou mac-address) pour le radius. Champ autocapture de la mac à true ou false""" - PRETTY_NAME = "Correspondance entre les nas et les machines connectées" - default_mode = '802.1X' AUTH = ( ('802.1X', '802.1X'), @@ -558,8 +557,10 @@ class Nas(RevMixin, AclMixin, models.Model): class Meta: permissions = ( - ("view_nas", "Peut voir un objet Nas"), + ("view_nas", _("Can view a NAS device object")), ) + verbose_name = _("NAS device") + verbose_name_plural = _("NAS devices") def __str__(self): return self.name @@ -571,36 +572,36 @@ class SOA(RevMixin, AclMixin, models.Model): Les valeurs par défault viennent des recommandations RIPE : https://www.ripe.net/publications/docs/ripe-203 """ - PRETTY_NAME = "Enregistrement SOA" - name = models.CharField(max_length=255) mail = models.EmailField( - help_text='Email du contact pour la zone' + help_text=_("Contact email address for the zone") ) refresh = models.PositiveIntegerField( default=86400, # 24 hours - help_text='Secondes avant que les DNS secondaires doivent demander le\ - serial du DNS primaire pour détecter une modification' + help_text=_("Seconds before the secondary DNS have to ask the primary" + " DNS serial to detect a modification") ) retry = models.PositiveIntegerField( default=7200, # 2 hours - help_text='Secondes avant que les DNS secondaires fassent une nouvelle\ - demande de serial en cas de timeout du DNS primaire' + help_text=_("Seconds before the secondary DNS ask the serial again in" + " case of a primary DNS timeout") ) expire = models.PositiveIntegerField( default=3600000, # 1000 hours - help_text='Secondes après lesquelles les DNS secondaires arrêtent de\ - de répondre aux requêtes en cas de timeout du DNS primaire' + help_text=_("Seconds before the secondary DNS stop answering requests" + " in case of primary DNS timeout") ) ttl = models.PositiveIntegerField( default=172800, # 2 days - help_text='Time To Live' + help_text=_("Time to Live") ) class Meta: permissions = ( - ("view_soa", "Peut voir un objet soa"), + ("view_soa", _("Can view an SOA record object")), ) + verbose_name = _("SOA record") + verbose_name_plural = _("SOA records") def __str__(self): return str(self.name) @@ -639,7 +640,7 @@ class SOA(RevMixin, AclMixin, models.Model): /!\ Ne jamais supprimer ou renommer cette fonction car elle est utilisée dans les migrations de la BDD. """ return cls.objects.get_or_create( - name="SOA to edit", + name=_("SOA to edit"), mail="postmaser@example.com" )[0].pk @@ -647,12 +648,10 @@ class SOA(RevMixin, AclMixin, models.Model): class Extension(RevMixin, AclMixin, models.Model): """ Extension dns type example.org. Précise si tout le monde peut l'utiliser, associé à un origin (ip d'origine)""" - PRETTY_NAME = "Extensions dns" - name = models.CharField( max_length=255, unique=True, - help_text="Nom de la zone, doit commencer par un point (.example.org)" + help_text=_("Zone name, must begin with a dot (.example.org)") ) need_infra = models.BooleanField(default=False) origin = models.ForeignKey( @@ -660,13 +659,13 @@ class Extension(RevMixin, AclMixin, models.Model): on_delete=models.PROTECT, blank=True, null=True, - help_text="Enregistrement A associé à la zone" + help_text=_("A record associated with the zone") ) origin_v6 = models.GenericIPAddressField( protocol='IPv6', null=True, blank=True, - help_text="Enregistrement AAAA associé à la zone" + help_text=_("AAAA record associated with the zone") ) soa = models.ForeignKey( 'SOA', @@ -675,9 +674,11 @@ class Extension(RevMixin, AclMixin, models.Model): class Meta: permissions = ( - ("view_extension", "Peut voir un objet extension"), - ("use_all_extension", "Peut utiliser toutes les extension"), + ("view_extension", _("Can view an extension object")), + ("use_all_extension", _("Can use all extensions")), ) + verbose_name = _("DNS extension") + verbose_name_plural = _("DNS extensions") @cached_property def dns_entry(self): @@ -728,7 +729,7 @@ class Extension(RevMixin, AclMixin, models.Model): def clean(self, *args, **kwargs): if self.name and self.name[0] != '.': - raise ValidationError("Une extension doit commencer par un point") + raise ValidationError(_("An extension must begin with a dot.")) super(Extension, self).clean(*args, **kwargs) @@ -736,16 +737,16 @@ class Mx(RevMixin, AclMixin, models.Model): """ Entrées des MX. Enregistre la zone (extension) associée et la priorité Todo : pouvoir associer un MX à une interface """ - PRETTY_NAME = "Enregistrements MX" - zone = models.ForeignKey('Extension', on_delete=models.PROTECT) priority = models.PositiveIntegerField() name = models.ForeignKey('Domain', on_delete=models.PROTECT) class Meta: permissions = ( - ("view_mx", "Peut voir un objet mx"), + ("view_mx", _("Can view an MX record object")), ) + verbose_name = _("MX record") + verbose_name_plural = _("MX records") @cached_property def dns_entry(self): @@ -762,15 +763,15 @@ class Mx(RevMixin, AclMixin, models.Model): class Ns(RevMixin, AclMixin, models.Model): """Liste des enregistrements name servers par zone considéérée""" - PRETTY_NAME = "Enregistrements NS" - zone = models.ForeignKey('Extension', on_delete=models.PROTECT) ns = models.ForeignKey('Domain', on_delete=models.PROTECT) class Meta: permissions = ( - ("view_ns", "Peut voir un objet ns"), + ("view_ns", _("Can view an NS record object")), ) + verbose_name = _("NS record") + verbose_name_plural = _("NS records") @cached_property def dns_entry(self): @@ -783,16 +784,16 @@ class Ns(RevMixin, AclMixin, models.Model): class Txt(RevMixin, AclMixin, models.Model): """ Un enregistrement TXT associé à une extension""" - PRETTY_NAME = "Enregistrement TXT" - zone = models.ForeignKey('Extension', on_delete=models.PROTECT) field1 = models.CharField(max_length=255) field2 = models.TextField(max_length=2047) class Meta: permissions = ( - ("view_txt", "Peut voir un objet txt"), + ("view_txt", _("Can view a TXT record object")), ) + verbose_name = _("TXT record") + verbose_name_plural = _("TXT records") def __str__(self): return str(self.zone) + " : " + str(self.field1) + " " +\ @@ -811,10 +812,10 @@ class DName(RevMixin, AclMixin, models.Model): class Meta: permissions = ( - ("view_dname", "Can see a dname object"), + ("view_dname", _("Can view a DNAME record object")), ) - verbose_name = "DNAME entry" - verbose_name_plural = "DNAME entries" + verbose_name = _("DNAME record") + verbose_name_plural = _("DNAME records") def __str__(self): return str(self.zone) + " : " + str(self.alias) @@ -827,8 +828,6 @@ class DName(RevMixin, AclMixin, models.Model): class Srv(RevMixin, AclMixin, models.Model): """ A SRV record """ - PRETTY_NAME = "Enregistrement Srv" - TCP = 'TCP' UDP = 'UDP' @@ -844,35 +843,37 @@ class Srv(RevMixin, AclMixin, models.Model): extension = models.ForeignKey('Extension', on_delete=models.PROTECT) ttl = models.PositiveIntegerField( default=172800, # 2 days - help_text='Time To Live' + help_text=_("Time to Live") ) priority = models.PositiveIntegerField( default=0, validators=[MaxValueValidator(65535)], - help_text=("La priorité du serveur cible (valeur entière non " - "négative, plus elle est faible, plus ce serveur sera " - "utilisé s'il est disponible)") + help_text=_("Priority of the target server (positive integer value," + " the lower it is, the more the server will be used if" + " available)") ) weight = models.PositiveIntegerField( default=0, validators=[MaxValueValidator(65535)], - help_text="Poids relatif pour les enregistrements de même priorité\ - (valeur entière de 0 à 65535)" + help_text=_("Relative weight for records with the same priority" + " (integer value between 0 and 65535)") ) port = models.PositiveIntegerField( validators=[MaxValueValidator(65535)], - help_text="Port (tcp/udp)" + help_text=_("TCP/UDP port") ) target = models.ForeignKey( 'Domain', on_delete=models.PROTECT, - help_text="Serveur cible" + help_text=_("Target server") ) class Meta: permissions = ( - ("view_srv", "Peut voir un objet srv"), + ("view_srv", _("Can view an SRV record object")), ) + verbose_name = _("SRV record") + verbose_name_plural = _("SRV records") def __str__(self): return str(self.service) + ' ' + str(self.protocole) + ' ' +\ @@ -936,10 +937,10 @@ class SshFp(RevMixin, AclMixin, models.Model): class Meta: permissions = ( - ("view_sshfp", "Can see an SSHFP record"), + ("view_sshfp", _("Can view an SSHFP record object")), ) - verbose_name = "SSHFP record" - verbose_name_plural = "SSHFP records" + verbose_name = _("SSHFP record") + verbose_name_plural = _("SSHFP records") def can_view(self, user_request, *_args, **_kwargs): return self.machine.can_view(user_request, *_args, **_kwargs) @@ -963,8 +964,6 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): - le type parent associé au range ip et à l'extension - un objet domain associé contenant son nom - la liste des ports oiuvert""" - PRETTY_NAME = "Interface" - ipv4 = models.OneToOneField( 'IpList', on_delete=models.PROTECT, @@ -979,10 +978,12 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): class Meta: permissions = ( - ("view_interface", "Peut voir un objet interface"), + ("view_interface", _("Can view an interface object")), ("change_interface_machine", - "Peut changer le propriétaire d'une interface"), + _("Can change the owner of an interface")), ) + verbose_name = _("interface") + verbose_name_plural = _("interfaces") @cached_property def is_active(self): @@ -1076,7 +1077,7 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): try: self.mac_address = str(EUI(self.mac_address)) except: - raise ValidationError("La mac donnée est invalide") + raise ValidationError(_("The given MAC address is invalid.")) def clean(self, *args, **kwargs): """ Formate l'addresse mac en mac_bare (fonction filter_mac) @@ -1089,7 +1090,7 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): # But in our case, it's impossible to create a type value so we raise # the error. if not hasattr(self, 'type'): - raise ValidationError("Le type d'ip choisi n'est pas valide") + raise ValidationError(_("The selected IP type is invalid.")) self.filter_macaddress() self.mac_address = str(EUI(self.mac_address)) or None if not self.ipv4 or self.type.ip_type != self.ipv4.ip_type: @@ -1102,8 +1103,8 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): if free_ips: self.ipv4 = free_ips[0] else: - raise ValidationError("Il n'y a plus d'ip disponibles\ - dans le slash") + raise ValidationError(_("There is no IP address available in the" + " slash.")) return def unassign_ipv4(self): @@ -1120,8 +1121,8 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): # On verifie la cohérence en forçant l'extension par la méthode if self.ipv4: if self.type.ip_type != self.ipv4.ip_type: - raise ValidationError("L'ipv4 et le type de la machine ne\ - correspondent pas") + raise ValidationError(_("The IPv4 address and the machine type" + " don't match.")) super(Interface, self).save(*args, **kwargs) @staticmethod @@ -1134,23 +1135,23 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): try: machine = Machine.objects.get(pk=machineid) except Machine.DoesNotExist: - return False, u"Machine inexistante" + return False, _("Nonexistent machine.") if not user_request.has_perm('machines.add_interface'): if not (preferences.models.OptionalMachine .get_cached_value('create_machine')): - return False, u"Vous ne pouvez pas ajouter une machine" + return False, _("You can't add a machine.") max_lambdauser_interfaces = (preferences.models.OptionalMachine .get_cached_value( 'max_lambdauser_interfaces' )) if machine.user != user_request: - return False, u"Vous ne pouvez pas ajouter une interface à une\ - machine d'un autre user que vous sans droit" + return False, _("You don't have the right to add an interface" + " to a machine of another user.") if (machine.user.user_interfaces().count() >= max_lambdauser_interfaces): - return False, u"Vous avez atteint le maximum d'interfaces\ - autorisées que vous pouvez créer vous même (%s) "\ - % max_lambdauser_interfaces + return False, (_("You reached the maximum number of interfaces" + " that you are allowed to create yourself" + " (%s)." % max_lambdauser_interfaces)) return True, None @staticmethod @@ -1158,7 +1159,7 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): """Check if a user can change the machine associated with an Interface object """ return (user_request.has_perm('machines.change_interface_machine'), - "Droit requis pour changer la machine") + _("Permission required to edit the machine.")) def can_edit(self, user_request, *args, **kwargs): """Verifie que l'user a les bons droits infra pour editer @@ -1172,9 +1173,9 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): user_request, *args, **kwargs - )[0]): - return False, (u"Vous ne pouvez pas éditer une machine " - "d'un autre user que vous sans droit") + )[0]): + return False, _("You don't have the right to edit a machine of" + " another user.") return True, None def can_delete(self, user_request, *args, **kwargs): @@ -1189,9 +1190,9 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): user_request, *args, **kwargs - )[0]): - return False, (u"Vous ne pouvez pas éditer une machine " - "d'un autre user que vous sans droit") + )[0]): + return False, _("You don't have the right to edit a machine of" + " another user.") return True, None def can_view(self, user_request, *_args, **_kwargs): @@ -1202,8 +1203,8 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): :return: True ou False avec la raison de l'échec le cas échéant""" if (not user_request.has_perm('machines.view_interface') and self.machine.user != user_request): - return False, (u"Vous n'avez pas le droit de voir des machines " - "autre que les vôtres") + return False, _("You don't have the right to view machines other" + " than yours.") return True, None def __init__(self, *args, **kwargs): @@ -1235,7 +1236,6 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): class Ipv6List(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): """ A list of IPv6 """ - PRETTY_NAME = 'Enregistrements Ipv6 des machines' ipv6 = models.GenericIPAddressField( protocol='IPv6', @@ -1249,10 +1249,12 @@ class Ipv6List(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): class Meta: permissions = ( - ("view_ipv6list", "Peut voir un objet ipv6"), - ("change_ipv6list_slaac_ip", - "Peut changer la valeur slaac sur une ipv6"), + ("view_ipv6list", _("Can view an IPv6 addresses list object")), + ("change_ipv6list_slaac_ip", _("Can change the SLAAC value of an" + " IPv6 addresses list")), ) + verbose_name = _("IPv6 addresses list") + verbose_name_plural = _("IPv6 addresses lists") @staticmethod def can_create(user_request, interfaceid, *_args, **_kwargs): @@ -1264,18 +1266,19 @@ class Ipv6List(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): try: interface = Interface.objects.get(pk=interfaceid) except Interface.DoesNotExist: - return False, u"Interface inexistante" + return False, _("Nonexistent interface.") if not user_request.has_perm('machines.add_ipv6list'): if interface.machine.user != user_request: - return False, u"Vous ne pouvez pas ajouter un alias à une\ - machine d'un autre user que vous sans droit" + return False, _("You don't have the right to add an alias to a" + " machine of another user.") return True, None @staticmethod def can_change_slaac_ip(user_request, *_args, **_kwargs): """ Check if a user can change the slaac value """ return (user_request.has_perm('machines.change_ipv6list_slaac_ip'), - "Droit requis pour changer la valeur slaac ip") + _("Permission required to change the SLAAC value of an IPv6" + " address")) def can_edit(self, user_request, *args, **kwargs): """Verifie que l'user a les bons droits infra pour editer @@ -1289,9 +1292,9 @@ class Ipv6List(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): user_request, *args, **kwargs - )[0]): - return False, (u"Vous ne pouvez pas éditer une machine " - "d'un autre user que vous sans droit") + )[0]): + return False, _("You don't have the right to edit a machine of" + " another user.") return True, None def can_delete(self, user_request, *args, **kwargs): @@ -1306,9 +1309,9 @@ class Ipv6List(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): user_request, *args, **kwargs - )[0]): - return False, (u"Vous ne pouvez pas éditer une machine " - "d'un autre user que vous sans droit") + )[0]): + return False, _("You don't have the right to edit a machine of" + " another user.") return True, None def can_view(self, user_request, *_args, **_kwargs): @@ -1319,8 +1322,8 @@ class Ipv6List(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): :return: True ou False avec la raison de l'échec le cas échéant""" if (not user_request.has_perm('machines.view_ipv6list') and self.interface.machine.user != user_request): - return False, (u"Vous n'avez pas le droit de voir des machines " - "autre que les vôtres") + return False, _("You don't have the right to view machines other" + " than yours.") return True, None def __init__(self, *args, **kwargs): @@ -1346,15 +1349,14 @@ class Ipv6List(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): if self.slaac_ip and (Ipv6List.objects .filter(interface=self.interface, slaac_ip=True) .exclude(id=self.id)): - raise ValidationError("Une ip slaac est déjà enregistrée") + raise ValidationError(_("A SLAAC IP address is already registered.")) prefix_v6 = self.interface.type.ip_type.prefix_v6.encode().decode('utf-8') if prefix_v6: if (IPv6Address(self.ipv6.encode().decode('utf-8')).exploded[:20] != IPv6Address(prefix_v6).exploded[:20]): - raise ValidationError( - "Le prefixv6 est incorrect et ne correspond pas au type " - "associé à la machine" - ) + raise ValidationError(_("The v6 prefix is incorrect and" + " doesn't match the type associated" + " with the machine.")) super(Ipv6List, self).clean(*args, **kwargs) def save(self, *args, **kwargs): @@ -1370,7 +1372,6 @@ class Domain(RevMixin, AclMixin, models.Model): """ Objet domain. Enregistrement A et CNAME en même temps : permet de stocker les alias et les nom de machines, suivant si interface_parent ou cname sont remplis""" - PRETTY_NAME = "Domaine dns" interface_parent = models.OneToOneField( 'Interface', @@ -1379,7 +1380,7 @@ class Domain(RevMixin, AclMixin, models.Model): null=True ) name = models.CharField( - help_text="Obligatoire et unique, ne doit pas comporter de points", + help_text=_("Mandatory and unique, must not contain dots."), max_length=255 ) extension = models.ForeignKey('Extension', on_delete=models.PROTECT) @@ -1393,8 +1394,10 @@ class Domain(RevMixin, AclMixin, models.Model): class Meta: unique_together = (("name", "extension"),) permissions = ( - ("view_domain", "Peut voir un objet domain"), + ("view_domain", _("Can view a domain object")), ) + verbose_name = _("domain") + verbose_name_plural = _("domains") def get_extension(self): """ Retourne l'extension de l'interface parente si c'est un A @@ -1416,20 +1419,22 @@ class Domain(RevMixin, AclMixin, models.Model): if self.get_extension(): self.extension = self.get_extension() if self.interface_parent and self.cname: - raise ValidationError("On ne peut créer à la fois A et CNAME") + raise ValidationError(_("You can't create a both A and CNAME" + " record.")) if self.cname == self: - raise ValidationError("On ne peut créer un cname sur lui même") + raise ValidationError(_("You can't create a CNAME record pointing" + " to itself.")) HOSTNAME_LABEL_PATTERN = re.compile( r"(?!-)[A-Z\d-]+(? 63: - raise ValidationError("Le nom de domaine %s est trop long\ - (maximum de 63 caractères)." % dns) + raise ValidationError(_("The domain name %s is too long (over 63" + " characters).") % dns) if not HOSTNAME_LABEL_PATTERN.match(dns): - raise ValidationError("Ce nom de domaine %s contient des\ - carractères interdits." % dns) + raise ValidationError(_("The domain name %s contains forbidden" + " characters.") % dns) self.validate_unique() super(Domain, self).clean() @@ -1446,7 +1451,7 @@ class Domain(RevMixin, AclMixin, models.Model): """ Empèche le save sans extension valide. Force à avoir appellé clean avant""" if not self.get_extension(): - raise ValidationError("Extension invalide") + raise ValidationError(_("Invalid extension.")) self.full_clean() super(Domain, self).save(*args, **kwargs) @@ -1472,24 +1477,24 @@ class Domain(RevMixin, AclMixin, models.Model): try: interface = Interface.objects.get(pk=interfaceid) except Interface.DoesNotExist: - return False, u"Interface inexistante" + return False, _("Nonexistent interface.") if not user_request.has_perm('machines.add_domain'): max_lambdauser_aliases = (preferences.models.OptionalMachine .get_cached_value( 'max_lambdauser_aliases' )) if interface.machine.user != user_request: - return False, (u"Vous ne pouvez pas ajouter un alias à une " - "machine d'un autre user que vous sans droit") + return False, _("You don't have the right to add an alias to a" + " machine of another user.") if Domain.objects.filter( - cname__in=Domain.objects.filter( - interface_parent__in=(interface.machine.user - .user_interfaces()) - ) - ).count() >= max_lambdauser_aliases: - return False, (u"Vous avez atteint le maximum d'alias " - "autorisés que vous pouvez créer vous même " - "(%s) " % max_lambdauser_aliases) + cname__in=Domain.objects.filter( + interface_parent__in=(interface.machine.user + .user_interfaces()) + ) + ).count() >= max_lambdauser_aliases: + return False, _("You reached the maximum number of alias that" + " you are allowed to create yourself (%s). " + % max_lambdauser_aliases) return True, None def can_edit(self, user_request, *_args, **_kwargs): @@ -1500,8 +1505,8 @@ class Domain(RevMixin, AclMixin, models.Model): :return: soit True, soit False avec la raison de l'échec""" if (not user_request.has_perm('machines.change_domain') and self.get_source_interface.machine.user != user_request): - return False, (u"Vous ne pouvez pas editer un alias à une machine " - "d'un autre user que vous sans droit") + return False, _("You don't have the right to edit an alias of a" + " machine of another user.") return True, None def can_delete(self, user_request, *_args, **_kwargs): @@ -1512,8 +1517,8 @@ class Domain(RevMixin, AclMixin, models.Model): :return: soit True, soit False avec la raison de l'échec""" if (not user_request.has_perm('machines.delete_domain') and self.get_source_interface.machine.user != user_request): - return False, (u"Vous ne pouvez pas supprimer un alias à une " - "machine d'un autre user que vous sans droit") + return False, _("You don't have the right to delete an alias of a" + " machine of another user.") return True, None def can_view(self, user_request, *_args, **_kwargs): @@ -1524,8 +1529,8 @@ class Domain(RevMixin, AclMixin, models.Model): :return: True ou False avec la raison de l'échec le cas échéant""" if (not user_request.has_perm('machines.view_domain') and self.get_source_interface.machine.user != user_request): - return False, (u"Vous n'avez pas le droit de voir des machines " - "autre que les vôtres") + return False, _("You don't have the right to view machines other" + " than yours.") return True, None def __str__(self): @@ -1534,15 +1539,16 @@ class Domain(RevMixin, AclMixin, models.Model): class IpList(RevMixin, AclMixin, models.Model): """ A list of IPv4 """ - PRETTY_NAME = "Addresses ipv4" ipv4 = models.GenericIPAddressField(protocol='IPv4', unique=True) ip_type = models.ForeignKey('IpType', on_delete=models.CASCADE) class Meta: permissions = ( - ("view_iplist", "Peut voir un objet iplist"), + ("view_iplist", _("Can view an IPv4 addresses list object")), ) + verbose_name = _("IPv4 addresses list") + verbose_name_plural = _("IPv4 addresses lists") @cached_property def need_infra(self): @@ -1553,8 +1559,8 @@ class IpList(RevMixin, AclMixin, models.Model): def clean(self): """ Erreur si l'ip_type est incorrect""" if not str(self.ipv4) in self.ip_type.ip_set_as_str: - raise ValidationError("L'ipv4 et le range de l'iptype ne\ - correspondent pas!") + raise ValidationError(_("The IPv4 address and the range of the IP" + " type don't match.")) return def save(self, *args, **kwargs): @@ -1571,19 +1577,19 @@ class Role(RevMixin, AclMixin, models.Model): """ ROLE = ( - ('dhcp-server', _l('DHCP server')), - ('switch-conf-server', _l('Switches configuration server')), - ('dns-recursif-server', _l('Recursive DNS server')), - ('ntp-server', _l('NTP server')), - ('radius-server', _l('Radius server')), - ('log-server', _l('Log server')), - ('ldap-master-server', _l('LDAP master server')), - ('ldap-backup-server', _l('LDAP backup server')), - ('smtp-server', _l('SMTP server')), - ('postgresql-server', _l('postgreSQL server')), - ('mysql-server', _l('mySQL server')), - ('sql-client', _l('SQL client')), - ('gateway', _l('Gatewaw')), + ('dhcp-server', _("DHCP server")), + ('switch-conf-server', _("Switches configuration server")), + ('dns-recursif-server', _("Recursive DNS server")), + ('ntp-server', _("NTP server")), + ('radius-server', _("RADIUS server")), + ('log-server', _("Log server")), + ('ldap-master-server', _("LDAP master server")), + ('ldap-backup-server', _("LDAP backup server")), + ('smtp-server', _("SMTP server")), + ('postgresql-server', _("postgreSQL server")), + ('mysql-server', _("mySQL server")), + ('sql-client', _("SQL client")), + ('gateway', _("Gateway")), ) role_type = models.CharField(max_length=255, unique=True) @@ -1597,9 +1603,10 @@ class Role(RevMixin, AclMixin, models.Model): class Meta: permissions = ( - ("view_role", _l("Can view a role.")), + ("view_role", _("Can view a role object")), ) - verbose_name = _l("Server role") + verbose_name = _("server role") + verbose_name_plural = _("server roles") @classmethod def get_instance(cls, roleid, *_args, **_kwargs): @@ -1636,23 +1643,24 @@ class Role(RevMixin, AclMixin, models.Model): class Service(RevMixin, AclMixin, models.Model): """ Definition d'un service (dhcp, dns, etc)""" - PRETTY_NAME = "Services à générer (dhcp, dns, etc)" service_type = models.CharField(max_length=255, blank=True, unique=True) min_time_regen = models.DurationField( default=timedelta(minutes=1), - help_text="Temps minimal avant nouvelle génération du service" + help_text=_("Minimal time before regeneration of the service.") ) regular_time_regen = models.DurationField( default=timedelta(hours=1), - help_text="Temps maximal avant nouvelle génération du service" + help_text=_("Maximal time before regeneration of the service.") ) servers = models.ManyToManyField('Interface', through='Service_link') class Meta: permissions = ( - ("view_service", "Peut voir un objet service"), + ("view_service", _("Can view a service object")), ) + verbose_name = _("service to generate (DHCP, DNS, ...)") + verbose_name_plural = _("services to generate (DHCP, DNS, ...)") def ask_regen(self): """ Marque à True la demande de régénération pour un service x """ @@ -1690,7 +1698,6 @@ def regen(service): class Service_link(RevMixin, AclMixin, models.Model): """ Definition du lien entre serveurs et services""" - PRETTY_NAME = "Relation entre service et serveur" service = models.ForeignKey('Service', on_delete=models.CASCADE) server = models.ForeignKey('Interface', on_delete=models.CASCADE) @@ -1699,8 +1706,10 @@ class Service_link(RevMixin, AclMixin, models.Model): class Meta: permissions = ( - ("view_service_link", "Peut voir un objet service_link"), + ("view_service_link", _("Can view a service server link object")), ) + verbose_name = _("link between service and server") + verbose_name_plural = _("links between service and server") def done_regen(self): """ Appellé lorsqu'un serveur a regénéré son service""" @@ -1740,17 +1749,19 @@ class Service_link(RevMixin, AclMixin, models.Model): class OuverturePortList(RevMixin, AclMixin, models.Model): """Liste des ports ouverts sur une interface.""" - PRETTY_NAME = "Profil d'ouverture de ports" name = models.CharField( - help_text="Nom de la configuration des ports.", + help_text=_("Name of the ports configuration"), max_length=255 ) class Meta: permissions = ( - ("view_ouvertureportlist", "Peut voir un objet ouvertureport"), + ("view_ouvertureportlist", _("Can view a ports opening list" + " object")), ) + verbose_name = _("ports opening list") + verbose_name_plural = _("ports opening lists") def can_delete(self, user_request, *_args, **_kwargs): """Verifie que l'user a les bons droits bureau pour delete @@ -1759,10 +1770,10 @@ class OuverturePortList(RevMixin, AclMixin, models.Model): :param user_request: Utilisateur qui fait la requête :return: soit True, soit False avec la raison de l'échec""" if not user_request.has_perm('machines.delete_ouvertureportlist'): - return False, (u"Vous n'avez pas le droit de supprimer une " - "ouverture de port") + return False, _("You don't have the right to delete a ports" + " opening list.") if self.interface_set.all(): - return False, u"Cette liste de ports est utilisée" + return False, _("This ports opening list is used.") return True, None def __str__(self): @@ -1806,7 +1817,6 @@ class OuverturePort(RevMixin, AclMixin, models.Model): On limite les ports entre 0 et 65535, tels que défini par la RFC """ - PRETTY_NAME = "Plage de port ouverte" TCP = 'T' UDP = 'U' @@ -1834,6 +1844,10 @@ class OuverturePort(RevMixin, AclMixin, models.Model): ), default=OUT, ) + + class Meta: + verbose_name = _("ports opening") + verbose_name = _("ports openings") def __str__(self): if self.begin == self.end: @@ -1999,3 +2013,4 @@ def srv_post_save(**_kwargs): def srv_post_delete(**_kwargs): """Regeneration dns après modification d'un SRV""" regen('dns') + diff --git a/machines/templates/machines/aff_alias.html b/machines/templates/machines/aff_alias.html index 184db6f4..17266a6e 100644 --- a/machines/templates/machines/aff_alias.html +++ b/machines/templates/machines/aff_alias.html @@ -23,25 +23,26 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load acl %} +{% load i18n %} {% load logs_extra %} - + {% for alias in alias_list %} - - - - + + + + {% endfor %}
    Alias{% trans "Aliases" %}
    {{ alias }} - {% can_edit alias %} - {% include 'buttons/edit.html' with href='machines:edit-alias' id=alias.id %} - {% acl_end %} - {% history_button alias %} -
    {{ alias }} + {% can_edit alias %} + {% include 'buttons/edit.html' with href='machines:edit-alias' id=alias.id %} + {% acl_end %} + {% history_button alias %} +
    diff --git a/machines/templates/machines/aff_dname.html b/machines/templates/machines/aff_dname.html index 8797be72..7e043d7c 100644 --- a/machines/templates/machines/aff_dname.html +++ b/machines/templates/machines/aff_dname.html @@ -22,16 +22,17 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load acl %} {% load logs_extra %} +{% load i18n %} - - - - - - - - - {% for dname in dname_list %} +
    Target zoneRecord
    + + + + + + + + {% for dname in dname_list %} @@ -39,10 +40,9 @@ with this program; if not, write to the Free Software Foundation, Inc., {% can_edit dname %} {% include 'buttons/edit.html' with href='machines:edit-dname' id=dname.id %} {% acl_end %} - {% history_button dname %} + {% history_button dname %} - {% endfor %} -
    {% trans "Target zone" %}{% trans "Record" %}
    {{ dname.zone }} {{ dname.dns_entry }}
    - + {% endfor %} +
    diff --git a/machines/templates/machines/aff_extension.html b/machines/templates/machines/aff_extension.html index ba444eca..43bb9e39 100644 --- a/machines/templates/machines/aff_extension.html +++ b/machines/templates/machines/aff_extension.html @@ -25,17 +25,18 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load acl %} {% load logs_extra %} {% load design %} +{% load i18n %}
    - - - - + + + + {% if ipv6_enabled %} - + {% endif %} @@ -44,7 +45,7 @@ with this program; if not, write to the Free Software Foundation, Inc., - + {% if ipv6_enabled %} @@ -59,3 +60,4 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endfor %}
    ExtensionDroit infra pour utiliser ?Enregistrement SOAEnregistrement A origin{% trans "Extension" %}{% trans "'infra' right required" %}{% trans "SOA record" %}{% trans "A record origin" %}Enregistrement AAAA origin{% trans "AAAA record origin" %}
    {{ extension.name }} {{ extension.need_infra|tick }}{{ extension.soa}}{{ extension.soa }} {{ extension.origin }}{{ extension.origin_v6 }}
    + diff --git a/machines/templates/machines/aff_iptype.html b/machines/templates/machines/aff_iptype.html index afd35d1b..b8ed5293 100644 --- a/machines/templates/machines/aff_iptype.html +++ b/machines/templates/machines/aff_iptype.html @@ -26,18 +26,20 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load acl %} {% load logs_extra %} +{% load i18n %} +
    - - - - - - - - + + + + + + + + @@ -61,3 +63,4 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endfor %}
    Type d'ipExtensionNécessite l'autorisation infraPlage ipv4Préfixe v6DNSSEC reverse v4/v6Sur vlanOuverture ports par défault{% trans "IP type" %}{% trans "Extension" %}{% trans "'infra' right required" %}{% trans "IPv4 range" %}{% trans "v6 prefix" %}{% trans "DNSSEC reverse v4/v6" %}{% trans "On VLAN(s)" %}{% trans "Default ports opening" %}
    + diff --git a/machines/templates/machines/aff_ipv6.html b/machines/templates/machines/aff_ipv6.html index d5323f61..a98c0327 100644 --- a/machines/templates/machines/aff_ipv6.html +++ b/machines/templates/machines/aff_ipv6.html @@ -24,29 +24,30 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load acl %} {% load logs_extra %} +{% load i18n %} - - + + - + {% for ipv6 in ipv6_list %} - - - - - + + + + + {% endfor %}
    Ipv6Slaac{% trans "IPv6 addresses" %}{% trans "SLAAC" %}
    {{ ipv6.ipv6 }}{{ ipv6.slaac_ip }} - {% can_edit ipv6 %} - {% include 'buttons/edit.html' with href='machines:edit-ipv6list' id=ipv6.id %} - {% acl_end %} - {% can_delete ipv6 %} - {% include 'buttons/suppr.html' with href='machines:del-ipv6list' id=ipv6.id %} - {% acl_end %} - {% history_button ipv6 %} -
    {{ ipv6.ipv6 }}{{ ipv6.slaac_ip }} + {% can_edit ipv6 %} + {% include 'buttons/edit.html' with href='machines:edit-ipv6list' id=ipv6.id %} + {% acl_end %} + {% can_delete ipv6 %} + {% include 'buttons/suppr.html' with href='machines:del-ipv6list' id=ipv6.id %} + {% acl_end %} + {% history_button ipv6 %} +
    diff --git a/machines/templates/machines/aff_machines.html b/machines/templates/machines/aff_machines.html index ba736f10..e3404036 100644 --- a/machines/templates/machines/aff_machines.html +++ b/machines/templates/machines/aff_machines.html @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load acl %} {% load logs_extra %} +{% load i18n %}
    {% if machines_list.paginator %} @@ -39,23 +40,27 @@ with this program; if not, write to the Free Software Foundation, Inc., - {% include "buttons/sort.html" with prefix='machine' col='name' text='Nom DNS' %} - Type - MAC - IP - Actions + {% trans "DNS name" as tr_dns_name %} + {% include "buttons/sort.html" with prefix='machine' col='name' text=tr_dns_name %} + {% trans "Type" %} + {% trans "MAC address" %} + {% trans "IP address" %} + {% trans "Actions" %} - {% for machine in machines_list %} + {% for machine in machines_list %} - {{ machine.name|default:'Pas de nom' }} - + {% trans "No name" as tr_no_name %} + {% trans "View the profile" as tr_view_the_profile %} + {{ machine.name|default:'tr_no_name' }} + {{ machine.user }} {% can_create Interface machine.id %} - {% include 'buttons/add.html' with href='machines:new-interface' id=machine.id desc='Ajouter une interface' %} + {% trans "Create an interface" as tr_create_an_interface %} + {% include 'buttons/add.html' with href='machines:new-interface' id=machine.id desc=tr_create_an_interface %} {% acl_end %} {% history_button machine %} {% can_delete machine %} @@ -68,8 +73,8 @@ with this program; if not, write to the Free Software Foundation, Inc., {% if interface.domain.related_domain.all %} {{ interface.domain }} - {% else %} {{ interface.domain }} @@ -77,7 +82,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {{ interface.type }} - + {{ interface.mac_address }} @@ -86,8 +91,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
    {% if ipv6_enabled and interface.ipv6 != 'None'%} IPv6 - {% endif %} @@ -97,39 +102,44 @@ with this program; if not, write to the Free Software Foundation, Inc., -
    - {% if ipv6_enabled and interface.ipv6 != 'None'%}
      - {% for ipv6 in interface.ipv6.all %} + {% for ipv6 in interface.ipv6.all %}
    • - {{ipv6}} + {{ ipv6 }}
    • - {% endfor %} + {% endfor %}
    - - {% endif %} - - - {% if interface.domain.related_domain.all %} - - -
    -
      - {% for al in interface.domain.related_domain.all %} -
    • - - {{ al }} - - -
    • - {% endfor %} -
    -
    - - - {% endif %} - {% endfor %} - - - - {% endfor %} + + {% endif %} + {% if interface.domain.related_domain.all %} + + +
    +
      + {% for al in interface.domain.related_domain.all %} +
    • + + {{ al }} + + +
    • + {% endfor %} +
    +
    + + + {% endif %} + {% endfor %} + + + + {% endfor %} + - - {% endblock %} + diff --git a/machines/templates/machines/index.html b/machines/templates/machines/index.html index 3d85dd59..509334a0 100644 --- a/machines/templates/machines/index.html +++ b/machines/templates/machines/index.html @@ -24,11 +24,12 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load bootstrap3 %} +{% load i18n %} -{% block title %}Machines{% endblock %} +{% block title %}{% trans "Machines" %}{% endblock %} {% block content %} -

    Machines

    +

    {% trans "Machines" %}

    {% include "machines/aff_machines.html" with machines_list=machines_list %}

    diff --git a/machines/templates/machines/index_alias.html b/machines/templates/machines/index_alias.html index 07750754..2d33177f 100644 --- a/machines/templates/machines/index_alias.html +++ b/machines/templates/machines/index_alias.html @@ -24,16 +24,17 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load bootstrap3 %} +{% load i18n %} -{% block title %}Machines{% endblock %} +{% block title %}{% trans "Machines" %}{% endblock %} {% block content %} -

    Liste des alias de l'interface

    - Ajouter un alias - Supprimer un ou plusieurs alias - {% include "machines/aff_alias.html" with alias_list=alias_list %} -
    -
    -
    +

    {% trans "List of the aliases of the interface" %}

    + {% trans " Add an alias" %} + {% trans " Delete one or several aliases" %} + {% include "machines/aff_alias.html" with alias_list=alias_list %} +
    +
    +
    {% endblock %} diff --git a/machines/templates/machines/index_extension.html b/machines/templates/machines/index_extension.html index 29c5a0bd..6669d197 100644 --- a/machines/templates/machines/index_extension.html +++ b/machines/templates/machines/index_extension.html @@ -28,57 +28,60 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load acl %} {% load i18n %} -{% block title %}Machines{% endblock %} +{% block title %}{% trans "Machines" %}{% endblock %} {% block content %} -

    Liste des extensions

    +

    {% trans "List of extensions" %}

    {% can_create Extension %} - Ajouter une extension + {% trans " Add an extension" %} {% acl_end %} - Supprimer une ou plusieurs extensions + {% trans " Delete one or several extensions" %} {% include "machines/aff_extension.html" with extension_list=extension_list %} -

    Liste des enregistrements SOA

    +

    {% trans "List of SOA records" %}

    {% can_create SOA %} - Ajouter un enregistrement SOA + {% trans " Add an SOA record" %} {% acl_end %} - Supprimer un enregistrement SOA + {% trans " Delete one or several SOA records" %} {% include "machines/aff_soa.html" with soa_list=soa_list %} -

    Liste des enregistrements MX

    + +

    {% trans "List of MX records" %}

    {% can_create Mx %} - Ajouter un enregistrement MX + {% trans " Add an MX record" %} {% acl_end %} - Supprimer un enregistrement MX + {% trans " Delete one or several MX records" %} {% include "machines/aff_mx.html" with mx_list=mx_list %} -

    Liste des enregistrements NS

    + +

    {% trans "List of NS records" %}

    {% can_create Ns %} - Ajouter un enregistrement NS + {% trans " Add an NS record" %} {% acl_end %} - Supprimer un enregistrement NS + {% trans " Delete one or several NS records" %} {% include "machines/aff_ns.html" with ns_list=ns_list %} -

    Liste des enregistrements TXT

    + +

    {% trans "List of TXT records" %}

    {% can_create Txt %} - Ajouter un enregistrement TXT + {% trans " Add a TXT record" %} {% acl_end %} - Supprimer un enregistrement TXT + {% trans " Delete one or several TXT records" %} {% include "machines/aff_txt.html" with txt_list=txt_list %} -

    DNAME records

    +

    {% trans "List of DNAME records" %}

    {% can_create DName %} - {% trans "Add a DNAME record" %} + {% trans " Add a DNAME record" %} {% acl_end %} - {% trans "Delete DNAME records" %} + {% trans " Delete one or several DNAME records" %} {% include "machines/aff_dname.html" with dname_list=dname_list %} -

    Liste des enregistrements SRV

    +

    {% trans "List of SRV records" %}

    {% can_create Srv %} - Ajouter un enregistrement SRV + {% trans " Add an SRV record" %} {% acl_end %} - Supprimer un enregistrement SRV + {% trans " Delete one or several SRV records" %} {% include "machines/aff_srv.html" with srv_list=srv_list %}

    diff --git a/machines/templates/machines/index_iptype.html b/machines/templates/machines/index_iptype.html index 4dacf96e..5be4561d 100644 --- a/machines/templates/machines/index_iptype.html +++ b/machines/templates/machines/index_iptype.html @@ -26,15 +26,16 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} {% load acl %} +{% load i18n %} -{% block title %}Ip{% endblock %} +{% block title %}{% trans "Machines" %}{% endblock %} {% block content %} -

    Liste des types d'ip

    +

    {% trans "List of IP types" %}

    {% can_create IpType %} - Ajouter un type d'ip + {% trans " Add an IP type" %} {% acl_end %} - Supprimer un ou plusieurs types d'ip + {% trans " Delete one or several IP types" %} {% include "machines/aff_iptype.html" with iptype_list=iptype_list %}

    diff --git a/machines/templates/machines/index_ipv6.html b/machines/templates/machines/index_ipv6.html index 584dc00a..06e287e2 100644 --- a/machines/templates/machines/index_ipv6.html +++ b/machines/templates/machines/index_ipv6.html @@ -25,13 +25,14 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} {% load acl %} +{% load i18n %} -{% block title %}Machines{% endblock %} +{% block title %}{% trans "Machines" %}{% endblock %} {% block content %} -

    Liste des ipv6 de l'interface

    +

    {% trans "List of the IPv6 addresses of the interface" %}

    {% can_create Ipv6List interface_id %} - Ajouter une ipv6 + {% trans " Add an IPv6 address" %} {% acl_end %} {% include "machines/aff_ipv6.html" with ipv6_list=ipv6_list %}
    diff --git a/machines/templates/machines/index_machinetype.html b/machines/templates/machines/index_machinetype.html index a0bfd7e5..de1e2f58 100644 --- a/machines/templates/machines/index_machinetype.html +++ b/machines/templates/machines/index_machinetype.html @@ -26,15 +26,16 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} {% load acl %} +{% load i18n %} -{% block title %}Machines{% endblock %} +{% block title %}{% trans "Machines" %}{% endblock %} {% block content %} -

    Liste des types de machines

    +

    {% trans "List of machine types" %}

    {% can_create MachineType %} - Ajouter un type de machine + {% trans " Add a machine type" %} {% acl_end %} - Supprimer un ou plusieurs types de machines + {% trans " Delete one or several machine types" %} {% include "machines/aff_machinetype.html" with machinetype_list=machinetype_list %}

    diff --git a/machines/templates/machines/index_nas.html b/machines/templates/machines/index_nas.html index 3f1cb90f..88f68213 100644 --- a/machines/templates/machines/index_nas.html +++ b/machines/templates/machines/index_nas.html @@ -26,17 +26,17 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} {% load acl %} +{% load i18n %} -{% block title %}Machines{% endblock %} +{% block title %}{% trans "Machines" %}{% endblock %} {% block content %} -

    Liste des nas

    -
    La correpondance nas-machinetype relie le type de nas à un type de machine. - Elle est utile pour l'autoenregistrement des macs par radius, et permet de choisir le type de machine à affecter aux machines en fonction du type de nas
    +

    {% trans "List of NAS devices" %}

    +
    {% trans "The NAS device type and machine type are linked. It is useful for MAC address auto capture by RADIUS, and allows to choose the machine type to assign to the machines according to the NAS device type." %}
    {% can_create Nas %} - Ajouter un type de nas + {% trans " Add a NAS device type" %} {% acl_end %} - Supprimer un ou plusieurs types nas + {% trans " Delete one or several NAS device types" %} {% include "machines/aff_nas.html" with nas_list=nas_list %}

    diff --git a/machines/templates/machines/index_portlist.html b/machines/templates/machines/index_portlist.html index e505ad43..0d3d0741 100644 --- a/machines/templates/machines/index_portlist.html +++ b/machines/templates/machines/index_portlist.html @@ -3,23 +3,24 @@ {% load bootstrap3 %} {% load acl %} +{% load i18n %} -{% block title %}Configuration de ports{% endblock %} +{% block title %}{% trans "Machines" %}{% endblock %} {% block content %} -

    Liste des configurations de ports

    +

    {% trans "List of ports configurations" %}

    {% can_create OuverturePortList %} - Ajouter une configuration + {% trans " Add a configuration" %} {% acl_end %} - - - - - - + + + + + + @@ -48,13 +49,13 @@ {% endif %} + {% can_delete pl %} + {% include 'buttons/suppr.html' with href='machines:del-portlist' id=pl.id %} + {% acl_end %} + {%endfor%}
    NomTCP (entrée)TCP (sortie)UDP (entrée)UDP (sortie)Machines{% trans "Name" %}{% trans "TCP (input)" %}{% trans "TCP (output)" %}{% trans "UDP (input)" %}{% trans "UDP (output)" %}{% trans "Machines" %}
    - {% can_delete pl %} - {% include 'buttons/suppr.html' with href='machines:del-portlist' id=pl.id %} - {% acl_end %} {% can_edit pl %} {% include 'buttons/edit.html' with href='machines:edit-portlist' id=pl.id %} {% acl_end %} -
    @@ -63,3 +64,4 @@
    {% endblock %} + diff --git a/machines/templates/machines/index_role.html b/machines/templates/machines/index_role.html index 86c36a09..ddc2ea8b 100644 --- a/machines/templates/machines/index_role.html +++ b/machines/templates/machines/index_role.html @@ -27,14 +27,14 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load acl %} {% load i18n %} -{% block title %}Machines{% endblock %} +{% block title %}{% trans "Machines" %}{% endblock %} {% block content %} -

    {% trans "Roles list" %}

    +

    {% trans "List of roles" %}

    {% can_create Role %} - {% trans "Add role"%} + {% trans " Add a role"%} {% acl_end %} - {% trans "Delete one or several roles" %} + {% trans " Delete one or several roles" %} {% include "machines/aff_role.html" with role_list=role_list %}

    diff --git a/machines/templates/machines/index_service.html b/machines/templates/machines/index_service.html index 3bc88189..9dec7032 100644 --- a/machines/templates/machines/index_service.html +++ b/machines/templates/machines/index_service.html @@ -25,17 +25,18 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} {% load acl %} +{% load i18n %} -{% block title %}Machines{% endblock %} +{% block title %}{% trans "Machines" %}{% endblock %} {% block content %} -

    Liste des services

    +

    {% trans "List of services" %}

    {% can_create machines.Service %} - Ajouter un service + {% trans " Add a service" %} {% acl_end %} - Supprimer un ou plusieurs service + {% trans " Delete one or several services" %} {% include "machines/aff_service.html" with service_list=service_list %} -

    Etat des serveurs

    +

    {% trans "States of servers" %}

    {% include "machines/aff_servers.html" with servers_list=servers_list %}

    diff --git a/machines/templates/machines/index_sshfp.html b/machines/templates/machines/index_sshfp.html index 2c8d1581..ce16d621 100644 --- a/machines/templates/machines/index_sshfp.html +++ b/machines/templates/machines/index_sshfp.html @@ -23,16 +23,17 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} {% load acl %} +{% load i18n %} -{% block title %}Machines{% endblock %} +{% block title %}{% trans "Machines" %}{% endblock %} {% block content %} -

    SSH fingerprints

    -{% can_create SshFp machine_id %} - - Add an SSH fingerprint - -{% acl_end %} -{% include "machines/aff_sshfp.html" with sshfp_list=sshfp_list %} +

    {% trans "SSH fingerprints" %}

    + {% can_create SshFp machine_id %} + + {% trans " Add an SSH fingerprint" %} + + {% acl_end %} + {% include "machines/aff_sshfp.html" with sshfp_list=sshfp_list %} {% endblock %} diff --git a/machines/templates/machines/index_vlan.html b/machines/templates/machines/index_vlan.html index beb6c80e..7af31fd5 100644 --- a/machines/templates/machines/index_vlan.html +++ b/machines/templates/machines/index_vlan.html @@ -26,15 +26,16 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} {% load acl %} +{% load i18n %} -{% block title %}Machines{% endblock %} +{% block title %}{% trans "Machines" %}{% endblock %} {% block content %} -

    Liste des vlans

    +

    {% trans "List of VLANs" %}

    {% can_create Vlan %} - Ajouter un vlan + {% trans " Add a VLAN" %} {% acl_end %} - Supprimer un ou plusieurs vlan + {% trans " Delete one or several VLANs" %} {% include "machines/aff_vlan.html" with vlan_list=vlan_list %}

    diff --git a/machines/templates/machines/machine.html b/machines/templates/machines/machine.html index d6c0f522..432d11f8 100644 --- a/machines/templates/machines/machine.html +++ b/machines/templates/machines/machine.html @@ -26,8 +26,9 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} {% load massive_bootstrap_form %} +{% load i18n %} -{% block title %}Création et modification de machines{% endblock %} +{% block title %}{% trans "Machines" %}{% endblock %} {% block content %} {% if machineform %} @@ -88,11 +89,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
    {% csrf_token %} {% if machineform %} -

    Machine

    +

    {% trans "Machine" %}

    {% massive_bootstrap_form machineform 'user' %} {% endif %} {% if interfaceform %} -

    Interface

    +

    {% trans "Interface" %}

    {% if i_mbf_param %} {% massive_bootstrap_form interfaceform 'ipv4,machine' mbf_param=i_mbf_param %} {% else %} @@ -100,55 +101,55 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endif %} {% endif %} {% if domainform %} -

    Domaine

    +

    {% trans "Domain" %}

    {% bootstrap_form domainform %} {% endif %} {% if iptypeform %} -

    Type d'IP

    +

    {% trans "IP type" %}

    {% bootstrap_form iptypeform %} {% endif %} {% if machinetypeform %} -

    Type de machine

    +

    {% trans "Machine type" %}

    {% bootstrap_form machinetypeform %} {% endif %} {% if extensionform %} -

    Extension

    +

    {% trans "Extension" %}

    {% massive_bootstrap_form extensionform 'origin' %} {% endif %} {% if soaform %} -

    Enregistrement SOA

    +

    {% trans "SOA record" %}

    {% bootstrap_form soaform %} {% endif %} {% if mxform %} -

    Enregistrement MX

    +

    {% trans "MX record" %}

    {% massive_bootstrap_form mxform 'name' %} {% endif %} {% if nsform %} -

    Enregistrement NS

    +

    {% trans "NS record" %}

    {% massive_bootstrap_form nsform 'ns' %} {% endif %} {% if txtform %} -

    Enregistrement TXT

    +

    {% trans "TXT record" %}

    {% bootstrap_form txtform %} {% endif %} {% if dnameform %} -

    DNAME record

    +

    {% trans "DNAME record" %}

    {% bootstrap_form dnameform %} {% endif %} {% if srvform %} -

    Enregistrement SRV

    +

    {% trans "SRV record" %}

    {% massive_bootstrap_form srvform 'target' %} {% endif %} {% if sshfpform %} -

    SSHFP record

    +

    {% trans "SSHFP record" %}

    {% bootstrap_form sshfpform %} {% endif %} {% if aliasform %} -

    Alias

    +

    {% trans "Alias" %}

    {% bootstrap_form aliasform %} {% endif %} {% if serviceform %} -

    Service

    +

    {% trans "Service" %}

    {% massive_bootstrap_form serviceform 'servers' %} {% endif %} {% if roleform %} @@ -156,15 +157,15 @@ with this program; if not, write to the Free Software Foundation, Inc., {% massive_bootstrap_form roleform 'servers' %} {% endif %} {% if vlanform %} -

    Vlan

    +

    {% trans "VLAN" %}

    {% bootstrap_form vlanform %} {% endif %} {% if nasform %} -

    NAS

    +

    {% trans "NAS device" %}

    {% bootstrap_form nasform %} {% endif %} {% if ipv6form %} -

    Ipv6

    +

    {% trans "IPv6 address" %}

    {% bootstrap_form ipv6form %} {% endif %} {% bootstrap_button action_name button_type="submit" icon="star" %} @@ -173,3 +174,4 @@ with this program; if not, write to the Free Software Foundation, Inc.,

    {% endblock %} + diff --git a/machines/templates/machines/sidebar.html b/machines/templates/machines/sidebar.html index 75badb6b..68e14ae0 100644 --- a/machines/templates/machines/sidebar.html +++ b/machines/templates/machines/sidebar.html @@ -30,43 +30,43 @@ with this program; if not, write to the Free Software Foundation, Inc., {% can_view_all Machine %} - Machines + {% trans "Machines" %} {% acl_end %} {% can_view_all MachineType %} - Types de machines + {% trans "Machine types" %} {% acl_end %} {% can_view_all Extension %} - Extensions et zones + {% trans "Extensions and zones" %} {% acl_end %} {% can_view_all IpType %} - Plages d'IP + {% trans "IP ranges" %} {% acl_end %} {% can_view_all Vlan %} - Vlans + {% trans "VLANs" %} {% acl_end %} {% can_view_all Nas %} - Gestion des nas + {% trans "NAS devices" %} {% acl_end %} {% can_view_all machines.Service %} - Services (dhcp, dns...) + {% trans "Services (DHCP, DNS, ...)" %} {% acl_end %} {% can_view_all Role %} @@ -78,7 +78,8 @@ with this program; if not, write to the Free Software Foundation, Inc., {% can_view_all OuverturePortList %} - Ouverture de ports + {% trans "Ports openings" %} {% acl_end %} {% endblock %} + diff --git a/machines/views.py b/machines/views.py index f7d138be..3d71acab 100644 --- a/machines/views.py +++ b/machines/views.py @@ -153,7 +153,7 @@ def generate_ipv4_choices(form_obj): """ f_ipv4 = form_obj.fields['ipv4'] used_mtype_id = [] - choices = '{"":[{key:"",value:"Choisissez d\'abord un type de machine"},' + choices = '{"":[{key:"",value:'+_("Select a machine type first.},") mtype_id = -1 for ip in (f_ipv4.queryset @@ -255,7 +255,7 @@ def new_machine(request, user, **_kwargs): new_interface_obj.save() new_domain.interface_parent = new_interface_obj new_domain.save() - messages.success(request, "La machine a été créée") + messages.success(request, _("The machine was created.")) return redirect(reverse( 'users:profil', kwargs={'userid': str(user.id)} @@ -267,7 +267,7 @@ def new_machine(request, user, **_kwargs): 'interfaceform': interface, 'domainform': domain, 'i_mbf_param': i_mbf_param, - 'action_name': 'Créer une machine' + 'action_name': _("Create a machine") }, 'machines/machine.html', request @@ -307,7 +307,7 @@ def edit_interface(request, interface_instance, **_kwargs): new_interface_obj.save() if domain_form.changed_data: new_domain_obj.save() - messages.success(request, "La machine a été modifiée") + messages.success(request, _("The machine was edited.")) return redirect(reverse( 'users:profil', kwargs={'userid': str(interface_instance.machine.user.id)} @@ -319,7 +319,7 @@ def edit_interface(request, interface_instance, **_kwargs): 'interfaceform': interface_form, 'domainform': domain_form, 'i_mbf_param': i_mbf_param, - 'action_name': 'Editer une interface' + 'action_name': _("Edit") }, 'machines/machine.html', request @@ -332,7 +332,7 @@ def del_machine(request, machine, **_kwargs): """ Supprime une machine, interfaces en mode cascade""" if request.method == "POST": machine.delete() - messages.success(request, "La machine a été détruite") + messages.success(request, _("The machine was deleted.")) return redirect(reverse( 'users:profil', kwargs={'userid': str(machine.user.id)} @@ -361,7 +361,7 @@ def new_interface(request, machine, **_kwargs): new_interface_obj.save() new_domain_obj.interface_parent = new_interface_obj new_domain_obj.save() - messages.success(request, "L'interface a été ajoutée") + messages.success(request, _("The interface was created.")) return redirect(reverse( 'users:profil', kwargs={'userid': str(machine.user.id)} @@ -372,7 +372,7 @@ def new_interface(request, machine, **_kwargs): 'interfaceform': interface_form, 'domainform': domain_form, 'i_mbf_param': i_mbf_param, - 'action_name': 'Créer une interface' + 'action_name': _("Create an interface") }, 'machines/machine.html', request @@ -388,7 +388,7 @@ def del_interface(request, interface, **_kwargs): interface.delete() if not machine.interface_set.all(): machine.delete() - messages.success(request, "L'interface a été détruite") + messages.success(request, _("The interface was deleted.")) return redirect(reverse( 'users:profil', kwargs={'userid': str(request.user.id)} @@ -413,13 +413,13 @@ def new_ipv6list(request, interface, **_kwargs): ) if ipv6.is_valid(): ipv6.save() - messages.success(request, "Ipv6 ajoutée") + messages.success(request, _("The IPv6 addresses list was created.")) return redirect(reverse( 'machines:index-ipv6', kwargs={'interfaceid': str(interface.id)} )) return form( - {'ipv6form': ipv6, 'action_name': 'Créer'}, + {'ipv6form': ipv6, 'action_name': _("Create an IPv6 addresses list")}, 'machines/machine.html', request ) @@ -437,13 +437,13 @@ def edit_ipv6list(request, ipv6list_instance, **_kwargs): if ipv6.is_valid(): if ipv6.changed_data: ipv6.save() - messages.success(request, "Ipv6 modifiée") + messages.success(request, _("The IPv6 addresses list was edited.")) return redirect(reverse( 'machines:index-ipv6', kwargs={'interfaceid': str(ipv6list_instance.interface.id)} )) return form( - {'ipv6form': ipv6, 'action_name': 'Editer'}, + {'ipv6form': ipv6, 'action_name': _("Edit")}, 'machines/machine.html', request ) @@ -456,7 +456,7 @@ def del_ipv6list(request, ipv6list, **_kwargs): if request.method == "POST": interfaceid = ipv6list.interface.id ipv6list.delete() - messages.success(request, "L'ipv6 a été détruite") + messages.success(request, _("The IPv6 addresses list was deleted.")) return redirect(reverse( 'machines:index-ipv6', kwargs={'interfaceid': str(interfaceid)} @@ -480,13 +480,13 @@ def new_sshfp(request, machine, **_kwargs): ) if sshfp.is_valid(): sshfp.save() - messages.success(request, "The SSHFP record was added") + messages.success(request, _("The SSHFP record was created.")) return redirect(reverse( 'machines:index-sshfp', kwargs={'machineid': str(machine.id)} )) return form( - {'sshfpform': sshfp, 'action_name': 'Create'}, + {'sshfpform': sshfp, 'action_name': _("Create a SSHFP record")}, 'machines/machine.html', request ) @@ -503,13 +503,13 @@ def edit_sshfp(request, sshfp_instance, **_kwargs): if sshfp.is_valid(): if sshfp.changed_data: sshfp.save() - messages.success(request, "The SSHFP record was edited") + messages.success(request, _("The SSHFP record was edited.")) return redirect(reverse( 'machines:index-sshfp', kwargs={'machineid': str(sshfp_instance.machine.id)} )) return form( - {'sshfpform': sshfp, 'action_name': 'Edit'}, + {'sshfpform': sshfp, 'action_name': _("Edit")}, 'machines/machine.html', request ) @@ -522,7 +522,7 @@ def del_sshfp(request, sshfp, **_kwargs): if request.method == "POST": machineid = sshfp.machine.id sshfp.delete() - messages.success(request, "The SSHFP record was deleted") + messages.success(request, _("The SSHFP record was deleted.")) return redirect(reverse( 'machines:index-sshfp', kwargs={'machineid': str(machineid)} @@ -543,10 +543,10 @@ def add_iptype(request): iptype = IpTypeForm(request.POST or None) if iptype.is_valid(): iptype.save() - messages.success(request, "Ce type d'ip a été ajouté") + messages.success(request, _("The IP type was created.")) return redirect(reverse('machines:index-iptype')) return form( - {'iptypeform': iptype, 'action_name': 'Créer'}, + {'iptypeform': iptype, 'action_name': _("Create an IP type")}, 'machines/machine.html', request ) @@ -562,10 +562,10 @@ def edit_iptype(request, iptype_instance, **_kwargs): if iptype.is_valid(): if iptype.changed_data: iptype.save() - messages.success(request, "Type d'ip modifié") + messages.success(request, _("The IP type was edited.")) return redirect(reverse('machines:index-iptype')) return form( - {'iptypeform': iptype, 'action_name': 'Editer'}, + {'iptypeform': iptype, 'action_name': _("Edit")}, 'machines/machine.html', request ) @@ -581,16 +581,16 @@ def del_iptype(request, instances): for iptype_del in iptype_dels: try: iptype_del.delete() - messages.success(request, "Le type d'ip a été supprimé") + messages.success(request, _("The IP type was deleted.")) except ProtectedError: messages.error( request, - ("Le type d'ip %s est affectée à au moins une machine, " - "vous ne pouvez pas le supprimer" % iptype_del) + (_("The IP type %s is assigned to at least one machine," + " you can't delete it.") % iptype_del) ) return redirect(reverse('machines:index-iptype')) return form( - {'iptypeform': iptype, 'action_name': 'Supprimer'}, + {'iptypeform': iptype, 'action_name': _("Delete")}, 'machines/machine.html', request ) @@ -603,10 +603,11 @@ def add_machinetype(request): machinetype = MachineTypeForm(request.POST or None) if machinetype.is_valid(): machinetype.save() - messages.success(request, "Ce type de machine a été ajouté") + messages.success(request, _("The machine type was created.")) return redirect(reverse('machines:index-machinetype')) return form( - {'machinetypeform': machinetype, 'action_name': 'Créer'}, + {'machinetypeform': machinetype, 'action_name': _("Create a machine" + " type")}, 'machines/machine.html', request ) @@ -623,10 +624,10 @@ def edit_machinetype(request, machinetype_instance, **_kwargs): if machinetype.is_valid(): if machinetype.changed_data: machinetype.save() - messages.success(request, "Type de machine modifié") + messages.success(request, _("The machine type was edited.")) return redirect(reverse('machines:index-machinetype')) return form( - {'machinetypeform': machinetype, 'action_name': 'Editer'}, + {'machinetypeform': machinetype, 'action_name': _("Edit")}, 'machines/machine.html', request ) @@ -642,17 +643,16 @@ def del_machinetype(request, instances): for machinetype_del in machinetype_dels: try: machinetype_del.delete() - messages.success(request, "Le type de machine a été supprimé") + messages.success(request, _("The machine type was deleted.")) except ProtectedError: messages.error( request, - ("Le type de machine %s est affectée à au moins une " - "machine, vous ne pouvez pas le supprimer" - % machinetype_del) + (_("The machine type %s is assigned to at least one" + " machine, you can't delete it.") % machinetype_del) ) return redirect(reverse('machines:index-machinetype')) return form( - {'machinetypeform': machinetype, 'action_name': 'Supprimer'}, + {'machinetypeform': machinetype, 'action_name': _("Delete")}, 'machines/machine.html', request ) @@ -665,10 +665,10 @@ def add_extension(request): extension = ExtensionForm(request.POST or None) if extension.is_valid(): extension.save() - messages.success(request, "Cette extension a été ajoutée") + messages.success(request, _("The extension was created.")) return redirect(reverse('machines:index-extension')) return form( - {'extensionform': extension, 'action_name': 'Créer'}, + {'extensionform': extension, 'action_name': _("Create an extension")}, 'machines/machine.html', request ) @@ -685,10 +685,10 @@ def edit_extension(request, extension_instance, **_kwargs): if extension.is_valid(): if extension.changed_data: extension.save() - messages.success(request, "Extension modifiée") + messages.success(request, _("The extension was edited.")) return redirect(reverse('machines:index-extension')) return form( - {'extensionform': extension, 'action_name': 'Editer'}, + {'extensionform': extension, 'action_name': _("Edit")}, 'machines/machine.html', request ) @@ -704,17 +704,16 @@ def del_extension(request, instances): for extension_del in extension_dels: try: extension_del.delete() - messages.success(request, "L'extension a été supprimée") + messages.success(request, _("The extension was deleted.")) except ProtectedError: messages.error( request, - ("L'extension %s est affectée à au moins un type de " - "machine, vous ne pouvez pas la supprimer" - % extension_del) + (_("The extension %s is assigned to at least one machine" + " type, you can't delete it." % extension_del)) ) return redirect(reverse('machines:index-extension')) return form( - {'extensionform': extension, 'action_name': 'Supprimer'}, + {'extensionform': extension, 'action_name': _("Delete")}, 'machines/machine.html', request ) @@ -727,10 +726,10 @@ def add_soa(request): soa = SOAForm(request.POST or None) if soa.is_valid(): soa.save() - messages.success(request, "Cet enregistrement SOA a été ajouté") + messages.success(request, _("The SOA record was created.")) return redirect(reverse('machines:index-extension')) return form( - {'soaform': soa, 'action_name': 'Créer'}, + {'soaform': soa, 'action_name': _("Create an SOA record")}, 'machines/machine.html', request ) @@ -744,10 +743,10 @@ def edit_soa(request, soa_instance, **_kwargs): if soa.is_valid(): if soa.changed_data: soa.save() - messages.success(request, "SOA modifié") + messages.success(request, _("The SOA record was edited.")) return redirect(reverse('machines:index-extension')) return form( - {'soaform': soa, 'action_name': 'Editer'}, + {'soaform': soa, 'action_name': _("Edit")}, 'machines/machine.html', request ) @@ -763,16 +762,15 @@ def del_soa(request, instances): for soa_del in soa_dels: try: soa_del.delete() - messages.success(request, "Le SOA a été supprimée") + messages.success(request, _("The SOA record was deleted.")) except ProtectedError: messages.error( request, - ("Erreur le SOA suivant %s ne peut être supprimé" - % soa_del) + (_("Error: the SOA record %s can't be deleted.") % soa_del) ) return redirect(reverse('machines:index-extension')) return form( - {'soaform': soa, 'action_name': 'Supprimer'}, + {'soaform': soa, 'action_name': _("Delete")}, 'machines/machine.html', request ) @@ -785,10 +783,10 @@ def add_mx(request): mx = MxForm(request.POST or None) if mx.is_valid(): mx.save() - messages.success(request, "Cet enregistrement mx a été ajouté") + messages.success(request, _("The MX record was created.")) return redirect(reverse('machines:index-extension')) return form( - {'mxform': mx, 'action_name': 'Créer'}, + {'mxform': mx, 'action_name': _("Create an MX record")}, 'machines/machine.html', request ) @@ -802,10 +800,10 @@ def edit_mx(request, mx_instance, **_kwargs): if mx.is_valid(): if mx.changed_data: mx.save() - messages.success(request, "Mx modifié") + messages.success(request, _("The MX record was edited.")) return redirect(reverse('machines:index-extension')) return form( - {'mxform': mx, 'action_name': 'Editer'}, + {'mxform': mx, 'action_name': _("Edit")}, 'machines/machine.html', request ) @@ -821,16 +819,15 @@ def del_mx(request, instances): for mx_del in mx_dels: try: mx_del.delete() - messages.success(request, "L'mx a été supprimée") + messages.success(request, _("The MX record was deleted.")) except ProtectedError: messages.error( request, - ("Erreur le Mx suivant %s ne peut être supprimé" - % mx_del) + (_("Error: the MX record %s can't be deleted.") % mx_del) ) return redirect(reverse('machines:index-extension')) return form( - {'mxform': mx, 'action_name': 'Supprimer'}, + {'mxform': mx, 'action_name': _("Delete")}, 'machines/machine.html', request ) @@ -843,10 +840,10 @@ def add_ns(request): ns = NsForm(request.POST or None) if ns.is_valid(): ns.save() - messages.success(request, "Cet enregistrement ns a été ajouté") + messages.success(request, _("The NS record was created.")) return redirect(reverse('machines:index-extension')) return form( - {'nsform': ns, 'action_name': 'Créer'}, + {'nsform': ns, 'action_name': _("Create an NS record")}, 'machines/machine.html', request ) @@ -860,10 +857,10 @@ def edit_ns(request, ns_instance, **_kwargs): if ns.is_valid(): if ns.changed_data: ns.save() - messages.success(request, "Ns modifié") + messages.success(request, _("The NS record was edited.")) return redirect(reverse('machines:index-extension')) return form( - {'nsform': ns, 'action_name': 'Editer'}, + {'nsform': ns, 'action_name': _("Edit")}, 'machines/machine.html', request ) @@ -879,16 +876,15 @@ def del_ns(request, instances): for ns_del in ns_dels: try: ns_del.delete() - messages.success(request, "Le ns a été supprimée") + messages.success(request, _("The NS record was deleted.")) except ProtectedError: messages.error( request, - ("Erreur le Ns suivant %s ne peut être supprimé" - % ns_del) + (_("Error: the NS record %s can't be deleted.") % ns_del) ) return redirect(reverse('machines:index-extension')) return form( - {'nsform': ns, 'action_name': 'Supprimer'}, + {'nsform': ns, 'action_name': _("Delete")}, 'machines/machine.html', request ) @@ -900,10 +896,10 @@ def add_dname(request): dname = DNameForm(request.POST or None) if dname.is_valid(): dname.save() - messages.success(request, "This DNAME record has been added") + messages.success(request, _("The DNAME record was created.")) return redirect(reverse('machines:index-extension')) return form( - {'dnameform': dname, 'action_name': "Create"}, + {'dnameform': dname, 'action_name': _("Create a DNAME record")}, 'machines/machine.html', request ) @@ -917,10 +913,10 @@ def edit_dname(request, dname_instance, **_kwargs): if dname.is_valid(): if dname.changed_data: dname.save() - messages.success(request, "DName successfully edited") + messages.success(request, _("The DNAME record was edited.")) return redirect(reverse('machines:index-extension')) return form( - {'dnameform': dname, 'action_name': "Edit"}, + {'dnameform': dname, 'action_name': _("Edit")}, 'machines/machine.html', request ) @@ -936,16 +932,16 @@ def del_dname(request, instances): for dname_del in dname_dels: try: dname_del.delete() - messages.success(request, - "The DNAME %s has been deleted" % dname_del) + messages.success(request, _("The DNAME record was deleted.")) except ProtectedError: messages.error( - request, - "The DNAME %s can not be deleted" % dname_del + request, + _("Error: the DNAME record %s can't be deleted.") + % dname_del ) return redirect(reverse('machines:index-extension')) return form( - {'dnameform': dname, 'action_name': 'Delete'}, + {'dnameform': dname, 'action_name': _("Delete")}, 'machines/machine.html', request ) @@ -958,10 +954,10 @@ def add_txt(request): txt = TxtForm(request.POST or None) if txt.is_valid(): txt.save() - messages.success(request, "Cet enregistrement text a été ajouté") + messages.success(request, _("The TXT record was created.")) return redirect(reverse('machines:index-extension')) return form( - {'txtform': txt, 'action_name': 'Créer'}, + {'txtform': txt, 'action_name': _("Create a TXT record")}, 'machines/machine.html', request ) @@ -975,10 +971,10 @@ def edit_txt(request, txt_instance, **_kwargs): if txt.is_valid(): if txt.changed_data: txt.save() - messages.success(request, "Txt modifié") + messages.success(request, _("The TXT record was edited.")) return redirect(reverse('machines:index-extension')) return form( - {'txtform': txt, 'action_name': 'Editer'}, + {'txtform': txt, 'action_name': _("Edit")}, 'machines/machine.html', request ) @@ -994,16 +990,15 @@ def del_txt(request, instances): for txt_del in txt_dels: try: txt_del.delete() - messages.success(request, "Le txt a été supprimé") + messages.success(request, _("The TXT record was deleted.")) except ProtectedError: messages.error( request, - ("Erreur le Txt suivant %s ne peut être supprimé" - % txt_del) + (_("Error: the TXT record %s can't be deleted.") % txt_del) ) return redirect(reverse('machines:index-extension')) return form( - {'txtform': txt, 'action_name': 'Supprimer'}, + {'txtform': txt, 'action_name': _("Delete")}, 'machines/machine.html', request ) @@ -1016,10 +1011,10 @@ def add_srv(request): srv = SrvForm(request.POST or None) if srv.is_valid(): srv.save() - messages.success(request, "Cet enregistrement srv a été ajouté") + messages.success(request, _("The SRV record was created.")) return redirect(reverse('machines:index-extension')) return form( - {'srvform': srv, 'action_name': 'Créer'}, + {'srvform': srv, 'action_name': _("Create an SRV record")}, 'machines/machine.html', request ) @@ -1033,10 +1028,10 @@ def edit_srv(request, srv_instance, **_kwargs): if srv.is_valid(): if srv.changed_data: srv.save() - messages.success(request, "Srv modifié") - return redirect(reverse('machines:index-extension')) + messages.success(request, _("The SRV record was edited.")) + return redirect(reverse('machines:1index-extension')) return form( - {'srvform': srv, 'action_name': 'Editer'}, + {'srvform': srv, 'action_name': _("Edit")}, 'machines/machine.html', request ) @@ -1052,16 +1047,15 @@ def del_srv(request, instances): for srv_del in srv_dels: try: srv_del.delete() - messages.success(request, "L'srv a été supprimée") + messages.success(request, _("The SRV record was deleted.")) except ProtectedError: messages.error( request, - ("Erreur le Srv suivant %s ne peut être supprimé" - % srv_del) + (_("Error: the SRV record %s can't be deleted.") % srv_del) ) return redirect(reverse('machines:index-extension')) return form( - {'srvform': srv, 'action_name': 'Supprimer'}, + {'srvform': srv, 'action_name': _("Delete")}, 'machines/machine.html', request ) @@ -1077,13 +1071,13 @@ def add_alias(request, interface, interfaceid): alias = alias.save(commit=False) alias.cname = interface.domain alias.save() - messages.success(request, "Cet alias a été ajouté") + messages.success(request, _("The alias was created.")) return redirect(reverse( 'machines:index-alias', kwargs={'interfaceid': str(interfaceid)} )) return form( - {'aliasform': alias, 'action_name': 'Créer'}, + {'aliasform': alias, 'action_name': _("Create an alias")}, 'machines/machine.html', request ) @@ -1101,7 +1095,7 @@ def edit_alias(request, domain_instance, **_kwargs): if alias.is_valid(): if alias.changed_data: domain_instance = alias.save() - messages.success(request, "Alias modifié") + messages.success(request, _("The alias was edited.")) return redirect(reverse( 'machines:index-alias', kwargs={ @@ -1109,7 +1103,7 @@ def edit_alias(request, domain_instance, **_kwargs): } )) return form( - {'aliasform': alias, 'action_name': 'Editer'}, + {'aliasform': alias, 'action_name': _("Edit")}, 'machines/machine.html', request ) @@ -1127,20 +1121,19 @@ def del_alias(request, interface, interfaceid): alias_del.delete() messages.success( request, - "L'alias %s a été supprimé" % alias_del + _("The alias %s was deleted.") % alias_del ) except ProtectedError: messages.error( request, - ("Erreur l'alias suivant %s ne peut être supprimé" - % alias_del) + (_("Error: the alias %s can't be deleted.") % alias_del) ) return redirect(reverse( 'machines:index-alias', kwargs={'interfaceid': str(interfaceid)} )) return form( - {'aliasform': alias, 'action_name': 'Supprimer'}, + {'aliasform': alias, 'action_name': _("Delete")}, 'machines/machine.html', request ) @@ -1153,10 +1146,10 @@ def add_role(request): role = RoleForm(request.POST or None) if role.is_valid(): role.save() - messages.success(request, "Cet enregistrement role a été ajouté") + messages.success(request, _("The role was created.")) return redirect(reverse('machines:index-role')) return form( - {'roleform': role, 'action_name': 'Créer'}, + {'roleform': role, 'action_name': _("Create a role")}, 'machines/machine.html', request ) @@ -1170,10 +1163,10 @@ def edit_role(request, role_instance, **_kwargs): if role.is_valid(): if role.changed_data: role.save() - messages.success(request, _("Role updated")) + messages.success(request, _("The role was edited.")) return redirect(reverse('machines:index-role')) return form( - {'roleform': role, 'action_name': _('Edit')}, + {'roleform': role, 'action_name': _("Edit")}, 'machines/machine.html', request ) @@ -1189,17 +1182,15 @@ def del_role(request, instances): for role_del in role_dels: try: role_del.delete() - messages.success(request, _("The role has been deleted.")) + messages.success(request, _("The role was deleted.")) except ProtectedError: messages.error( request, - (_("Error: The following role cannot be deleted: %(role)") - % {'role': role_del} - ) + (_("Error: the role %s can't be deleted.") % role_del) ) return redirect(reverse('machines:index-role')) return form( - {'roleform': role, 'action_name': _('Delete')}, + {'roleform': role, 'action_name': _("Delete")}, 'machines/machine.html', request ) @@ -1212,10 +1203,10 @@ def add_service(request): service = ServiceForm(request.POST or None) if service.is_valid(): service.save() - messages.success(request, "Cet enregistrement service a été ajouté") + messages.success(request, _("The service was created.")) return redirect(reverse('machines:index-service')) return form( - {'serviceform': service, 'action_name': 'Créer'}, + {'serviceform': service, 'action_name': _("Create a service")}, 'machines/machine.html', request ) @@ -1229,10 +1220,10 @@ def edit_service(request, service_instance, **_kwargs): if service.is_valid(): if service.changed_data: service.save() - messages.success(request, "Service modifié") + messages.success(request, _("The service was edited.")) return redirect(reverse('machines:index-service')) return form( - {'serviceform': service, 'action_name': 'Editer'}, + {'serviceform': service, 'action_name': _("Edit")}, 'machines/machine.html', request ) @@ -1248,16 +1239,15 @@ def del_service(request, instances): for service_del in service_dels: try: service_del.delete() - messages.success(request, "Le service a été supprimée") + messages.success(request, _("The service was deleted.")) except ProtectedError: messages.error( request, - ("Erreur le service suivant %s ne peut être supprimé" - % service_del) + (_("Error: the service %s can't be deleted.") % service_del) ) return redirect(reverse('machines:index-service')) return form( - {'serviceform': service, 'action_name': 'Supprimer'}, + {'serviceform': service, 'action_name': _("Delete")}, 'machines/machine.html', request ) @@ -1279,10 +1269,10 @@ def add_vlan(request): vlan = VlanForm(request.POST or None) if vlan.is_valid(): vlan.save() - messages.success(request, "Cet enregistrement vlan a été ajouté") + messages.success(request, _("The VLAN was created.")) return redirect(reverse('machines:index-vlan')) return form( - {'vlanform': vlan, 'action_name': 'Créer'}, + {'vlanform': vlan, 'action_name': _("Create a VLAN")}, 'machines/machine.html', request ) @@ -1296,10 +1286,10 @@ def edit_vlan(request, vlan_instance, **_kwargs): if vlan.is_valid(): if vlan.changed_data: vlan.save() - messages.success(request, "Vlan modifié") + messages.success(request, _("The VLAN was edited.")) return redirect(reverse('machines:index-vlan')) return form( - {'vlanform': vlan, 'action_name': 'Editer'}, + {'vlanform': vlan, 'action_name': _("Edit")}, 'machines/machine.html', request ) @@ -1315,16 +1305,15 @@ def del_vlan(request, instances): for vlan_del in vlan_dels: try: vlan_del.delete() - messages.success(request, "Le vlan a été supprimée") + messages.success(request, _("The VLAN was deleted.")) except ProtectedError: messages.error( request, - ("Erreur le Vlan suivant %s ne peut être supprimé" - % vlan_del) + (_("Error: the VLAN %s can't be deleted.") % vlan_del) ) return redirect(reverse('machines:index-vlan')) return form( - {'vlanform': vlan, 'action_name': 'Supprimer'}, + {'vlanform': vlan, 'action_name': _("Delete")}, 'machines/machine.html', request ) @@ -1337,10 +1326,10 @@ def add_nas(request): nas = NasForm(request.POST or None) if nas.is_valid(): nas.save() - messages.success(request, "Cet enregistrement nas a été ajouté") + messages.success(request, _("The NAS device was created.")) return redirect(reverse('machines:index-nas')) return form( - {'nasform': nas, 'action_name': 'Créer'}, + {'nasform': nas, 'action_name': _("Create a NAS device")}, 'machines/machine.html', request ) @@ -1354,10 +1343,10 @@ def edit_nas(request, nas_instance, **_kwargs): if nas.is_valid(): if nas.changed_data: nas.save() - messages.success(request, "Nas modifié") + messages.success(request, _("The NAS device was edited.")) return redirect(reverse('machines:index-nas')) return form( - {'nasform': nas, 'action_name': 'Editer'}, + {'nasform': nas, 'action_name': _("Edit")}, 'machines/machine.html', request ) @@ -1373,16 +1362,15 @@ def del_nas(request, instances): for nas_del in nas_dels: try: nas_del.delete() - messages.success(request, "Le nas a été supprimé") + messages.success(request, _("The NAS device was deleted.")) except ProtectedError: messages.error( request, - ("Erreur le Nas suivant %s ne peut être supprimé" - % nas_del) + (_("Error: the NAS device %s can't be deleted.") % nas_del) ) return redirect(reverse('machines:index-nas')) return form( - {'nasform': nas, 'action_name': 'Supprimer'}, + {'nasform': nas, 'action_name': _("Delete")}, 'machines/machine.html', request ) @@ -1634,7 +1622,7 @@ def edit_portlist(request, ouvertureportlist_instance, **_kwargs): for port in instances: port.port_list = pl port.save() - messages.success(request, "Liste de ports modifiée") + messages.success(request, _("The ports list was edited.")) return redirect(reverse('machines:index-portlist')) return form( {'port_list': port_list, 'ports': port_formset}, @@ -1648,7 +1636,7 @@ def edit_portlist(request, ouvertureportlist_instance, **_kwargs): def del_portlist(request, port_list_instance, **_kwargs): """ View used to delete a port policy """ port_list_instance.delete() - messages.success(request, "La liste de ports a été supprimée") + messages.success(request, _("The ports list was deleted.")) return redirect(reverse('machines:index-portlist')) @@ -1673,7 +1661,7 @@ def add_portlist(request): for port in instances: port.port_list = pl port.save() - messages.success(request, "Liste de ports créée") + messages.success(request, _("The ports list was created.")) return redirect(reverse('machines:index-portlist')) return form( {'port_list': port_list, 'ports': port_formset}, @@ -1691,8 +1679,8 @@ def configure_ports(request, interface_instance, **_kwargs): if not interface_instance.may_have_port_open(): messages.error( request, - ("Attention, l'ipv4 n'est pas publique, l'ouverture n'aura pas " - "d'effet en v4") + (_("Warning: the IPv4 isn't public, the opening won't have effect" + " in v4.")) ) interface = EditOuverturePortConfigForm( request.POST or None, @@ -1701,10 +1689,11 @@ def configure_ports(request, interface_instance, **_kwargs): if interface.is_valid(): if interface.changed_data: interface.save() - messages.success(request, "Configuration des ports mise à jour.") + messages.success(request, _("The ports configuration was edited.")) return redirect(reverse('machines:index')) return form( - {'interfaceform': interface, 'action_name': 'Editer la configuration'}, + {'interfaceform': interface, 'action_name': _("Edit the" + " configuration")}, 'machines/machine.html', request ) @@ -1950,3 +1939,4 @@ def regen_achieved(request): if obj: obj.first().done_regen() return HttpResponse("Ok") + From c3b3146f3910bd6fb7feec71fdac5c4f9a83ff6e Mon Sep 17 00:00:00 2001 From: Laouen Fernet Date: Sun, 5 Aug 2018 18:48:35 +0200 Subject: [PATCH 144/171] Translation of preferences/ (front) --- preferences/acl.py | 5 +- preferences/forms.py | 107 +-- preferences/locale/fr/LC_MESSAGES/django.mo | Bin 1245 -> 11865 bytes preferences/locale/fr/LC_MESSAGES/django.po | 625 ++++++++++++++++-- .../migrations/0050_auto_20180818_1329.py | 146 ++++ preferences/models.py | 102 +-- .../preferences/aff_mailcontact.html | 32 +- .../templates/preferences/aff_service.html | 37 +- .../preferences/display_preferences.html | 426 ++++++------ .../preferences/edit_preferences.html | 19 +- .../templates/preferences/preferences.html | 4 +- preferences/views.py | 45 +- 12 files changed, 1136 insertions(+), 412 deletions(-) create mode 100644 preferences/migrations/0050_auto_20180818_1329.py diff --git a/preferences/acl.py b/preferences/acl.py index 1f3f666e..d4b22cfe 100644 --- a/preferences/acl.py +++ b/preferences/acl.py @@ -25,6 +25,7 @@ Here are defined some functions to check acl on the application. """ +from django.utils.translation import ugettext as _ def can_view(user): @@ -38,4 +39,6 @@ def can_view(user): viewing is granted and msg is a message (can be None). """ can = user.has_module_perms('preferences') - return can, None if can else "Vous ne pouvez pas voir cette application." + return can, None if can else _("You don't have the right to view this" + " application.") + diff --git a/preferences/forms.py b/preferences/forms.py index 02463103..7da8b545 100644 --- a/preferences/forms.py +++ b/preferences/forms.py @@ -27,7 +27,7 @@ from __future__ import unicode_literals from django.forms import ModelForm, Form from django import forms - +from django.utils.translation import ugettext_lazy as _ from re2o.mixins import FormRevMixin from .models import ( OptionalUser, @@ -56,9 +56,13 @@ class EditOptionalUserForm(ModelForm): **kwargs ) self.fields['is_tel_mandatory'].label = ( - 'Exiger un numéro de téléphone' + _("Telephone number required") ) - self.fields['self_adhesion'].label = 'Auto inscription' + self.fields['gpg_fingerprint'].label = _("GPG fingerprint") + self.fields['all_can_create_club'].label = _("All can create a club") + self.fields['all_can_create_adherent'].label = _("All can create a member") + self.fields['self_adhesion'].label = _("Self registration") + self.fields['shell_default'].label = _("Default shell") class EditOptionalMachineForm(ModelForm): @@ -74,12 +78,17 @@ class EditOptionalMachineForm(ModelForm): prefix=prefix, **kwargs ) - self.fields['password_machine'].label = "Possibilité d'attribuer\ - un mot de passe par interface" - self.fields['max_lambdauser_interfaces'].label = "Maximum\ - d'interfaces autorisées pour un user normal" - self.fields['max_lambdauser_aliases'].label = "Maximum d'alias\ - dns autorisés pour un user normal" + self.fields['password_machine'].label = _("Possibility to set a" + " password per machine") + self.fields['max_lambdauser_interfaces'].label = _("Maximum number of" + " interfaces" + " allowed for a" + " standard user") + self.fields['max_lambdauser_aliases'].label = _("Maximum number of DNS" + " aliases allowed for" + " a standard user") + self.fields['ipv6_mode'].label = _("IPv6 mode") + self.fields['create_machine'].label = _("Can create a machine") class EditOptionalTopologieForm(ModelForm): @@ -95,10 +104,11 @@ class EditOptionalTopologieForm(ModelForm): prefix=prefix, **kwargs ) - self.fields['vlan_decision_ok'].label = "Vlan où placer les\ - machines après acceptation RADIUS" - self.fields['vlan_decision_nok'].label = "Vlan où placer les\ - machines après rejet RADIUS" + self.fields['radius_general_policy'].label = _("RADIUS general policy") + self.fields['vlan_decision_ok'].label = _("VLAN for machines accepted" + " by RADIUS") + self.fields['vlan_decision_nok'].label = _("VLAN for machines rejected" + " by RADIUS") class EditGeneralOptionForm(ModelForm): @@ -114,18 +124,25 @@ class EditGeneralOptionForm(ModelForm): prefix=prefix, **kwargs ) - self.fields['search_display_page'].label = 'Resultats\ - affichés dans une recherche' - self.fields['pagination_number'].label = 'Items par page,\ - taille normale (ex users)' - self.fields['pagination_large_number'].label = 'Items par page,\ - taille élevée (machines)' - self.fields['req_expire_hrs'].label = 'Temps avant expiration du lien\ - de reinitialisation de mot de passe (en heures)' - self.fields['site_name'].label = 'Nom du site web' - self.fields['email_from'].label = "Adresse mail d\ - 'expedition automatique" - self.fields['GTU_sum_up'].label = "Résumé des CGU" + self.fields['general_message'].label = _("General message") + self.fields['search_display_page'].label = _("Number of results" + " displayed when" + " searching") + self.fields['pagination_number'].label = _("Number of items per page," + " standard size (e.g." + " users)") + self.fields['pagination_large_number'].label = _("Number of items per" + " page, large size" + " (e.g. machines)") + self.fields['req_expire_hrs'].label = _("Time before expiration of the" + " reset password link (in" + " hours)") + self.fields['site_name'].label = _("Website name") + self.fields['email_from'].label = _("Email address for automatic" + " emailing") + self.fields['GTU_sum_up'].label = _("Summary of the General Terms of" + " Use") + self.fields['GTU'].label = _("General Terms of Use") class EditAssoOptionForm(ModelForm): @@ -141,15 +158,19 @@ class EditAssoOptionForm(ModelForm): prefix=prefix, **kwargs ) - self.fields['name'].label = 'Nom de l\'asso' - self.fields['siret'].label = 'SIRET' - self.fields['adresse1'].label = 'Adresse (ligne 1)' - self.fields['adresse2'].label = 'Adresse (ligne 2)' - self.fields['contact'].label = 'Email de contact' - self.fields['telephone'].label = 'Numéro de téléphone' - self.fields['pseudo'].label = 'Pseudo d\'usage' - self.fields['utilisateur_asso'].label = 'Compte utilisé pour\ - faire les modifications depuis /admin' + self.fields['name'].label = _("Organisation name") + self.fields['siret'].label = _("SIRET number") + self.fields['adresse1'].label = _("Address (line 1)") + self.fields['adresse2'].label = _("Address (line 2)") + self.fields['contact'].label = _("Contact email address") + self.fields['telephone'].label = _("Telephone number") + self.fields['pseudo'].label = _("Usual name") + self.fields['utilisateur_asso'].label = _("Account used for editing" + " from /admin") + self.fields['payment'].label = _("Payment") + self.fields['payment_id'].label = _("Payment ID") + self.fields['payment_pass'].label = _("Payment password") + self.fields['description'].label = _("Description") class EditMailMessageOptionForm(ModelForm): @@ -165,10 +186,10 @@ class EditMailMessageOptionForm(ModelForm): prefix=prefix, **kwargs ) - self.fields['welcome_mail_fr'].label = 'Message dans le\ - mail de bienvenue en français' - self.fields['welcome_mail_en'].label = 'Message dans le\ - mail de bienvenue en anglais' + self.fields['welcome_mail_fr'].label = _("Message for the French" + " welcome email") + self.fields['welcome_mail_en'].label = _("Message for the English" + " welcome email") class EditHomeOptionForm(ModelForm): @@ -184,6 +205,9 @@ class EditHomeOptionForm(ModelForm): prefix=prefix, **kwargs ) + self.fields['facebook_url'].label = _("Facebook URL") + self.fields['twitter_url'].label = _("Twitter URL") + self.fields['twitter_account_name'].label = _("Twitter account name") class ServiceForm(ModelForm): @@ -195,13 +219,17 @@ class ServiceForm(ModelForm): def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(ServiceForm, self).__init__(*args, prefix=prefix, **kwargs) + self.fields['name'].label = _("Name") + self.fields['url'].label = _("URL") + self.fields['description'].label = _("Description") + self.fields['image'].label = _("Image") class DelServiceForm(Form): """Suppression de services sur la page d'accueil""" services = forms.ModelMultipleChoiceField( queryset=Service.objects.none(), - label="Enregistrements service actuels", + label=_("Current services"), widget=forms.CheckboxSelectMultiple ) @@ -239,3 +267,4 @@ class DelMailContactForm(Form): self.fields['mailcontacts'].queryset = instances else: self.fields['mailcontacts'].queryset = MailContact.objects.all() + diff --git a/preferences/locale/fr/LC_MESSAGES/django.mo b/preferences/locale/fr/LC_MESSAGES/django.mo index 21ed01a47b5f1f12bd59e566a77fbaba297530be..657adab31015ec0a5c0e5dd7aa792a0dc1569c4c 100644 GIT binary patch literal 11865 zcmcJUdu%1wUB^#oUfnd!gVJ}%X_~Efo3*pM*-bajgLQ20#<1RX{n#Z*OESK9d_CEF z@7&DHwbxr3pb3SBs;!hFk@5-zDeHoYRE5xpp#EW0(FaIWlt-wbil_vA@K1}Psv!7$ z&zzY%*S_9tBN+KN-#h1he&_c(KmYT!7yr89c^rB>)V$c3&x0>q&Wq=BR~Ykp@Okih z@Grrez<&d81mEyFW8Mbd23`p+fNEa@F9Q?sYVfSbUju)F@81I71b)`zm%umh{Sr6? zz6{;~UeBOf*WKWC-~;~qgWx;({*?cI!PoyI_!i1v1~vaREJpLa1yui=LCrG_YCnsh z*7qS_{sc%@a}Lz{o&zrh{~A>LuYqjc{3o~vyy&XDd;}EBj)U(4Pl2$~JOaKEd=z{u z_$g3)`xEdd!56`u;159U`+63`*3D5+>zoIvG7+eKtoibf`|>{k-%t6o;QPR9uQ6s9 z7=v2xZ-74zeh#$YSHRbT-vu?#%ity8#n2F3qXkU#TDURuvHpw{=N z;BDYnK&}5$COZU1e|ecufp08e{-3Vb);UjsFc zn4opP3uNi$7LY143F^G>19yOnAS^L0Q2TfU)VZAl_kz!Y{F!g^(s(}vHQwbM);qzs zg6e-4D0w;o>RcmG>-dDn=Roo2>mYyT2fW@4UXAcbes=R(2gg9RYQ7EL0sarDb>Bf_ zt?Rv@>faBl{9*7l;Df6aeqpw{~sDE>bQN)LVyRJ%`u zn)lDZ0~cXGK<)FtF&g6YH8AfWcn~}Qo(5I^6sUH8;qf0pjsG1`=k$G#m!j0NKW_#t zc);TVsP(UalAn)(H-pcEvK#*nN?xx(2`zXFsCno8_Zd+3=Sg4w0;qHP2B>kr18Uxj zITZ27f;yjDd1>D#LGix<($stu)c*bul>U9g*MAGtxL4U+{@(_&H8TOKJOKBCEl_;@ z9Z=&v1F{A41yFkPub}3;lFezJ{opNN1C(6;rpM2MvTt7m#oxaN)$iM&_|NBe2~P993+3Abu7D%p)8K2suY%I^zXQcDEn4;az@Gvi1U2qc zp!o6*Jv2eHxVfeF4C-Ueuke9L5HCaLU%*&f!+s+A1v4WGNd_*N9(@B ze|#AHSqOGJKkJLlQUBvCsC6wvAApWSl0U@Io%?ISUxAv?N1*pY#Up-C`VZ}0&wY^i z6hmy$EJAuN&cWR2QShk$Ek5aKLH9s9FX`e8bPUq-pnK=GLg#liv;ygDZiOC)Pze)3 zvydL?>s{_w{*wG<(6ld;KIxI%>9OwJdr2Ilh72DKugdeNYBGi2de3V=SU9a`x1WrG_)TYhZdkKp;OQUke){%$^U8S=b+mm z$)%n&2h;T^2%uktq}!6)HRv(uM(76ULr@!f9drN+p?5=wb@@nsUtfM1^Gp8sC^!$z zK{rG1g!I&*Gm!MXeC$}gZUdW!$x2iUjhzUaVHVoB4J8z>gh|jWs#7y&+u^G1(%ja9 zwyh;$K{1f9?9D;F6}3%1eJyTh zK`pakD~OslsMnJ)WwO4dxvzG$vS=i0IGT1V>f39Jj*Gwq57~|h%IJZawFcxOYSsMFThjI=NzPIT#L|;dcRzc z3~F3G88g8VEjGitnTSz{w#{OxoLgWsuAz@!PjUrI%_NHDUeYdR?Yg&!6Wqa66wzX0_Xjv78P9{m5j5@9q z0=6@iQVwZ2TX+1fkC-DtEnJM_({^F@Sns1SEGj%YbJQ-et}y8&5%RU^o4T-BH%Gnx z%!f%U6-O4*uq-TOp=eZSVm(Sb&0vk=LNIlJlkG+`N|!BinR;PW@oKo3Mp(UF;Yee| z!irkJ;V_BLDht~IeBZr!HoIl?BRZh}ufhEq%T=JaQEkng$jM`4$I+5ZrnpyMk(jBO z!pMfHgg9!nC0A(Z#69CPE4P{{UbYq2!~9p;?R4TKGgGagK~Wn)k_^GbU@`7yj+K4l zeJnzX;MYKtiO3f9UQ}2_yKYOXR~7DK1GK?%0#13VmAU+yV{yCAx+cf>&)P#{_n7J2 zj;DQUF&&(VTHTgycjXYY(23)7HfTm1Gizx!<5lm_@S7QH3zE8Xi43>7r&rYez607x zaFQjY|3{smPw{e1W(=pxn-}LDj_Rt?y&5)ac$8e@&Ge>LUP}8L9S>TeIUZ)KadKLs zQ9WIo)|TVAY3CR-!$~&G@sf{d4eHo|sm_1xY6b}&FpbWlFWa{&d3^qcdu(qwubYMQ z*nadqJtk>%-_Lo0Y9|5;yhd74;??D_jTQxo0)d7(ky{K@CkZf4L#cIYhU|#dejVmS z#r^hu*tR%2gR;TWy5msMXC_#4j?|-_nxLI4f{A&1GZUkwi%~Pm*5oTtZUT~`wtuvW zp3kr=T4KP4++rmd1;-T3g&nyr7yV33EzAv>*)gFlf|J~)n7OIh$$76VW-e?l*#rlX zW_k2v=DMv`kPtHDtSF3bpJkrM{&$uYbLKi{HWx7D!`&#sBhJIlfp){L26z+~kK#zq z-%NpMxJ@e;J8G=bDQT>V(ZT*FXiH^?!lkJf(LphCwYYDvYDI6HC@riqQ{&=LW66#? z8gIUXC9H%$8hM%C;f&f4e=KWtA%eI^v3=l&{!yi1ei zND!Pk(}{fK?9V)(p_E4g_0x72Vz(T3lXSP4FQUo{lg&Kwg*GdnU;M7d0h`O50;%yr z`*b^AC9=#TZyY~NalZ=@tXeDM>Z=p0z=bBo<;LF^29*07L$5}MsYBy3ln0m#X_sit zt?fco%xrCxgt|af_0XL1@rN7sRMmPruKK8DH|;8h-Bsnre7Ctb?ppjH!Pj!I;;wHt zi5knSJ$8c49+1X7lpi^7Yj^o^t%FX75X1TX5i?Lj2bYGa=?l@zyG3DUiW@~402Bq@ zR+Sy9=UNteT{9k2v*ixcbrRxlVKYfAeh*L8hfn5F->BU`a&X6Ns1Rp(I&DPt;k&zy zba*}+MeOm9`cW8L;q2Yap?7mw@_uqQc-u-*`(r|b-T!|D$_O-ip zc>e*r@8Iac19`h+L7ENE<3rMBkSSz77Q|gV&5#*)DJd@o_{2R4z zx3%RU!8P2uFn?tDP_Lbq#mvK#?OI&N>y6q&i&3`2*s+I*^U$-dT%u1T%n=nPm<3mg zymgx0GzwApqDhLX+IHMs37fW?;k>w(QjfbLW~?O}>vT;qX9OoWBAZg>(hB4Gciv@V z-FC1dwu!YQB8`^32+DkSx(IQRW++$4z0BnY$=M7?p#Sj6nC?SySSqOW{jDc3ah#- zp^O<+z7k`Gm`lcwcwn1NyCGatvCylQxKiATdhCT=X+Gta_Kl78Hj54zW1AL?=wiP5 z5LUb!M$PTpZQVuR(uRtAHM+F@AcY)myFYISBEqhcTmAD?gp#9h>5PCFq&)Y{@-vRi z=Jy^66*(qOo^#3&0kRjBx+2J`mJXH2Ugvq&VW2T*D z2owJ!_fiNstma&noVqDjGE@D^O@nMtK3M;`m-A^J4#+1?P)=vp^803CV`9Pr)O<4D4X^0ZZ=fr|%fZqsT;#(ycI~c4taXHn*CDSa##ngk)GtcU~LoBZi>J z*1LrnEix?I6;HMZhIAXXWLC`ha?t8|qQ#k}YMy1wOxupTg#X%j=KpSF-CW5M z&X8KkB3b2fvm8h6l<<1o>2-f|o5UI`!rLTgy>6tuvn+`gIddEzrZU%*)N-$s)cE`h z;v7;$CPunQTq344hg`{)Bn_hg_Z-~2cb}aJl4yYbm6q~&+*(AeTRy=8Ym`#S z6EmnDoeMh%k1HxuUhmB|BjiTtRSr?j+Pumk`VYWmb95{hHd-P3g|xZNz~03uY!h)H zk|>Q=r&0mW+ql#b@ym?$46sfO>qy3ftAnI%W4#t*g{EBwt>A8*;^xM>1U`3Td7dZ3 zM$$HFNmv7n3%Qm2K+oTy$o!nY`5wDveDXLl8HM;ZJFmkYknyUctG=;cVwKgJD(7)E(wUB(n(G#w`}3mb zOb|H>W2Pq5t5baTb*NVKvPI|&73AsUGEkh>wsTY+qcdNMCZcx4Ubqza1X9H(x_^(s zAD2bKyXVr7Cy<5)n@m=c*DDo>OOCC}EV+NkMeW{yT4s?#HDKwfB}9H@XH<;G44IQp zmuo7g=`YSxO(dZU>zf-Z8hO)5;$&l;ae6+M*yWX5=eH9V=)7{{(tT@aF|W#hay*$| zLOE!vc4bIVtT526M=`*Fx^jwvwZ*_AX50xp9$9jmA9^M!S4t{(d$4Apo3jDSK351q z9vRDv<)=K#<&Ln(Ij0L!5mL3AJBTfQO|ME0_t^v+$@vq%AW8S?jO&n_A+6RU-oj+wsGqkHe}(I1@oq}Ha`tSEJ6p$z1T zQsul(xGP@7wY=T#`mc)hay#q`05+{?sM6TUTjd+!8+?70T)slP@=mk;pt+zC6*m>w zmJYSNy5z=isH^{#=jpKvv@+W@RzeM(=(9-qBzd&SKWFl^K+lQlEWH=qy-juLH+P$p zy7IS&NUWX37~>g`utbu}!I!(OVRMWB{g0fti@R&*&HGYpeYYrvE&{jyfTB{d^={A6 umbnw%VocGb8j%)djI=4(tJD;}s4VJ}w&E^cM3Ut^jM-C#Uf+sTng0bWX`3(r literal 1245 zcmZ{jO=}ZD7{^DgZ_#?NN~J13Afi$>CPB1rZE>TVRYy$QZ zX4cX^#>T-KcnEv|o(3O-r$7y!1-Ekld+-AGAHiwx3pfV;08QL4(Bzre&)6h51)c{h z;39l2(A0l(fU(2i2hjBO2|NRS1uuf%!K2_flAHY`cmc^_zIi?--1T} z8AQ44ZU;e=%PbRXmg&*_CTNEpfkB?-IS!(9whB2jI6**OL|!1dQp_1)0Vj%c^5aN* zz9!!HM93EQ?zSWTb;uQkLTOgzA=f+y#Sy1iQa=r~NJ5?`;0k`_OOa?1M-;D-UWZSq z*cYDW!KijFE;2U|nz^Q({D{&dFuEdZqf7aebxnA74SMD&@+BX+&3Sf@z1|?}5Wy`O zZ*X5*wZK~CQlS-_TF%0K=blTeZo5-!E*IK7iKVt0swV>LZrW2;H@2zJYIdy!Qw2d* z6~%0-l*%(!>84e=L6x$7{nm7;QYt}cwfUwn$#dO9*J)dgrd4%lscAQA(~p|VZlUfe zZFQv=snFB0v}tjrUZ-WJ;nHol;neDPoNBf0b~^KrM!nSQp=6;^Yq-0;my4x>c^V!e z)r$lI&7bRQN$81ami%>3Dz4{Nx{KD#uFjNPdea>BpXr9VfXA8{Ph*QlarFiSnBk+8tIBW4#(`GD;m$DZOb|!3Fl)um( zM;(L2DZohO`PAQ, YEAR. +# 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. # -#, fuzzy +# Copyright © 2018 Maël Kervella +# +# 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. msgid "" msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" +"Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-07-26 21:49+0200\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language: \n" +"POT-Creation-Date: 2018-08-18 13:26+0200\n" +"PO-Revision-Date: 2018-06-24 15:54+0200\n" +"Last-Translator: Laouen Fernet \n" +"Language-Team: \n" +"Language: fr_FR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: models.py:256 -msgid "Contact email adress" -msgstr "Adresse email de contact" +#: acl.py:42 +msgid "You don't have the right to view this application." +msgstr "Vous n'avez pas le droit de voir cette application." -#: models.py:263 -msgid "Description of the associated email adress." +#: forms.py:59 templates/preferences/display_preferences.html:41 +msgid "Telephone number required" +msgstr "Numéro de téléphone requis" + +#: forms.py:61 +msgid "GPG fingerprint" +msgstr "Empreinte GPG" + +#: forms.py:62 +msgid "All can create a club" +msgstr "Tous peuvent créer un club" + +#: forms.py:63 +msgid "All can create a member" +msgstr "Tous peuvent créer un adhérent" + +#: forms.py:64 templates/preferences/display_preferences.html:43 +msgid "Self registration" +msgstr "Autoinscription" + +#: forms.py:65 +msgid "Default shell" +msgstr "Interface système par défaut" + +#: forms.py:81 +msgid "Possibility to set a password per machine" +msgstr "Possibilité de mettre un mot de passe par machine" + +#: forms.py:83 templates/preferences/display_preferences.html:87 +msgid "Maximum number of interfaces allowed for a standard user" +msgstr "Nombre maximum d'interfaces autorisé pour un utilisateur standard" + +#: forms.py:87 templates/preferences/display_preferences.html:91 +msgid "Maximum number of DNS aliases allowed for a standard user" +msgstr "Nombre maximum d'alias DNS autorisé pour un utilisateur standard" + +#: forms.py:90 +msgid "IPv6 mode" +msgstr "Mode IPv6" + +#: forms.py:91 +msgid "Can create a machine" +msgstr "Peut créer une machine" + +#: forms.py:107 +msgid "RADIUS general policy" +msgstr "Politique générale de RADIUS" + +#: forms.py:108 templates/preferences/display_preferences.html:116 +msgid "VLAN for machines accepted by RADIUS" +msgstr "VLAN pour les machines acceptées par RADIUS" + +#: forms.py:110 templates/preferences/display_preferences.html:118 +msgid "VLAN for machines rejected by RADIUS" +msgstr "VLAN pour les machines rejetées par RADIUS" + +#: forms.py:127 +msgid "General message" +msgstr "Message général" + +#: forms.py:128 templates/preferences/display_preferences.html:137 +msgid "Number of results displayed when searching" +msgstr "Nombre de résultats affichés lors de la recherche" + +#: forms.py:131 +msgid "Number of items per page, standard size (e.g. users)" +msgstr "Nombre d'éléments par page, taille standard (ex : utilisateurs)" + +#: forms.py:134 +msgid "Number of items per page, large size (e.g. machines)" +msgstr "Nombre d'éléments par page, taille importante (ex : machines)" + +#: forms.py:137 templates/preferences/display_preferences.html:145 +msgid "Time before expiration of the reset password link (in hours)" +msgstr "" +"Temps avant expiration du lien de réinitialisation de mot de passe (en " +"heures)" + +#: forms.py:140 templates/preferences/display_preferences.html:131 +msgid "Website name" +msgstr "Nom du site" + +#: forms.py:141 templates/preferences/display_preferences.html:133 +msgid "Email address for automatic emailing" +msgstr "Adresse mail pour les mails automatiques" + +#: forms.py:143 templates/preferences/display_preferences.html:151 +msgid "Summary of the General Terms of Use" +msgstr "Résumé des Conditions Générales d'Utilisation" + +#: forms.py:145 templates/preferences/display_preferences.html:155 +msgid "General Terms of Use" +msgstr "Conditions Générales d'Utilisation" + +#: forms.py:161 +msgid "Organisation name" +msgstr "Nom de l'association" + +#: forms.py:162 templates/preferences/display_preferences.html:170 +msgid "SIRET number" +msgstr "Numéro SIRET" + +#: forms.py:163 +msgid "Address (line 1)" +msgstr "Adresse (ligne 1)" + +#: forms.py:164 +msgid "Address (line 2)" +msgstr "Adresse (ligne 2)" + +#: forms.py:165 models.py:288 +#: templates/preferences/display_preferences.html:178 +msgid "Contact email address" +msgstr "Adresse mail de contact" + +#: forms.py:166 templates/preferences/display_preferences.html:182 +msgid "Telephone number" +msgstr "Numéro de téléphone" + +#: forms.py:167 templates/preferences/display_preferences.html:184 +msgid "Usual name" +msgstr "Nom d'usage" + +#: forms.py:168 +msgid "Account used for editing from /admin" +msgstr "Compte utilisé pour les modifications depuis /admin" + +#: forms.py:170 +msgid "Payment" +msgstr "Paiement" + +#: forms.py:171 +msgid "Payment ID" +msgstr "ID de paiement" + +#: forms.py:172 +msgid "Payment password" +msgstr "Mot de passe de paiement" + +#: forms.py:173 forms.py:224 templates/preferences/aff_service.html:33 +msgid "Description" +msgstr "Description" + +#: forms.py:189 +msgid "Message for the French welcome email" +msgstr "Message pour le mail de bienvenue en français" + +#: forms.py:191 +msgid "Message for the English welcome email" +msgstr "Message pour le mail de bienvenue en anglais" + +#: forms.py:208 +msgid "Facebook URL" +msgstr "URL du compte Facebook" + +#: forms.py:209 +msgid "Twitter URL" +msgstr "URL du compte Twitter" + +#: forms.py:210 templates/preferences/display_preferences.html:233 +msgid "Twitter account name" +msgstr "Nom du compte Twitter" + +#: forms.py:222 templates/preferences/aff_service.html:31 +#: templates/preferences/display_preferences.html:168 +msgid "Name" +msgstr "Nom" + +#: forms.py:223 templates/preferences/aff_service.html:32 +msgid "URL" +msgstr "URL" + +#: forms.py:225 templates/preferences/aff_service.html:34 +msgid "Image" +msgstr "Image" + +#: forms.py:232 +msgid "Current services" +msgstr "Services actuels" + +#: models.py:71 +msgid "Users can create a club" +msgstr "Les utilisateurs peuvent créer un club" + +#: models.py:75 +msgid "Users can create a member" +msgstr "Les utilisateurs peuvent créer un adhérent" + +#: models.py:79 +msgid "A new user can create their account on Re2o" +msgstr "Un nouvel utilisateur peut créer son compte sur Re2o" + +#: models.py:89 templates/preferences/display_preferences.html:49 +msgid "Users can edit their shell" +msgstr "Les utilisateurs peuvent modifier leur interface système" + +#: models.py:93 +msgid "Enable local email accounts for users" +msgstr "Active les comptes mail locaux pour les utilisateurs" + +#: models.py:98 +msgid "Domain to use for local email accounts" +msgstr "Domaine à utiliser pour les comptes mail locaux" + +#: models.py:102 +msgid "Maximum number of local email addresses for a standard user" +msgstr "" +"Nombre maximum d'adresses mail locales autorisé pour un utilisateur standard" + +#: models.py:108 +msgid "Can view the user options" +msgstr "Peut voir les options d'utilisateur" + +#: models.py:110 +msgid "user options" +msgstr "options d'utilisateur" + +#: models.py:117 +msgid "Email domain must begin with @" +msgstr "Un domaine mail doit commencer par @" + +#: models.py:135 +msgid "Autoconfiguration by RA" +msgstr "Configuration automatique par RA" + +#: models.py:136 +msgid "IP addresses assigning by DHCPv6" +msgstr "Attribution d'adresses IP par DHCPv6" + +#: models.py:137 +msgid "Disabled" +msgstr "Désactivé" + +#: models.py:159 +msgid "Can view the machine options" +msgstr "Peut voir les options de machine" + +#: models.py:161 +msgid "machine options" +msgstr "options de machine" + +#: models.py:180 +msgid "On the IP range's VLAN of the machine" +msgstr "Sur le VLAN de la plage d'IP de la machine" + +#: models.py:181 +msgid "Preset in 'VLAN for machines accepted by RADIUS'" +msgstr "Prédéfinie dans 'VLAN pour les machines acceptées par RADIUS'" + +#: models.py:206 +msgid "Can view the topology options" +msgstr "Peut voir les options de topologie" + +#: models.py:208 +msgid "topology options" +msgstr "options de topologie" + +#: models.py:225 +msgid "" +"General message displayed on the French version of the website (e.g. in case " +"of maintenance)" +msgstr "" +"Message général affiché sur la version française du site (ex : en cas de " +"maintenance)" + +#: models.py:231 +msgid "" +"General message displayed on the English version of the website (e.g. in " +"case of maintenance)" +msgstr "" +"Message général affiché sur la version anglaise du site (ex : en cas de " +"maintenance)" + +#: models.py:253 +msgid "Can view the general options" +msgstr "Peut voir les options générales" + +#: models.py:255 +msgid "general options" +msgstr "options générales" + +#: models.py:275 +msgid "Can view the service options" +msgstr "Peut voir les options de service" + +#: models.py:277 +msgid "service" +msgstr "service" + +#: models.py:278 +msgid "services" +msgstr "services" + +#: models.py:295 +msgid "Description of the associated email address." msgstr "Description de l'adresse mail associée." -#: models.py:273 -msgid "Can see contact email" -msgstr "Peut voir un mail de contact" +#: models.py:305 +msgid "Can view a contact email address object" +msgstr "Peut voir un objet adresse mail de contact" -#: templates/preferences/aff_mailcontact.html:30 -msgid "Adress" -msgstr "Adresse" +#: models.py:307 +msgid "contact email address" +msgstr "adresse mail de contact" + +#: models.py:308 +msgid "contact email addresses" +msgstr "adresses mail de contact" + +#: models.py:318 +msgid "Networking organisation school Something" +msgstr "Association de réseau de l'école Machin" + +#: models.py:322 +msgid "Threadneedle Street" +msgstr "1 rue de la Vrillière" + +#: models.py:323 +msgid "London EC2R 8AH" +msgstr "75001 Paris" + +#: models.py:326 +msgid "Organisation" +msgstr "Association" + +#: models.py:340 +msgid "Can view the organisation options" +msgstr "Peut voir les options d'association" + +#: models.py:342 +msgid "organisation options" +msgstr "options d'association" + +#: models.py:371 +msgid "Can view the homepage options" +msgstr "Peut voir les options de page d'accueil" + +#: models.py:373 +msgid "homepage options" +msgstr "options de page d'accueil" + +#: models.py:391 +msgid "Can view the email message options" +msgstr "Peut voir les options de message pour les mails" + +#: models.py:394 +msgid "email message options" +msgstr "options de messages pour les mails" #: templates/preferences/aff_mailcontact.html:31 -msgid "Remark" +#: templates/preferences/display_preferences.html:174 +msgid "Address" +msgstr "Adresse" + +#: templates/preferences/aff_mailcontact.html:32 +msgid "Comment" msgstr "Commentaire" -#: templates/preferences/display_preferences.html:205 -msgid "Contact email adresses list" -msgstr "Liste des adresses email de contact" +#: templates/preferences/display_preferences.html:31 +#: templates/preferences/edit_preferences.html:30 +#: templates/preferences/preferences.html:29 +msgid "Preferences" +msgstr "Préférences" + +#: templates/preferences/display_preferences.html:34 +msgid "User preferences" +msgstr "Préférences d'utilisateur" + +#: templates/preferences/display_preferences.html:37 +#: templates/preferences/display_preferences.html:79 +#: templates/preferences/display_preferences.html:104 +#: templates/preferences/display_preferences.html:125 +#: templates/preferences/display_preferences.html:162 +#: templates/preferences/display_preferences.html:197 +#: templates/preferences/display_preferences.html:219 +#: templates/preferences/edit_preferences.html:40 views.py:170 views.py:234 +msgid "Edit" +msgstr "Modifier" + +#: templates/preferences/display_preferences.html:47 +msgid "Default shell for users" +msgstr "Interface système par défaut pour les utilisateurs" + +#: templates/preferences/display_preferences.html:53 +msgid "Creation of members by everyone" +msgstr "Création d'adhérents par tous" + +#: templates/preferences/display_preferences.html:55 +msgid "Creation of clubs by everyone" +msgstr "Création de clubs par tous" + +#: templates/preferences/display_preferences.html:59 +msgid "GPG fingerprint field" +msgstr "Champ empreinte GPG" + +#: templates/preferences/display_preferences.html:63 +msgid "Email accounts preferences" +msgstr "Préférences de comptes mail" + +#: templates/preferences/display_preferences.html:66 +msgid "Local email accounts enabled" +msgstr "Comptes mail locaux activés" + +#: templates/preferences/display_preferences.html:68 +msgid "Local email domain" +msgstr "Domaine de mail local" + +#: templates/preferences/display_preferences.html:72 +msgid "Maximum number of email aliases allowed" +msgstr "Nombre maximum d'alias mail autorisé pour un utilisateur standard" + +#: templates/preferences/display_preferences.html:76 +msgid "Machines preferences" +msgstr "Préférences de machines" + +#: templates/preferences/display_preferences.html:85 +msgid "Password per machine" +msgstr "Mot de passe par machine" + +#: templates/preferences/display_preferences.html:93 +msgid "IPv6 support" +msgstr "Support de l'IPv6" + +#: templates/preferences/display_preferences.html:97 +msgid "Creation of machines" +msgstr "Création de machines" + +#: templates/preferences/display_preferences.html:101 +msgid "Topology preferences" +msgstr "Préférences de topologie" + +#: templates/preferences/display_preferences.html:110 +msgid "General policy for VLAN setting" +msgstr "Politique générale pour le placement sur un VLAN" + +#: templates/preferences/display_preferences.html:112 +msgid "" +"This setting defines the VLAN policy after acceptance by RADIUS: either on " +"the IP range's VLAN of the machine, or a VLAN preset in 'VLAN for machines " +"accepted by RADIUS'" +msgstr "" +"Ce réglage définit la politique de placement sur un VLAN après acceptation " +"par RADIUS: soit sur le VLAN de la plage d'IP de la machine, soit sur le " +"VLAN prédéfini dans 'VLAN pour les machines acceptées par RADIUS'" + +#: templates/preferences/display_preferences.html:122 +msgid "General preferences" +msgstr "Préférences générales" + +#: templates/preferences/display_preferences.html:139 +msgid "Number of items per page (standard size)" +msgstr "Nombre d'éléments par page (taille standard)" + +#: templates/preferences/display_preferences.html:143 +msgid "Number of items per page (large size)" +msgstr "Nombre d'éléments par page (taille importante)" + +#: templates/preferences/display_preferences.html:149 +msgid "General message displayed on the website" +msgstr "Message général affiché sur le site" + +#: templates/preferences/display_preferences.html:159 +msgid "Information about the organisation" +msgstr "Informations sur l'association" + +#: templates/preferences/display_preferences.html:188 +msgid "User object of the organisation" +msgstr "Objet utilisateur de l'association" + +#: templates/preferences/display_preferences.html:190 +msgid "Description of the organisation" +msgstr "Description de l'association" + +#: templates/preferences/display_preferences.html:194 +msgid "Custom email message" +msgstr "Message personnalisé pour les mails" + +#: templates/preferences/display_preferences.html:203 +msgid "Welcome email (in French)" +msgstr "Mail de bienvenue (en français)" #: templates/preferences/display_preferences.html:207 -msgid "Add an adress" +msgid "Welcome email (in English)" +msgstr "Mail de bienvenue (en anglais)" + +#: templates/preferences/display_preferences.html:211 +msgid "List of services and homepage preferences" +msgstr "Liste des services et préférences de page d'accueil" + +#: templates/preferences/display_preferences.html:213 +msgid " Add a service" +msgstr " Ajouter un service" + +#: templates/preferences/display_preferences.html:215 +msgid " Delete one or several services" +msgstr " Supprimer un ou plusieurs services" + +#: templates/preferences/display_preferences.html:221 +msgid "List of contact email addresses" +msgstr "Liste des adresses mail de contact" + +#: templates/preferences/display_preferences.html:223 +msgid "Add an address" msgstr "Ajouter une adresse" -#: templates/preferences/display_preferences.html:209 -msgid "Delete one or multiple adresses" -msgstr "Supprimer une ou plusieurs adresses" +#: templates/preferences/display_preferences.html:225 +msgid "Delete one or several addresses" +msgstr " Supprimer une ou plusieurs adresses" -#: views.py:210 -msgid "The adress was created." -msgstr "L'adresse a été créée." +#: templates/preferences/display_preferences.html:231 +msgid "Twitter account URL" +msgstr "URL du compte Twitter" -#: views.py:230 -msgid "Email adress updated." -msgstr "L'adresse email a été mise à jour." +#: templates/preferences/display_preferences.html:237 +msgid "Facebook account URL" +msgstr "URL du compte Facebook" -#: views.py:233 -msgid "Edit" -msgstr "Éditer" +#: templates/preferences/edit_preferences.html:35 +msgid "Editing of preferences" +msgstr "Modification des préférences" -#: views.py:251 -msgid "The email adress was deleted." -msgstr "L'adresse email a été supprimée." +#: views.py:98 +msgid "Unknown object" +msgstr "Objet inconnu" -#: views.py:254 +#: views.py:104 +msgid "You don't have the right to edit this option." +msgstr "Vous n'avez pas le droit de modifier cette option." + +#: views.py:121 +msgid "The preferences were edited." +msgstr "Les préférences ont été modifiées." + +#: views.py:140 +msgid "The service was added." +msgstr "Le service a été ajouté." + +#: views.py:143 +msgid "Add a service" +msgstr " Ajouter un service" + +#: views.py:167 +msgid "The service was edited." +msgstr "Le service a été modifié." + +#: views.py:188 +msgid "The service was deleted." +msgstr "Le service a été supprimé." + +#: views.py:190 +#, python-format +msgid "Error: the service %s can't be deleted." +msgstr "Erreur : le service %s ne peut pas être supprimé." + +#: views.py:194 views.py:256 msgid "Delete" msgstr "Supprimer" + +#: views.py:210 +msgid "The contact email address was created." +msgstr "L'adresse mail de contact a été supprimée." + +#: views.py:214 +msgid "Add a contact email address" +msgstr "Ajouter une adresse mail de contact" + +#: views.py:231 +msgid "The contact email address was edited." +msgstr "L'adresse mail de contact a été modifiée." + +#: views.py:253 +msgid "The contact email adress was deleted." +msgstr "L'adresse mail de contact a été supprimée." diff --git a/preferences/migrations/0050_auto_20180818_1329.py b/preferences/migrations/0050_auto_20180818_1329.py new file mode 100644 index 00000000..1cd4a269 --- /dev/null +++ b/preferences/migrations/0050_auto_20180818_1329.py @@ -0,0 +1,146 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-08-18 11:29 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('preferences', '0049_optionaluser_self_change_shell'), + ] + + operations = [ + migrations.AlterModelOptions( + name='assooption', + options={'permissions': (('view_assooption', 'Can view the organisation options'),), 'verbose_name': 'organisation options'}, + ), + migrations.AlterModelOptions( + name='generaloption', + options={'permissions': (('view_generaloption', 'Can view the general options'),), 'verbose_name': 'general options'}, + ), + migrations.AlterModelOptions( + name='homeoption', + options={'permissions': (('view_homeoption', 'Can view the homepage options'),), 'verbose_name': 'homepage options'}, + ), + migrations.AlterModelOptions( + name='mailcontact', + options={'permissions': (('view_mailcontact', 'Can view a contact email address object'),), 'verbose_name': 'contact email address', 'verbose_name_plural': 'contact email addresses'}, + ), + migrations.AlterModelOptions( + name='mailmessageoption', + options={'permissions': (('view_mailmessageoption', 'Can view the email message options'),), 'verbose_name': 'email message options'}, + ), + migrations.AlterModelOptions( + name='optionalmachine', + options={'permissions': (('view_optionalmachine', 'Can view the machine options'),), 'verbose_name': 'machine options'}, + ), + migrations.AlterModelOptions( + name='optionaltopologie', + options={'permissions': (('view_optionaltopologie', 'Can view the topology options'),), 'verbose_name': 'topology options'}, + ), + migrations.AlterModelOptions( + name='optionaluser', + options={'permissions': (('view_optionaluser', 'Can view the user options'),), 'verbose_name': 'user options'}, + ), + migrations.AlterModelOptions( + name='service', + options={'permissions': (('view_service', 'Can view the service options'),), 'verbose_name': 'service', 'verbose_name_plural': 'services'}, + ), + migrations.AlterField( + model_name='assooption', + name='adresse1', + field=models.CharField(default='Threadneedle Street', max_length=128), + ), + migrations.AlterField( + model_name='assooption', + name='adresse2', + field=models.CharField(default='London EC2R 8AH', max_length=128), + ), + migrations.AlterField( + model_name='assooption', + name='name', + field=models.CharField(default='Networking organisation school Something', max_length=256), + ), + migrations.AlterField( + model_name='assooption', + name='pseudo', + field=models.CharField(default='Organisation', max_length=32), + ), + migrations.AlterField( + model_name='generaloption', + name='general_message_en', + field=models.TextField(blank=True, default='', help_text='General message displayed on the English version of the website (e.g. in case of maintenance)'), + ), + migrations.AlterField( + model_name='generaloption', + name='general_message_fr', + field=models.TextField(blank=True, default='', help_text='General message displayed on the French version of the website (e.g. in case of maintenance)'), + ), + migrations.AlterField( + model_name='homeoption', + name='facebook_url', + field=models.URLField(blank=True, null=True), + ), + migrations.AlterField( + model_name='homeoption', + name='twitter_account_name', + field=models.CharField(blank=True, max_length=32, null=True), + ), + migrations.AlterField( + model_name='homeoption', + name='twitter_url', + field=models.URLField(blank=True, null=True), + ), + migrations.AlterField( + model_name='mailcontact', + name='address', + field=models.EmailField(default='contact@example.org', help_text='Contact email address', max_length=254), + ), + migrations.AlterField( + model_name='mailcontact', + name='commentary', + field=models.CharField(blank=True, help_text='Description of the associated email address.', max_length=256, null=True), + ), + migrations.AlterField( + model_name='optionalmachine', + name='create_machine', + field=models.BooleanField(default=True), + ), + migrations.AlterField( + model_name='optionalmachine', + name='ipv6_mode', + field=models.CharField(choices=[('SLAAC', 'Autoconfiguration by RA'), ('DHCPV6', 'IP addresses assigning by DHCPv6'), ('DISABLED', 'Disabled')], default='DISABLED', max_length=32), + ), + migrations.AlterField( + model_name='optionaltopologie', + name='radius_general_policy', + field=models.CharField(choices=[('MACHINE', "On the IP range's VLAN of the machine"), ('DEFINED', "Preset in 'VLAN for machines accepted by RADIUS'")], default='DEFINED', max_length=32), + ), + migrations.AlterField( + model_name='optionaluser', + name='all_can_create_adherent', + field=models.BooleanField(default=False, help_text='Users can create a member'), + ), + migrations.AlterField( + model_name='optionaluser', + name='all_can_create_club', + field=models.BooleanField(default=False, help_text='Users can create a club'), + ), + migrations.AlterField( + model_name='optionaluser', + name='max_email_address', + field=models.IntegerField(default=15, help_text='Maximum number of local email addresses for a standard user'), + ), + migrations.AlterField( + model_name='optionaluser', + name='self_adhesion', + field=models.BooleanField(default=False, help_text='A new user can create their account on Re2o'), + ), + migrations.AlterField( + model_name='optionaluser', + name='self_change_shell', + field=models.BooleanField(default=False, help_text='Users can edit their shell'), + ), + ] diff --git a/preferences/models.py b/preferences/models.py index 0ebb2fec..3199dd6c 100644 --- a/preferences/models.py +++ b/preferences/models.py @@ -63,21 +63,20 @@ class PreferencesModel(models.Model): class OptionalUser(AclMixin, PreferencesModel): """Options pour l'user : obligation ou nom du telephone, activation ou non du solde, autorisation du negatif, fingerprint etc""" - PRETTY_NAME = "Options utilisateur" is_tel_mandatory = models.BooleanField(default=True) gpg_fingerprint = models.BooleanField(default=True) all_can_create_club = models.BooleanField( default=False, - help_text="Les users peuvent créer un club" + help_text=_("Users can create a club") ) all_can_create_adherent = models.BooleanField( default=False, - help_text="Les users peuvent créer d'autres adhérents", + help_text=_("Users can create a member"), ) self_adhesion = models.BooleanField( default=False, - help_text="Un nouvel utilisateur peut se créer son compte sur re2o" + help_text=_("A new user can create their account on Re2o") ) shell_default = models.OneToOneField( 'users.ListShell', @@ -87,33 +86,35 @@ class OptionalUser(AclMixin, PreferencesModel): ) self_change_shell = models.BooleanField( default=False, - help_text="Users can change their shell" + help_text=_("Users can edit their shell") ) local_email_accounts_enabled = models.BooleanField( default=False, - help_text="Enable local email accounts for users" + help_text=_("Enable local email accounts for users") ) local_email_domain = models.CharField( - max_length = 32, - default = "@example.org", - help_text="Domain to use for local email accounts", + max_length=32, + default="@example.org", + help_text=_("Domain to use for local email accounts") ) max_email_address = models.IntegerField( - default = 15, - help_text = "Maximum number of local email address for a standard user" + default=15, + help_text=_("Maximum number of local email addresses for a standard" + " user") ) class Meta: permissions = ( - ("view_optionaluser", "Peut voir les options de l'user"), + ("view_optionaluser", _("Can view the user options")), ) + verbose_name = _("user options") def clean(self): """Clean model: Check the mail_extension """ if self.local_email_domain[0] != "@": - raise ValidationError("Mail domain must begin with @") + raise ValidationError(_("Email domain must begin with @")) @receiver(post_save, sender=OptionalUser) @@ -126,15 +127,14 @@ def optionaluser_post_save(**kwargs): class OptionalMachine(AclMixin, PreferencesModel): """Options pour les machines : maximum de machines ou d'alias par user sans droit, activation de l'ipv6""" - PRETTY_NAME = "Options machines" SLAAC = 'SLAAC' DHCPV6 = 'DHCPV6' DISABLED = 'DISABLED' CHOICE_IPV6 = ( - (SLAAC, 'Autoconfiguration par RA'), - (DHCPV6, 'Attribution des ip par dhcpv6'), - (DISABLED, 'Désactivé'), + (SLAAC, _("Autoconfiguration by RA")), + (DHCPV6, _("IP addresses assigning by DHCPv6")), + (DISABLED, _("Disabled")), ) password_machine = models.BooleanField(default=False) @@ -146,8 +146,7 @@ class OptionalMachine(AclMixin, PreferencesModel): default='DISABLED' ) create_machine = models.BooleanField( - default=True, - help_text="Permet à l'user de créer une machine" + default=True ) @cached_property @@ -157,8 +156,9 @@ class OptionalMachine(AclMixin, PreferencesModel): class Meta: permissions = ( - ("view_optionalmachine", "Peut voir les options de machine"), + ("view_optionalmachine", _("Can view the machine options")), ) + verbose_name = _("machine options") @receiver(post_save, sender=OptionalMachine) @@ -174,13 +174,11 @@ def optionalmachine_post_save(**kwargs): class OptionalTopologie(AclMixin, PreferencesModel): """Reglages pour la topologie : mode d'accès radius, vlan où placer les machines en accept ou reject""" - PRETTY_NAME = "Options topologie" MACHINE = 'MACHINE' DEFINED = 'DEFINED' CHOICE_RADIUS = ( - (MACHINE, 'Sur le vlan de la plage ip machine'), - (DEFINED, 'Prédéfini dans "Vlan où placer les machines\ - après acceptation RADIUS"'), + (MACHINE, _("On the IP range's VLAN of the machine")), + (DEFINED, _("Preset in 'VLAN for machines accepted by RADIUS'")), ) radius_general_policy = models.CharField( @@ -205,8 +203,9 @@ class OptionalTopologie(AclMixin, PreferencesModel): class Meta: permissions = ( - ("view_optionaltopologie", "Peut voir les options de topologie"), + ("view_optionaltopologie", _("Can view the topology options")), ) + verbose_name = _("topology options") @receiver(post_save, sender=OptionalTopologie) @@ -219,17 +218,18 @@ def optionaltopologie_post_save(**kwargs): class GeneralOption(AclMixin, PreferencesModel): """Options générales : nombre de resultats par page, nom du site, temps où les liens sont valides""" - PRETTY_NAME = "Options générales" general_message_fr = models.TextField( default="", blank=True, - help_text="Message général affiché sur le site (maintenance, etc)" + help_text=_("General message displayed on the French version of the" + " website (e.g. in case of maintenance)") ) general_message_en = models.TextField( default="", blank=True, - help_text="General message displayed on the English version of the website." + help_text=_("General message displayed on the English version of the" + " website (e.g. in case of maintenance)") ) search_display_page = models.IntegerField(default=15) pagination_number = models.IntegerField(default=25) @@ -250,8 +250,9 @@ class GeneralOption(AclMixin, PreferencesModel): class Meta: permissions = ( - ("view_generaloption", "Peut voir les options générales"), + ("view_generaloption", _("Can view the general options")), ) + verbose_name = _("general options") @receiver(post_save, sender=GeneralOption) @@ -271,8 +272,10 @@ class Service(AclMixin, models.Model): class Meta: permissions = ( - ("view_service", "Peut voir les options de service"), + ("view_service", _("Can view the service options")), ) + verbose_name = _("service") + verbose_name_plural =_("services") def __str__(self): return str(self.name) @@ -282,14 +285,14 @@ class MailContact(AclMixin, models.Model): address = models.EmailField( default = "contact@example.org", - help_text = _("Contact email adress") + help_text = _("Contact email address") ) commentary = models.CharField( blank = True, null = True, help_text = _( - "Description of the associated email adress."), + "Description of the associated email address."), max_length = 256 ) @@ -299,8 +302,10 @@ class MailContact(AclMixin, models.Model): class Meta: permissions = ( - ("view_mailcontact", _("Can see contact email")), + ("view_mailcontact", _("Can view a contact email address object")), ) + verbose_name = _("contact email address") + verbose_name_plural = _("contact email addresses") def __str__(self): return(self.address) @@ -308,18 +313,17 @@ class MailContact(AclMixin, models.Model): class AssoOption(AclMixin, PreferencesModel): """Options générales de l'asso : siret, addresse, nom, etc""" - PRETTY_NAME = "Options de l'association" name = models.CharField( - default="Association réseau école machin", + default=_("Networking organisation school Something"), max_length=256 ) siret = models.CharField(default="00000000000000", max_length=32) - adresse1 = models.CharField(default="1 Rue de exemple", max_length=128) - adresse2 = models.CharField(default="94230 Cachan", max_length=128) + adresse1 = models.CharField(default=_("Threadneedle Street"), max_length=128) + adresse2 = models.CharField(default=_("London EC2R 8AH"), max_length=128) contact = models.EmailField(default="contact@example.org") telephone = models.CharField(max_length=15, default="0000000000") - pseudo = models.CharField(default="Asso", max_length=32) + pseudo = models.CharField(default=_("Organisation"), max_length=32) utilisateur_asso = models.OneToOneField( 'users.User', on_delete=models.PROTECT, @@ -333,8 +337,9 @@ class AssoOption(AclMixin, PreferencesModel): class Meta: permissions = ( - ("view_assooption", "Peut voir les options de l'asso"), + ("view_assooption", _("Can view the organisation options")), ) + verbose_name = _("organisation options") @receiver(post_save, sender=AssoOption) @@ -346,29 +351,26 @@ def assooption_post_save(**kwargs): class HomeOption(AclMixin, PreferencesModel): """Settings of the home page (facebook/twitter etc)""" - PRETTY_NAME = "Options de la page d'accueil" facebook_url = models.URLField( null=True, - blank=True, - help_text="Url du compte facebook" + blank=True ) twitter_url = models.URLField( null=True, - blank=True, - help_text="Url du compte twitter" + blank=True ) twitter_account_name = models.CharField( max_length=32, null=True, - blank=True, - help_text="Nom du compte à afficher" + blank=True ) class Meta: permissions = ( - ("view_homeoption", "Peut voir les options de l'accueil"), + ("view_homeoption", _("Can view the homepage options")), ) + verbose_name = _("homepage options") @receiver(post_save, sender=HomeOption) @@ -380,12 +382,14 @@ def homeoption_post_save(**kwargs): class MailMessageOption(AclMixin, models.Model): """Reglages, mail de bienvenue et autre""" - PRETTY_NAME = "Options de corps de mail" welcome_mail_fr = models.TextField(default="") welcome_mail_en = models.TextField(default="") class Meta: permissions = ( - ("view_mailmessageoption", "Peut voir les options de mail"), + ("view_mailmessageoption", _("Can view the email message" + " options")), ) + verbose_name = _("email message options") + diff --git a/preferences/templates/preferences/aff_mailcontact.html b/preferences/templates/preferences/aff_mailcontact.html index a87e03bb..98ee8cdc 100644 --- a/preferences/templates/preferences/aff_mailcontact.html +++ b/preferences/templates/preferences/aff_mailcontact.html @@ -24,24 +24,26 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load i18n %} {% load acl %} {% load logs_extra %} - - - - - - - - - {% for mailcontact in mailcontact_list %} + +
    {% trans "Adress" %}{% trans "Remark" %}
    + + + + + + + + {% for mailcontact in mailcontact_list %} - {% endfor %} -
    {% trans "Address" %}{% trans "Comment" %}
    {{ mailcontact.address }} {{ mailcontact.commentary }} - {% can_edit mailcontact %} - {% include 'buttons/edit.html' with href='preferences:edit-mailcontact' id=mailcontact.id %} - {% acl_end %} - {% history_button mailcontact %} + {% can_edit mailcontact %} + {% include 'buttons/edit.html' with href='preferences:edit-mailcontact' id=mailcontact.id %} + {% acl_end %} + {% history_button mailcontact %}
    + {% endfor %} + + diff --git a/preferences/templates/preferences/aff_service.html b/preferences/templates/preferences/aff_service.html index 89cfc641..c08e14e0 100644 --- a/preferences/templates/preferences/aff_service.html +++ b/preferences/templates/preferences/aff_service.html @@ -23,30 +23,31 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load acl %} {% load logs_extra %} - - - - - - - - - - - - {% for service in service_list %} +{% load i18n %} + +
    NomUrlDescriptionImage
    + + + + + + + + + + {% for service in service_list %} - {% endfor %} -
    {% trans "Name" %}{% trans "URL" %}{% trans "Description" %}{% trans "Image" %}
    {{ service.name }} {{ service.url }} {{ service.description }} {{ service.image }} - {% can_edit service%} - {% include 'buttons/edit.html' with href='preferences:edit-service' id=service.id %} - {% acl_end %} - {% history_button service %} + {% can_edit service%} + {% include 'buttons/edit.html' with href='preferences:edit-service' id=service.id %} + {% acl_end %} + {% history_button service %}
    + {% endfor %} + diff --git a/preferences/templates/preferences/display_preferences.html b/preferences/templates/preferences/display_preferences.html index a7ada02b..6a499969 100644 --- a/preferences/templates/preferences/display_preferences.html +++ b/preferences/templates/preferences/display_preferences.html @@ -28,223 +28,215 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load design %} {% load i18n %} -{% block title %}Création et modification des préférences{% endblock %} +{% block title %}{% trans "Preferences" %}{% endblock %} {% block content %} -

    Préférences utilisateur

    - - Editer - - -
    Généralités
    - - - - - - - - - - - - - - - - - - - - - - - - -
    Téléphone obligatoirement requis{{ useroptions.is_tel_mandatory|tick }}Auto inscription{{ useroptions.self_adhesion|tick }}
    Shell par défaut des utilisateurs{{ useroptions.shell_default }}Les utilisateurs peuvent changer leur shell{{ useroptions.self_change_shell|tick }}
    Creations d'adhérents par tous{{ useroptions.all_can_create_adherent|tick }}Creations de clubs par tous{{ useroptions.all_can_create_club|tick }}
    Champ gpg fingerprint{{ useroptions.gpg_fingerprint|tick }}
    - -
    Comptes mails
    - - - - - - - - - - - -
    Gestion des comptes mails{{ useroptions.local_email_accounts_enabled | tick }}Extension mail interne{{ useroptions.local_email_domain }}
    Nombre d'alias mail max{{ useroptions.max_email_address }}
    - - -

    Préférences machines

    - - Editer - - - - - - - - - - - - - - - - - - -
    Mot de passe par machine{{ machineoptions.password_machine|tick }}Machines/interfaces autorisées par utilisateurs{{ machineoptions.max_lambdauser_interfaces }}
    Alias dns autorisé par utilisateur{{ machineoptions.max_lambdauser_aliases }}Support de l'ipv6{{ machineoptions.ipv6_mode }}
    Creation de machines{{ machineoptions.create_machine|tick }}
    - - -

    Préférences topologie

    - - Editer - - - - - - - - - - - - - - -
    Politique générale de placement de vlan{{ topologieoptions.radius_general_policy }} - Ce réglage défini la politique vlan après acceptation radius : - soit sur le vlan de la plage d'ip de la machine, soit sur un - vlan prédéfini dans "Vlan où placer les machines après acceptation - RADIUS" -
    Vlan où placer les machines après acceptation RADIUS{{ topologieoptions.vlan_decision_ok }}Vlan où placer les machines après rejet RADIUS{{ topologieoptions.vlan_decision_nok }}
    - - -

    Préférences generales

    - - Editer - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Nom du site web{{ generaloptions.site_name }}Adresse mail d'expedition automatique{{ generaloptions.email_from }}
    Affichage de résultats dans le champ de recherche{{ generaloptions.search_display_page }}Nombre d'items affichés en liste (taille normale){{ generaloptions.pagination_number }}
    Nombre d'items affichés en liste (taille élevée){{ generaloptions.pagination_large_number }}Temps avant expiration du lien de reinitialisation de mot de passe (en heures){{ generaloptions.req_expire_hrs }}
    Message global affiché sur le site{{ generaloptions.general_message }}Résumé des CGU{{ generaloptions.GTU_sum_up }}
    CGU{{generaloptions.GTU}} -
    - - -

    Données de l'association

    - - Editer - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Nom{{ assooptions.name }}SIRET{{ assooptions.siret }}
    Adresse{{ assooptions.adresse1 }}
    - {{ assooptions.adresse2 }}
    Contact mail{{ assooptions.contact }}
    Telephone{{ assooptions.telephone }}Pseudo d'usage{{ assooptions.pseudo }}
    Objet utilisateur de l'association{{ assooptions.utilisateur_asso }}Description de l'association{{ assooptions.description | safe }}
    - - -

    Messages personalisé dans les mails

    - - Editer - - - - - - - - - - -
    Mail de bienvenue (Français){{ mailmessageoptions.welcome_mail_fr | safe }}
    Mail de bienvenue (Anglais){{ mailmessageoptions.welcome_mail_en | safe }}
    - - -

    Liste des services et préférences page d'accueil

    -{% can_create preferences.Service%} - - Ajouter un service - -{% acl_end %} - - Supprimer un ou plusieurs service - -{% include "preferences/aff_service.html" with service_list=service_list %} - - - Editer - - -

    {% trans "Contact email adresses list" %}

    - {% can_create preferences.MailContact%} - {% trans "Add an adress" %} - {% acl_end %} - {% trans "Delete one or multiple adresses" %} - {% include "preferences/aff_mailcontact.html" with mailcontact_list=mailcontact_list %} -

    - - - - - - - - - - - - -
    Url du compte twitter{{ homeoptions.twitter_url }}Nom utilisé pour afficher le compte{{ homeoptions.twitter_account_name }}
    Url du compte facebook{{ homeoptions.facebook_url }}
    +

    {% trans "User preferences" %}

    + + + {% trans "Edit" %} + + + + + + + + + + + + + + + + + + + + + + + + +
    {% trans "Telephone number required" %}{{ useroptions.is_tel_mandatory|tick }}{% trans "Self registration" %}{{ useroptions.self_adhesion|tick }}
    {% trans "Default shell for users" %}{{ useroptions.shell_default }}{% trans "Users can edit their shell" %}{{ useroptions.self_change_shell|tick }}
    {% trans "Creation of members by everyone" %}{{ useroptions.all_can_create_adherent|tick }}{% trans "Creation of clubs by everyone" %}{{ useroptions.all_can_create_club|tick }}
    {% trans "GPG fingerprint field" %}{{ useroptions.gpg_fingerprint|tick }}
    +
    {% trans "Email accounts preferences" %} + + + + + + + + + + + +
    {% trans "Local email accounts enabled" %}{{ useroptions.local_email_accounts_enabled|tick }}{% trans "Local email domain" %}{{ useroptions.local_email_domain }}
    {% trans "Maximum number of email aliases allowed" %}{{ useroptions.max_email_address }}
    +

    {% trans "Machines preferences" %}

    + + + {% trans "Edit" %} + +

    +

    + + + + + + + + + + + + + + + + + +
    {% trans "Password per machine" %}{{ machineoptions.password_machine|tick }}{% trans "Maximum number of interfaces allowed for a standard user" %}{{ machineoptions.max_lambdauser_interfaces }}
    {% trans "Maximum number of DNS aliases allowed for a standard user" %}{{ machineoptions.max_lambdauser_aliases }}{% trans "IPv6 support" %}{{ machineoptions.ipv6_mode }}
    {% trans "Creation of machines" %}{{ machineoptions.create_machine|tick }}
    +

    {% trans "Topology preferences" %}

    + + + {% trans "Edit" %} + +

    +

    + + + + + + + + + + + + + +
    {% trans "General policy for VLAN setting" %}{{ topologieoptions.radius_general_policy }}{% trans "This setting defines the VLAN policy after acceptance by RADIUS: either on the IP range's VLAN of the machine, or a VLAN preset in 'VLAN for machines accepted by RADIUS'" %}
    {% trans "VLAN for machines accepted by RADIUS" %}{{ topologieoptions.vlan_decision_ok }}{% trans "VLAN for machines rejected by RADIUS" %}{{ topologieoptions.vlan_decision_nok }}
    +

    {% trans "General preferences" %}

    + + + {% trans "Edit" %} + +

    +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {% trans "Website name" %}{{ generaloptions.site_name }}{% trans "Email address for automatic emailing" %}{{ generaloptions.email_from }}
    {% trans "Number of results displayed when searching" %}{{ generaloptions.search_display_page }}{% trans "Number of items per page (standard size)" %}{{ generaloptions.pagination_number }}
    {% trans "Number of items per page (large size)" %}{{ generaloptions.pagination_large_number }}{% trans "Time before expiration of the reset password link (in hours)" %}{{ generaloptions.req_expire_hrs }}
    {% trans "General message displayed on the website" %}{{ generaloptions.general_message }}{% trans "Summary of the General Terms of Use" %}{{ generaloptions.GTU_sum_up }}
    {% trans "General Terms of Use" %}{{ generaloptions.GTU }} +
    +

    {% trans "Information about the organisation" %}

    + + + {% trans "Edit" %} + +

    +

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    {% trans "Name" %}{{ assooptions.name }}{% trans "SIRET number" %}{{ assooptions.siret }}
    {% trans "Address" %}{{ assooptions.adresse1 }}
    + {{ assooptions.adresse2 }} +
    {% trans "Contact email address" %}{{ assooptions.contact }}
    {% trans "Telephone number" %}{{ assooptions.telephone }}{% trans "Usual name" %}{{ assooptions.pseudo }}
    {% trans "User object of the organisation" %}{{ assooptions.utilisateur_asso }}{% trans "Description of the organisation" %}{{ assooptions.description|safe }}
    +

    {% trans "Custom email message" %}

    + + + {% trans "Edit" %} + +

    +

    + + + + + + + + + +
    {% trans "Welcome email (in French)" %}{{ mailmessageoptions.welcome_mail_fr|safe }}
    {% trans "Welcome email (in English)" %}{{ mailmessageoptions.welcome_mail_en|safe }}
    +

    {% trans "List of services and homepage preferences" %}

    + {% can_create preferences.Service%} + {% trans " Add a service" %} + {% acl_end %} + {% trans " Delete one or several services" %} + {% include "preferences/aff_service.html" with service_list=service_list %} + + + {% trans "Edit" %} + +

    {% trans "List of contact email addresses" %}

    + {% can_create preferences.MailContact %} + {% trans "Add an address" %} + {% acl_end %} + {% trans "Delete one or several addresses" %} + {% include "preferences/aff_mailcontact.html" with mailcontact_list=mailcontact_list %} +

    +

    + + + + + + + + + + + +
    {% trans "Twitter account URL" %}{{ homeoptions.twitter_url }}{% trans "Twitter account name" %}{{ homeoptions.twitter_account_name }}
    {% trans "Facebook account URL" %}{{ homeoptions.facebook_url }}
    {% endblock %} + diff --git a/preferences/templates/preferences/edit_preferences.html b/preferences/templates/preferences/edit_preferences.html index 055ac7e8..356f2362 100644 --- a/preferences/templates/preferences/edit_preferences.html +++ b/preferences/templates/preferences/edit_preferences.html @@ -25,20 +25,23 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} {% load massive_bootstrap_form %} +{% load i18n %} -{% block title %}Création et modification des préférences{% endblock %} +{% block title %}{% trans "Preferences" %}{% endblock %} {% block content %} {% bootstrap_form_errors options %} -

    Edition des préférences

    +

    {% trans "Editing of preferences" %}

    -{% csrf_token %} -{% massive_bootstrap_form options 'utilisateur_asso' %} -{% bootstrap_button "Modifier" button_type="submit" icon="star" %} + {% csrf_token %} + {% massive_bootstrap_form options 'utilisateur_asso' %} + {% trans "Edit" as tr_edit %} + {% bootstrap_button tr_edit button_type="submit" icon="star" %} -
    -
    -
    +
    +
    +
    {% endblock %} + diff --git a/preferences/templates/preferences/preferences.html b/preferences/templates/preferences/preferences.html index e8972d8d..e4ad4ba2 100644 --- a/preferences/templates/preferences/preferences.html +++ b/preferences/templates/preferences/preferences.html @@ -24,8 +24,9 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load bootstrap3 %} +{% load i18n %} -{% block title %}Création et modification des preferences{% endblock %} +{% block title %}{% trans "Preferences" %}{% endblock %} {% block content %} {% if preferenceform %} @@ -44,3 +45,4 @@ with this program; if not, write to the Free Software Foundation, Inc.,

    {% endblock %} + diff --git a/preferences/views.py b/preferences/views.py index 3c0c4879..559cdfef 100644 --- a/preferences/views.py +++ b/preferences/views.py @@ -95,14 +95,14 @@ def edit_options(request, section): model = getattr(models, section, None) form_instance = getattr(forms, 'Edit' + section + 'Form', None) if not (model or form_instance): - messages.error(request, "Objet inconnu") + messages.error(request, _("Unknown object")) return redirect(reverse('preferences:display-options')) options_instance, _created = model.objects.get_or_create() can, msg = options_instance.can_edit(request.user) if not can: - messages.error(request, msg or "Vous ne pouvez pas éditer cette\ - option.") + messages.error(request, msg or _("You don't have the right to edit" + " this option.")) return redirect(reverse('index')) options = form_instance( request.POST or None, @@ -114,11 +114,11 @@ def edit_options(request, section): options.save() reversion.set_user(request.user) reversion.set_comment( - "Champs modifié(s) : %s" % ', '.join( + "Field(s) edited: %s" % ', '.join( field for field in options.changed_data ) ) - messages.success(request, "Préférences modifiées") + messages.success(request, _("The preferences were edited.")) return redirect(reverse('preferences:display-options')) return form( {'options': options}, @@ -136,11 +136,11 @@ def add_service(request): with transaction.atomic(), reversion.create_revision(): service.save() reversion.set_user(request.user) - reversion.set_comment("Création") - messages.success(request, "Ce service a été ajouté") + reversion.set_comment("Creation") + messages.success(request, _("The service was added.")) return redirect(reverse('preferences:display-options')) return form( - {'preferenceform': service, 'action_name': 'Ajouter'}, + {'preferenceform': service, 'action_name': _("Add a service")}, 'preferences/preferences.html', request ) @@ -160,14 +160,14 @@ def edit_service(request, service_instance, **_kwargs): service.save() reversion.set_user(request.user) reversion.set_comment( - "Champs modifié(s) : %s" % ', '.join( + "Field(s) edited: %s" % ', '.join( field for field in service.changed_data ) ) - messages.success(request, "Service modifié") + messages.success(request, _("The service was edited.")) return redirect(reverse('preferences:display-options')) return form( - {'preferenceform': service, 'action_name': 'Editer'}, + {'preferenceform': service, 'action_name': _("Edit")}, 'preferences/preferences.html', request ) @@ -185,13 +185,13 @@ def del_service(request, instances): with transaction.atomic(), reversion.create_revision(): services_del.delete() reversion.set_user(request.user) - messages.success(request, "Le service a été supprimé") + messages.success(request, _("The service was deleted.")) except ProtectedError: - messages.error(request, "Erreur le service\ - suivant %s ne peut être supprimé" % services_del) + messages.error(request, _("Error: the service %s can't be" + " deleted.") % services_del) return redirect(reverse('preferences:display-options')) return form( - {'preferenceform': services, 'action_name': 'Supprimer'}, + {'preferenceform': services, 'action_name': _("Delete")}, 'preferences/preferences.html', request ) @@ -207,10 +207,11 @@ def add_mailcontact(request): ) if mailcontact.is_valid(): mailcontact.save() - messages.success(request, _("The adress was created.")) + messages.success(request, _("The contact email address was created.")) return redirect(reverse('preferences:display-options')) return form( - {'preferenceform': mailcontact, 'action_name': 'Ajouter'}, + {'preferenceform': mailcontact, + 'action_name': _("Add a contact email address")}, 'preferences/preferences.html', request ) @@ -227,10 +228,10 @@ def edit_mailcontact(request, mailcontact_instance, **_kwargs): ) if mailcontact.is_valid(): mailcontact.save() - messages.success(request, _("Email adress updated.")) + messages.success(request, _("The contact email address was edited.")) return redirect(reverse('preferences:display-options')) return form( - {'preferenceform': mailcontact, 'action_name': _('Edit')}, + {'preferenceform': mailcontact, 'action_name': _("Edit")}, 'preferences/preferences.html', request ) @@ -248,10 +249,12 @@ def del_mailcontact(request, instances): mailcontacts_dels = mailcontacts.cleaned_data['mailcontacts'] for mailcontacts_del in mailcontacts_dels: mailcontacts_del.delete() - messages.success(request, _("The email adress was deleted.")) + messages.success(request, + _("The contact email adress was deleted.")) return redirect(reverse('preferences:display-options')) return form( - {'preferenceform': mailcontacts, 'action_name': _('Delete')}, + {'preferenceform': mailcontacts, 'action_name': _("Delete")}, 'preferences/preferences.html', request ) + From e27625dd80e652578c794e2b1ded6c9b1e8f79bd Mon Sep 17 00:00:00 2001 From: Laouen Fernet Date: Sun, 5 Aug 2018 18:48:45 +0200 Subject: [PATCH 145/171] Translation of search/ (front) --- search/forms.py | 46 +++---- search/locale/fr/LC_MESSAGES/django.mo | Bin 0 -> 2426 bytes search/locale/fr/LC_MESSAGES/django.po | 163 +++++++++++++++++++++++++ search/templates/search/index.html | 26 ++-- search/templates/search/search.html | 7 +- search/templates/search/sidebar.html | 6 +- 6 files changed, 210 insertions(+), 38 deletions(-) create mode 100644 search/locale/fr/LC_MESSAGES/django.mo create mode 100644 search/locale/fr/LC_MESSAGES/django.po diff --git a/search/forms.py b/search/forms.py index 8cdb1cc1..6065e799 100644 --- a/search/forms.py +++ b/search/forms.py @@ -26,23 +26,24 @@ from __future__ import unicode_literals from django import forms from django.forms import Form +from django.utils.translation import ugettext_lazy as _ from re2o.utils import get_input_formats_help_text CHOICES_USER = ( - ('0', 'Actifs'), - ('1', 'Désactivés'), - ('2', 'Archivés'), + ('0', _("Active")), + ('1', _("Disabled")), + ('2', _("Archived")), ) CHOICES_AFF = ( - ('0', 'Utilisateurs'), - ('1', 'Machines'), - ('2', 'Factures'), - ('3', 'Bannissements'), - ('4', 'Accès à titre gracieux'), - ('5', 'Chambres'), - ('6', 'Ports'), - ('7', 'Switchs'), + ('0', _("Users")), + ('1', _("Machines")), + ('2', _("Invoices")), + ('3', _("Bans")), + ('4', _("Whitelists")), + ('5', _("Rooms")), + ('6', _("Ports")), + ('7', _("Switches")), ) @@ -55,11 +56,11 @@ def initial_choices(choice_set): class SearchForm(Form): """The form for a simple search""" q = forms.CharField( - label='Recherche', + label=_("Search"), help_text=( - 'Utilisez « » et «,» pour spécifier différents mots, «"query"» ' - 'pour une recherche exacte et «\\» pour échapper un caractère.' - ), + _("Use « » and «,» to specify distinct words, «\"query\"» for" + " an exact search and «\\» to escape a character.") + ), max_length=100 ) @@ -67,23 +68,23 @@ class SearchForm(Form): class SearchFormPlus(Form): """The form for an advanced search (with filters)""" q = forms.CharField( - label='Recherche', + label=_("Search"), help_text=( - 'Utilisez « » et «,» pour spécifier différents mots, «"query"» ' - 'pour une recherche exacte et «\\» pour échapper un caractère.' + _("Use « » and «,» to specify distinct words, «\"query\"» for" + " an exact search and «\\» to escape a character.") ), max_length=100, required=False ) u = forms.MultipleChoiceField( - label="Filtre utilisateurs", + label=_("Users filter"), required=False, widget=forms.CheckboxSelectMultiple, choices=CHOICES_USER, initial=initial_choices(CHOICES_USER) ) a = forms.MultipleChoiceField( - label="Filtre affichage", + label=_("Display filter"), required=False, widget=forms.CheckboxSelectMultiple, choices=CHOICES_AFF, @@ -91,11 +92,11 @@ class SearchFormPlus(Form): ) s = forms.DateField( required=False, - label="Date de début", + label=_("Start date"), ) e = forms.DateField( required=False, - label="Date de fin" + label=_("End date") ) def __init__(self, *args, **kwargs): @@ -106,3 +107,4 @@ class SearchFormPlus(Form): self.fields['e'].help_text = get_input_formats_help_text( self.fields['e'].input_formats ) + diff --git a/search/locale/fr/LC_MESSAGES/django.mo b/search/locale/fr/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..94a441044b3a988be0af93847edc5a4a164569be GIT binary patch literal 2426 zcmZ{lJ!~9B6vrnJARGab@DajSM&Zj*vX?t6M%DpQ9LFbF@)65lP()aByR&y=?H4n% zwvPgdh6+j=3WU3yB03s`l)~45l!lTf9SsE)qM(8Q+nw_{wr`}l-_FdN_ujlWZ|=dF z#U%)2#EBH8g>QTm?1b0+HbL^E1wIYl z1}oqONPg#F3VsgKzQ4ee;NKw09mC{R#Ns6QF~&1cN_xM=&lzwBq;vcTlKy)j?Y|G6 z0)Gde0{;Zb-an52x%CrIly=X7Z({vCNb$c3o(F~F7jAyXjqib!p9kO@UUaiKRe!pskVkK6^_B5I88dd4Xup>bAFV!4^1kk_@Hp?>>QlWjxeUvG(M!* zY$@w^;poGT%kJw|n<8zHG`(CVdt5C{b1=zU5n~4$Nf;T=+DXx_1F$VXCo-%WZ-6GD}X$?NW&} zzQ`mO-0unvA*rhj_OvN~hdKLb?=WmpryJ6@-hAM#K~AMfgHKkcDoc{W=(S8o1>TLk zW4x8L!7GcmR=im)g@vGZ6KRKZtv>D5roH+UuTMAXQvP#}7!eylWV0ESGO^@Wr zEm-#E5Tu9UAu3r`Vo3K9*Amr-Oj1b>`Y6t#7_pbCF;T`8=24cILZa0sEj3Y+sP)ng zLc=ISJfPb!oXF|uSI3uccWR;@^juI-$cidv=?2lU? zmh-svqO8X)B9?I*ha0<|#&M2J$lT>cihP`GZWRjM$&NXx@cqFZ!w27Ut0?3hRGX6d zHgwK9S4LUX`=f<`M*`1<%kW5wo8sx%%ULk!w{y#8kMNwW7B0%q+(UvwDc_MS%{5+< zK_5>Et{@(hFdXzXE~?>iY7aaog>;_a1`NF^pBBjrh4iKrB2d>ASq6Q1g6m2T%ixZd Wcv|*FYIynfM-TrjMVN-sVgCaK0i(G9 literal 0 HcmV?d00001 diff --git a/search/locale/fr/LC_MESSAGES/django.po b/search/locale/fr/LC_MESSAGES/django.po new file mode 100644 index 00000000..dd0b63a3 --- /dev/null +++ b/search/locale/fr/LC_MESSAGES/django.po @@ -0,0 +1,163 @@ +# 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 © 2018 Maël Kervella +# +# 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. +msgid "" +msgstr "" +"Project-Id-Version: 2.5\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-08-15 18:15+0200\n" +"PO-Revision-Date: 2018-06-24 20:10+0200\n" +"Last-Translator: Laouen Fernet \n" +"Language-Team: \n" +"Language: fr_FR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: forms.py:33 +msgid "Active" +msgstr "Actifs" + +#: forms.py:34 +msgid "Disabled" +msgstr "Désactivés" + +#: forms.py:35 +msgid "Archived" +msgstr "Archivés" + +#: forms.py:39 +msgid "Users" +msgstr "Utilisateurs" + +#: forms.py:40 +msgid "Machines" +msgstr "Machines" + +#: forms.py:41 +msgid "Invoices" +msgstr "Factures" + +#: forms.py:42 +msgid "Bans" +msgstr "Bannissements" + +#: forms.py:43 +msgid "Whitelists" +msgstr "Accès gracieux" + +#: forms.py:44 +msgid "Rooms" +msgstr "Chambres" + +#: forms.py:45 +msgid "Ports" +msgstr "Ports" + +#: forms.py:46 +msgid "Switches" +msgstr "Commutateurs réseau" + +#: forms.py:59 forms.py:71 templates/search/search.html:29 +#: templates/search/search.html:48 +msgid "Search" +msgstr "Rechercher" + +#: forms.py:61 forms.py:73 +msgid "" +"Use « » and «,» to specify distinct words, «\"query\"» for an exact search " +"and «\\» to escape a character." +msgstr "" +"Utilisez « » et «,» pour spécifier différents mots, «\"query\"» pour une " +"recherche exacte et «\\» pour échapper un caractère." + +#: forms.py:80 +msgid "Users filter" +msgstr "Filtre utilisateurs" + +#: forms.py:87 +msgid "Display filter" +msgstr "Filtre affichage" + +#: forms.py:95 +msgid "Start date" +msgstr "Date de début" + +#: forms.py:99 +msgid "End date" +msgstr "Date de fin" + +#: templates/search/index.html:29 +msgid "Search results" +msgstr "Résultats de la recherche" + +#: templates/search/index.html:33 +msgid "Results among users:" +msgstr "Résultats parmi les utilisateurs :" + +#: templates/search/index.html:37 +msgid "Results among clubs:" +msgstr "Résultats parmi les clubs :" + +#: templates/search/index.html:41 +msgid "Results among machines:" +msgstr "Résultats parmi les machines :" + +#: templates/search/index.html:45 +msgid "Results among invoices:" +msgstr "Résultats parmi les factures :" + +#: templates/search/index.html:49 +msgid "Results among whitelists:" +msgstr "Résultats parmi les accès à titre gracieux :" + +#: templates/search/index.html:53 +msgid "Results among bans:" +msgstr "Résultats parmi les bannissements :" + +#: templates/search/index.html:57 +msgid "Results among rooms:" +msgstr "Résultats parmi les chambres :" + +#: templates/search/index.html:61 +msgid "Results among ports" +msgstr "Résultats parmi les ports :" + +#: templates/search/index.html:65 +msgid "Results among switches" +msgstr "Résultats parmi les commutateurs réseau :" + +#: templates/search/index.html:69 +msgid "No result" +msgstr "Pas de résultat" + +#: templates/search/index.html:71 +#, python-format +msgid "(Only the first %(max_result)s results are displayed in each category)" +msgstr "" +"(Seulement les %(max_result)s premiers résultats sont affichés dans chaque " +"catégorie)" + +#: templates/search/sidebar.html:31 +msgid "Simple search" +msgstr "Recherche simple" + +#: templates/search/sidebar.html:35 +msgid "Advanced search" +msgstr "Recherche avancée" diff --git a/search/templates/search/index.html b/search/templates/search/index.html index 2fad0c89..23b57cce 100644 --- a/search/templates/search/index.html +++ b/search/templates/search/index.html @@ -24,52 +24,54 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load bootstrap3 %} +{% load i18n %} -{% block title %}Résultats de la recherche{% endblock %} +{% block title %}{% trans "Search results" %}{% endblock %} {% block content %} {% if users %} -

    Résultats dans les utilisateurs

    +

    {% trans "Results among users:" %}

    {% include "users/aff_users.html" with users_list=users %} {% endif%} {% if clubs %} -

    Résultats dans les clubs

    +

    {% trans "Results among clubs:" %}

    {% include "users/aff_clubs.html" with clubs_list=clubs %} {% endif%} {% if machines %} -

    Résultats dans les machines :

    +

    {% trans "Results among machines:" %}

    {% include "machines/aff_machines.html" with machines_list=machines %} {% endif %} {% if factures %} -

    Résultats dans les factures :

    +

    {% trans "Results among invoices:" %}

    {% include "cotisations/aff_cotisations.html" with facture_list=factures %} {% endif %} {% if whitelists %} -

    Résultats dans les accès à titre gracieux :

    +

    {% trans "Results among whitelists:" %}

    {% include "users/aff_whitelists.html" with white_list=whitelists %} {% endif %} {% if bans %} -

    Résultats dans les banissements :

    +

    {% trans "Results among bans:" %}

    {% include "users/aff_bans.html" with ban_list=bans %} {% endif %} {% if rooms %} -

    Résultats dans les chambres :

    +

    {% trans "Results among rooms:" %}

    {% include "topologie/aff_chambres.html" with room_list=rooms %} {% endif %} {% if ports %} -

    Résultats dans les ports :

    +

    {% trans "Results among ports" %}

    {% include "topologie/aff_port.html" with port_list=ports %} {% endif %} {% if switches %} -

    Résultats dans les switchs :

    +

    {% trans "Results among switches" %}

    {% include "topologie/aff_switch.html" with switch_list=switches %} {% endif %} {% if not users and not machines and not factures and not whitelists and not bans and not rooms and not ports and not switches %} -

    Aucun résultat

    +

    {% trans "No result" %}

    {% else %} -
    (Seulement les {{ max_result }} premiers résultats sont affichés dans chaque catégorie)
    +
    {% blocktrans %}(Only the first {{ max_result }} results are displayed in each category){% endblocktrans %}
    {% endif %}


    {% endblock %} + diff --git a/search/templates/search/search.html b/search/templates/search/search.html index 7ae5d56d..42012339 100644 --- a/search/templates/search/search.html +++ b/search/templates/search/search.html @@ -24,8 +24,9 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load bootstrap3 %} +{% load i18n %} -{% block title %}Recherche{% endblock %} +{% block title %}{% trans "Search" %}{% endblock %} {% block content %} {% bootstrap_form_errors search_form %} @@ -44,7 +45,8 @@ with this program; if not, write to the Free Software Foundation, Inc., {% if search_form.e %} {% bootstrap_field search_form.e %} {% endif %} - {% bootstrap_button "Search" button_type="submit" icon="search" %} + {% trans "Search" as tr_search %} + {% bootstrap_button tr_search button_type="submit" icon="search" %}

    @@ -52,3 +54,4 @@ with this program; if not, write to the Free Software Foundation, Inc.,

    {% endblock %} + diff --git a/search/templates/search/sidebar.html b/search/templates/search/sidebar.html index 8e29d379..a445ef41 100644 --- a/search/templates/search/sidebar.html +++ b/search/templates/search/sidebar.html @@ -23,14 +23,16 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. {% endcomment %} +{% load i18n %} {% block sidebar %} - Recherche simple + {% trans "Simple search" %} - Recherche avancée + {% trans "Advanced search" %} {% endblock %} + From 2ecf6b86f1d4cf72886b19b0f95ed820f7d189b2 Mon Sep 17 00:00:00 2001 From: Laouen Fernet Date: Sun, 5 Aug 2018 18:48:56 +0200 Subject: [PATCH 146/171] Translation of topologie/ (front) --- topologie/acl.py | 5 +- topologie/forms.py | 4 +- topologie/locale/fr/LC_MESSAGES/django.mo | Bin 0 -> 14067 bytes topologie/locale/fr/LC_MESSAGES/django.po | 842 ++++++++++++++++++ .../migrations/0062_auto_20180815_1918.py | 146 +++ topologie/models.py | 146 +-- topologie/templates/topologie/aff_ap.html | 58 +- .../templates/topologie/aff_building.html | 13 +- .../templates/topologie/aff_chambres.html | 17 +- .../topologie/aff_constructor_switch.html | 13 +- .../templates/topologie/aff_model_switch.html | 18 +- topologie/templates/topologie/aff_port.html | 123 +-- .../templates/topologie/aff_port_profile.html | 25 +- topologie/templates/topologie/aff_stacks.html | 55 +- topologie/templates/topologie/aff_switch.html | 78 +- .../templates/topologie/aff_switch_bay.html | 56 +- topologie/templates/topologie/delete.html | 9 +- .../templates/topologie/edit_stack_sw.html | 48 +- topologie/templates/topologie/index.html | 10 +- topologie/templates/topologie/index_ap.html | 8 +- .../topologie/index_model_switch.html | 12 +- topologie/templates/topologie/index_p.html | 12 +- .../topologie/index_physical_grouping.html | 16 +- .../topologie/index_portprofile.html | 3 +- topologie/templates/topologie/index_room.html | 8 +- topologie/templates/topologie/sidebar.html | 14 +- topologie/templates/topologie/switch.html | 11 +- topologie/templates/topologie/topo.html | 6 +- topologie/templates/topologie/topo_more.html | 13 +- topologie/views.py | 148 +-- 30 files changed, 1507 insertions(+), 410 deletions(-) create mode 100644 topologie/locale/fr/LC_MESSAGES/django.mo create mode 100644 topologie/locale/fr/LC_MESSAGES/django.po create mode 100644 topologie/migrations/0062_auto_20180815_1918.py diff --git a/topologie/acl.py b/topologie/acl.py index ebef17c9..62e51d8a 100644 --- a/topologie/acl.py +++ b/topologie/acl.py @@ -25,6 +25,7 @@ Here are defined some functions to check acl on the application. """ +from django.utils.translation import ugettext as _ def can_view(user): @@ -38,4 +39,6 @@ def can_view(user): viewing is granted and msg is a message (can be None). """ can = user.has_module_perms('topologie') - return can, None if can else "Vous ne pouvez pas voir cette application." + return can, None if can else _("You don't have the right to view this" + " application.") + diff --git a/topologie/forms.py b/topologie/forms.py index 86b5c541..fa089507 100644 --- a/topologie/forms.py +++ b/topologie/forms.py @@ -176,8 +176,8 @@ class EditRoomForm(FormRevMixin, ModelForm): class CreatePortsForm(forms.Form): """Permet de créer une liste de ports pour un switch.""" - begin = forms.IntegerField(label="Début :", min_value=0) - end = forms.IntegerField(label="Fin :", min_value=0) + begin = forms.IntegerField(label=_("Start:"), min_value=0) + end = forms.IntegerField(label=_("End:"), min_value=0) class EditModelSwitchForm(FormRevMixin, ModelForm): diff --git a/topologie/locale/fr/LC_MESSAGES/django.mo b/topologie/locale/fr/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..c7ef4c2e7db3ddb76a90e85db64b2c6fa05bc545 GIT binary patch literal 14067 zcmb`Nd5|5&eaBneNZeo!C%`}ul0dtxgd~*Z(rQh!esCGXE zZNH$t^Fyfb{{-s2x8MPA9!eAsg3`+ZxCWjG_k&ls^sC^RJcn>CycO!Z&q00fWq2BV z6{`LP7)9+o4yxQ4Q2pqGD!&%0T_aH6AA>Z>Tmh-Vl%U@K7}R^WLY2D*s-540(#N9^ z)0n@3vj1~X<$nt02mb~6XWryTc3gl{$WCWM*<}!_y;niCGk~i1TBz^e;L>k#>9<3* ze+H@@k3hBSX}B6b1NGgvq55+GPNMoxfM-JwdT<2ldu6EdyP(S50A;tE9B+Z@*KJVc z?}n=PK`1>v4b_exLACp5P=5D1lzxxEIaTlRP~}f`&*wt<*9B1exDpP+9Z=)zNyq1* z^z=Hs8Xm&K5*R}D=S%Q1_!PVtp1{l8n#*7>tU-O}PRGB7`rf}nwdXB}s?1>+M|L_L z@+os3+ypnlHSkWTa{uo5CRF*ue#@8vcp5w%79d?WH#&Y3>U)pERq&tTI(P_)+?tJ! z+aXn&8{r0cH`F+N-SJpDq598*>*1A9{kn%AikPRN+WivL_kRr+!NV|y^uGd1FBd}T zX&bb5g|ge-P~-n$D1Z1q9EE=im%|enyt4BLpvqqcRc`?0j}<6C+zD0gBT(gThN|zY zF8>bqd@q!r%|Nx|&!PP5NhtsNJE(eIgsSHisPFw6s-15`jgP}UW6qg}E}-nQfgqsq zSb)-J9m-C(K#j}$Ag`M5LR4g4fakzp!L#5QC|P>n1l5ihs(&{^>G@Wuat}ha<0nvh ze+5dv{|D9I6RAZ0e+tz5XF~O}52~K?q29Y3@@Z3pYVQ|a`h$>v<{5tEH?P8F&^sfE zgQHOG*aiFGeNf+d9!jq-L$&`GF8zO?`n&(aB>glfy{>@L<65Zpjza10N~rg*f$HZ+ zq3Zt}lpVeTSHioX?Dm37|2aI6=hvY0{oim6+@IjR9~^?R>w2j6kHJxRgG+xF>N_t( z>E%_Z`WN8L(%T78&nuwpx&}(GTj9a*YPdg~g0geT@v~6<{5F)Id=DM~pM~n*k6r#R zAR)oL38kN-7AJAy6sUeJhss|KJva(6xrre|#oPuDf!~3u_xn(K_y;I`zUg$o@l<#)<<5o|!E@muFoJsj!%+SGJnV&EgUjI$puY1PsD2%ea@GE0pxW(0)pI74 zeb0d^zXrwq4c)k>_qRULFKQ4hr(@;BIati6n+Mdz{jBM zb|8r=e>znCYvFa!hpPYIAWb)i_uzBzJh&d-3l+~^gR9|wbXtCKK2&=@2-W_NLyf=t zp~lfuPifTfTj9|R_TPh(Q1SN>sP_C5)cE*Mcqu%LMsaJlLe={zsPcEfW$+28 za=(OQa3zUq&&?3mF%Lsr*Zdexz_+2sae%RY7k&e(KQBNZ9(gW44X=g#GxzhO_g{pI z;XE?+-dRxkE<*WT1+IZ#fb-$A@NoDNJOaK3W&i($Tc8H5>K}uOKQ}@3?=Gl%ABU=^ z3FSxs2vyH>@JRS`sQTW3^3%7V{7RiW790wXf+xeHVK0<@)VQG^PsNpAT@i(`~a^;wjz%rS0TDktF8Y|Xxjm=KvcJ`ZyqU(dm*AeM1f8D^ttuEny$A_VGD0_b!*@=7$`9mbV z#<}^c^Z|a+-G^`l`LKK556W&|LH+{ShUl6^E zT!`F;Xsld}==wCGSfHzz!2Bs3Ms7hqj%bX00MRA?({&kgInt*GcRc|waQE^zT@~aY zd&j&D*CG9ghg^iLL>3{s4nrpFUGj4zxCiOYrRnwy$Ro%fA#0GMkn<5;2P29xYxThO zNhC(<$o|M~MAwIq0c0WaSwzK3Aqn>9MSbTVJS${qH49A-p6&na835G zBdiyu+Mm2}f7h&(LbVdtqeh`#jb`PPtHq$?(kh-`CjQF zhnTE-I2{{sZ(DBS8}=h{rCP1Yq4hMq%4(WK@^*fvf9RK#X113_u?ehbSCwhjSan{ zb|pTsd1_ZoZ%f`}RBc$l*sMP3Csk70L!3OtwE$xz&9G$|eRxCb)^qVeDZG<}6wjaZ zL*g55QLS|(M%BqiU`N6pvZhzfkvTeGMhCpfh94EV85-WY#o8`^kJq}K)XUQ6`mS1# zzgNj%B8Y+tmK=3qvL?lhD!%JysYm2ZY|Y>+X2DdYJUlx*6X^qpFN~hJ>A-Fl)EK5!RCr zWOa0Wx-BOOH?zO1ck&G?xLVP0(szuQ#SJ!;I~f0^iQm_+&AN z6y}2{sz!0IH)v-RMa7yQ5eo3iIB6|KlPRA%N}6fvQ`DrAvFz?93aPd1?+nY0GM=H? zsJ(1Cthl_S)@n5AR|w~d>x6jP8F|HOU>j{ikhd$SFZF7)n82FYN3l~8oP`GC#x?=p zcO%TT){dNaVVh*+mGFn61|5@OzT#xnW0SElOQK2=3F&UVS|$83)h7M3^`y&>4m)?` zw%NKPH*as7sS25+Qm4$)I;)xOE-D&(JBUi{PMSeUM54%RtIsBt5&a805o_nKCw z@4T;>wsW;~b|#DKw$ZbDq(Pgd&hUZ+Vt z9X{(c-pcLLgt<)7u4+cBt1NNQDSujvRKHF@uIx&dX!uICRxMR0nF-xZIy)xsGHs5r zeo>-Z-gK;D!-{XWYfFY*pZVoB9bC9rGi&`r+FLA1m+Ri*G@Ya+7n>`1xyW=< z_nZm{O5x;`t<3Ht?Q+|%)kTzGbU>`d0TWTgh#IU*Fk% z%lrB$*fYv@Nfox0Th+6CrMGNF|H^ZdawC3R?-`5yO3XT>8ufc4ezg%)yi0jQ40^@_zf9B8XKptPZ&=sr&a&RV`Gf5F*rxQz zYx`OD?5v-|!Y8bp?-i!}h_(5JTgNWxS=}n97V*)Zb(KO@hcW%$>hZ8XpTm&t>^50` zG%DVB^PYNGrrNetYxaDx#Nf<9c5vm(GAB;?IIa#$7OAdFk6l`8*X^*xWkry>* z;=pgTzcKEIfj%%Ev7tvN`P`Pr=yeM1r8dnt&E4!__o1jg3-XGK7#Pjn1QwL$aWvKX zd@)FNteg^UK#vn)pfjdY2?aLq@=884R?PCVRcvd;YRO~!T?^ROHy@}o&{-{n%^CYj zBkUtECD(isqNx~af&x3J3IG{}g^;1SemeBx-zNzO>JJKTVQ zDz;aKaOYSy{cMot5VHf}MSX-%;?!`YQKHjPLwiTm4Dy7aa( zY$&%hYPE=4~g|(f3*|P_YBhavowmD)Zu8-TQKBf*An0(GSYWuNou#7WSdD5kx6CE# zb^}X(RKsx?$ETfkGtDots_T;7gmevT(kZ#T4s$ z+Ui*M&RQLCC|fcfR-ZVVW^#b~lNrJQ@mE>*Kf zy~B8zU=-N1HYw9hFlNwA$By{}Hx?r{Y`Cc?Mkn^XEnqUUW7VCxO>hF>UgvKTNquZ} zbO2dk)R`^K8GG)=ux*XXxJD{LOV)McvU3dXv3U1seMI{~t1ssSX0s*}c8#cmp>J0j zN+RMlXT}L*{bud#wS*F^UuCNc0!*?7-NUSsFJs^CHbY>|JuNj{_TFB%>cvU3HqH4$ zx8bRHnJus4Y<<-&(P>Q+uR9`?GmU7(bq&(|DT>(6!le*3XPPs;`%<)QXD?ScTVZN5 ztf{b34_Pr$qb5RTGQuh0!iMX%V?>!Z7nkjqTa|TbR%fOQ>K!dfUv6vYq(AO+n4F*a zq%3#LSXrdD*O&m(_H%^D5qGl0W1@@v=0p53hF9oj6ya9ZggSU^^m^&y;5wZW>J#aB zcM4_v@~sKxK7A&vXlRFWO<&VzlFM|Ad-K8~oIgE!oK?JJ^OB=Sl}s9&EW4%9MJ%qX zg)o`7=#$R-u)R()*d;{4Qg1ro?~ODsruP}ETWlDUvh;1Fp9B4Ah-qM`4B zm2HPz)9Cc}=3TAo(?zvyvTc2|)FYDVU=p2Gt^WAiSdFtu{=ksD*tT@->e1axwEIu3 zd!aTbvQ1Cj%h&cN8#Cx8&6riM?3-rgYf~K6;ickqeh}ug?LxV{AkkFjp6!-Qs>OW% zv<@3^1&wE(@nGt({(!FhbT>EKjPjAaj#j_5ifXGX?OPH;*!JYUljdbA>7JdcCCOFH ze-B!yv4SRa-l+wPb?jt9N%w9&$=+=`EapC(HaXuN+oCk1tuNHS#~C8Cd=_ABvl`v@ zxUw;h&k9yiWW36O0w<*n4jmLwlfaVBiB`+A_gC+M()iCEvu4*>JxDY)XQ4Lb?4h!} z#qIhnn;Nb6w&uHZx|`j%eeu}Y^GkjPp6fHIl{0(oTkF!-vi@VBclKtg5&Pm^oa#M@ z!h88b_x|kTOKn~0vZ|WpQchLb5+^fx+K5(tgVJed!Thx0+tUz+jf;df$Y$roS{Ee; z#`ZjlKO?rPYb~vk^?2u2&>bcBGlh3OJ8sNAJ7%@I4`;^%bshcbn5dt#oCec#4~V_# z$hW6=t-#9$Gx^U!%*5?S$ZcoG-o<8{DyS^t?EE^r03f6mqbds^LZa0WHL*5mJ3XFb zk!Wl>>z$4Rs%%YWvb*$DxotKz$>eI6OtJ?x>VH5K+-Am+gqAT\n" +"Language-Team: \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: acl.py:42 +msgid "You don't have the right to view this application." +msgstr "Vous n'avez pas le droit de voir cette application." + +#: forms.py:179 +msgid "Start:" +msgstr "Début :" + +#: forms.py:180 +msgid "End:" +msgstr "Fin :" + +#: models.py:69 +msgid "Can view a stack object" +msgstr "Peut voir un objet pile" + +#: models.py:71 +msgid "switches stack" +msgstr "pile de commutateurs réseau" + +#: models.py:72 +msgid "switches stacks" +msgstr "piles de commutateurs réseau" + +#: models.py:87 +msgid "The maximum ID is less than the minimum ID." +msgstr "L'ID maximum est inférieur l'ID minimum." + +#: models.py:100 +msgid "Details about the AP's location" +msgstr "Détails sur l'emplacement du point d'accès sans fil" + +#: models.py:107 +msgid "Can view an access point object" +msgstr "Peut voir un objet point d'accès sans fil" + +#: models.py:109 +msgid "access point" +msgstr "point d'accès sans fil" + +#: models.py:110 +msgid "access points" +msgstr "points d'accès sans fil" + +#: models.py:206 +msgid "Number of ports" +msgstr "Nombre de ports" + +#: models.py:223 templates/topologie/aff_switch.html:48 views.py:803 +msgid "Switch model" +msgstr "Modèle de commutateur réseau" + +#: models.py:235 +msgid "Can view a switch object" +msgstr "Peut voir un objet commutateur réseau" + +#: models.py:237 +msgid "switch" +msgstr "commutateur réseau" + +#: models.py:238 +msgid "switches" +msgstr "commutateurs réseau" + +#: models.py:249 +msgid "The switch ID exceeds the limits allowed by the stack." +msgstr "L'ID du commutateur réseau dépasse les bornes autorisées par la pile." + +#: models.py:254 +msgid "The stack member ID can't be void." +msgstr "L'ID de membre dans la pile ne peut-être vide." + +#: models.py:270 +msgid "The end port is less than the start port." +msgstr "Le port de fin est inférieur au port de début." + +#: models.py:273 +msgid "This switch can't have that many ports." +msgstr "Ce commutateur réseau ne peut pas avoir autant de ports." + +#: models.py:283 +msgid "Creation" +msgstr "Création" + +#: models.py:285 +msgid "Creation of an existing port." +msgstr "Création d'un port existant." + +#: models.py:310 +msgid "Can view a switch model object" +msgstr "Peut voir un objet modèle de commutateur réseau" + +#: models.py:312 +msgid "switch model" +msgstr "modèle de commutateur réseau" + +#: models.py:313 +msgid "switch models" +msgstr "modèles de commutateur réseau" + +#: models.py:326 +msgid "Can view a switch constructor object" +msgstr "Peut voir un objet constructeur de commutateur réseau" + +#: models.py:329 +msgid "switch constructor" +msgstr "constructeur de commutateur réseau" + +#: models.py:352 +msgid "Can view a switch bay object" +msgstr "Peut voir un objet baie de brassage" + +#: models.py:354 +msgid "switch bay" +msgstr "baie de brassage" + +#: models.py:355 +msgid "switch bays" +msgstr "baies de brassage" + +#: models.py:368 +msgid "Can view a building object" +msgstr "Peut voir un objet bâtiment" + +#: models.py:370 +msgid "building" +msgstr "bâtiment" + +#: models.py:371 +msgid "buildings" +msgstr "bâtiments" + +#: models.py:427 +msgid "Port state Active" +msgstr "État du port Actif" + +#: models.py:434 +msgid "Can view a port object" +msgstr "Peut voir un objet port" + +#: models.py:436 +msgid "port" +msgstr "port" + +#: models.py:437 +msgid "ports" +msgstr "ports" + +#: models.py:504 +msgid "The port can't exist, its number is too great." +msgstr "Le port ne peut pas exister, son numéro est trop grand." + +#: models.py:510 +msgid "Room, interface and related port are mutually exclusive." +msgstr "Chambre, interface et port relié sont mutuellement exclusifs." + +#: models.py:513 +msgid "A port can't be related to itself." +msgstr "Un port ne peut être relié à lui-même." + +#: models.py:517 +msgid "" +"The related port is already used, please clear it before creating the " +"relation." +msgstr "" +"Le port relié est déjà utilisé, veuillez le modifier avant de créer la " +"relation." + +#: models.py:538 +msgid "Can view a room object" +msgstr "Peut voir un objet chambre" + +#: models.py:540 +msgid "room" +msgstr "chambre" + +#: models.py:541 +msgid "rooms" +msgstr "chambres" + +#: models.py:575 templates/topologie/aff_port_profile.html:37 +msgid "Name" +msgstr "Nom" + +#: models.py:582 +msgid "Default profile" +msgstr "Profil par défaut" + +#: models.py:590 +msgid "VLAN untagged" +msgstr "VLAN untagged" + +#: models.py:596 +msgid "VLAN(s) tagged" +msgstr "VLAN(s) tagged" + +#: models.py:601 +msgid "Type of RADIUS authentication : inactive, MAC-address or 802.1X" +msgstr "Type d'authentification RADIUS : inactive, MAC-address ou 802.1X" + +#: models.py:603 +msgid "RADIUS type" +msgstr "Type de RADIUS" + +#: models.py:609 +msgid "In case of MAC-authentication : mode COMMON or STRICT on this port" +msgstr "" +"Dans le cas d'authentification par adresse MAC : mode COMMON ou STRICT sur " +"ce port" + +#: models.py:611 +msgid "RADIUS mode" +msgstr "Mode de RADIUS" + +#: models.py:617 +msgid "Port speed limit" +msgstr "Limite de vitesse du port" + +#: models.py:622 +msgid "Limit of MAC-address on this port" +msgstr "Limite de MAC-address sur ce port" + +#: models.py:623 +msgid "MAC limit" +msgstr "Limite MAC" + +#: models.py:627 +msgid "Flow control" +msgstr "Contrôle du flux" + +#: models.py:631 +msgid "Protect against rogue DHCP" +msgstr "Protège contre les DHCP pirates" + +#: models.py:632 +msgid "DHCP snooping" +msgstr "DHCP snooping" + +#: models.py:636 +msgid "Protect against rogue DHCPv6" +msgstr "Protège contre les DHCPv6 pirates" + +#: models.py:637 +msgid "DHCPv6 snooping" +msgstr "DHCPv6 snooping" + +#: models.py:641 +msgid "Check if IP adress is DHCP assigned" +msgstr "Vérifie si l'adresse IP est attribuée par DHCP" + +#: models.py:642 +msgid "ARP protection" +msgstr "Protection ARP" + +#: models.py:646 +msgid "Protect against rogue RA" +msgstr "Protège contre les RA pirates" + +#: models.py:647 +msgid "RA guard" +msgstr "RA guard" + +#: models.py:651 +msgid "Protect against loop" +msgstr "Protège contre un boucle" + +#: models.py:652 +msgid "Loop protection" +msgstr "Protection contre une boucle" + +#: models.py:657 +msgid "Can view a port profile object" +msgstr "Peut voir un objet profil de port" + +#: models.py:659 +msgid "port profile" +msgstr "profil de port" + +#: models.py:660 +msgid "port profiles" +msgstr "profils de port" + +#: templates/topologie/aff_ap.html:36 +msgid "Access point" +msgstr "Point d'accès sans fil" + +#: templates/topologie/aff_ap.html:38 +msgid "MAC address" +msgstr "Adresse MAC" + +#: templates/topologie/aff_ap.html:40 templates/topologie/aff_switch.html:39 +msgid "IPv4 address" +msgstr "Adresse IPv4" + +#: templates/topologie/aff_ap.html:42 templates/topologie/aff_chambres.html:38 +#: templates/topologie/aff_port.html:43 templates/topologie/aff_stacks.html:36 +#: templates/topologie/aff_switch.html:49 +#: templates/topologie/edit_stack_sw.html:34 +msgid "Details" +msgstr "Détails" + +#: templates/topologie/aff_ap.html:43 +msgid "Location" +msgstr "Emplacement" + +#: templates/topologie/aff_ap.html:56 templates/topologie/aff_building.html:46 +#: templates/topologie/aff_chambres.html:48 +#: templates/topologie/aff_constructor_switch.html:46 +#: templates/topologie/aff_model_switch.html:49 +#: templates/topologie/aff_port.html:91 templates/topologie/aff_stacks.html:55 +#: templates/topologie/aff_switch_bay.html:59 +#: templates/topologie/edit_stack_sw.html:44 views.py:357 views.py:411 +#: views.py:722 views.py:781 views.py:836 views.py:891 views.py:950 +#: views.py:1005 +msgid "Edit" +msgstr "Modifier" + +#: templates/topologie/aff_ap.html:62 templates/topologie/aff_building.html:52 +#: templates/topologie/aff_chambres.html:54 +#: templates/topologie/aff_constructor_switch.html:52 +#: templates/topologie/aff_model_switch.html:55 +#: templates/topologie/aff_port.html:97 templates/topologie/aff_stacks.html:61 +#: templates/topologie/aff_switch_bay.html:65 +#: templates/topologie/edit_stack_sw.html:50 +msgid "Delete" +msgstr "Supprimer" + +#: templates/topologie/aff_building.html:36 +#: templates/topologie/aff_switch_bay.html:38 views.py:913 +msgid "Building" +msgstr "Bâtiment" + +#: templates/topologie/aff_chambres.html:36 +#: templates/topologie/aff_port.html:35 views.py:744 +msgid "Room" +msgstr "Chambre" + +#: templates/topologie/aff_constructor_switch.html:36 +#: templates/topologie/aff_model_switch.html:38 views.py:973 +msgid "Switch constructor" +msgstr "Constructeur de commutateur réseau" + +#: templates/topologie/aff_model_switch.html:36 +msgid "Reference" +msgstr "Référence" + +#: templates/topologie/aff_port.html:33 +msgid "Port" +msgstr "Port" + +#: templates/topologie/aff_port.html:37 +msgid "Interface" +msgstr "Interface" + +#: templates/topologie/aff_port.html:39 +msgid "Related port" +msgstr "Port relié" + +#: templates/topologie/aff_port.html:41 +msgid "Port state" +msgstr "État du port" + +#: templates/topologie/aff_port.html:42 views.py:1025 +msgid "Port profile" +msgstr "Profil de port" + +#: templates/topologie/aff_port.html:77 +msgid "Active" +msgstr "Actif" + +#: templates/topologie/aff_port.html:79 +msgid "Disabled" +msgstr "Désactivé" + +#: templates/topologie/aff_port.html:84 +msgid "Default: " +msgstr "Par défaut : " + +#: templates/topologie/aff_port_profile.html:38 +msgid "Default for" +msgstr "Par défaut pour" + +#: templates/topologie/aff_port_profile.html:39 +msgid "VLANs" +msgstr "VLANs" + +#: templates/topologie/aff_port_profile.html:40 +msgid "RADIUS settings" +msgstr "Paramètres RADIUS" + +#: templates/topologie/aff_port_profile.html:41 +msgid "Speed limit" +msgstr "Limite de vitesse" + +#: templates/topologie/aff_port_profile.html:42 +msgid "MAC address limit" +msgstr "Limite d'adresse MAC" + +#: templates/topologie/aff_port_profile.html:43 +msgid "Security" +msgstr "Sécurité" + +#: templates/topologie/aff_port_profile.html:53 +msgid "Untagged: " +msgstr "Untagged :" + +#: templates/topologie/aff_port_profile.html:57 +msgid "Tagged: " +msgstr "Tagged : " + +#: templates/topologie/aff_port_profile.html:61 +msgid "RADIUS type: " +msgstr "Type de RADIUS : " + +#: templates/topologie/aff_port_profile.html:64 +msgid "RADIUS mode: " +msgstr "Mode de RADIUS : " + +#: templates/topologie/aff_stacks.html:32 +#: templates/topologie/aff_switch.html:45 +#: templates/topologie/edit_stack_sw.html:32 +msgid "Stack" +msgstr "Pile" + +#: templates/topologie/aff_stacks.html:34 +msgid "ID" +msgstr "ID" + +#: templates/topologie/aff_stacks.html:37 +msgid "Members" +msgstr "Membres" + +#: templates/topologie/aff_switch.html:37 templates/topologie/topo_more.html:56 +msgid "DNS name" +msgstr "Nom DNS" + +#: templates/topologie/aff_switch.html:41 +#: templates/topologie/aff_switch_bay.html:36 views.py:858 +msgid "Switch bay" +msgstr "Baie de brassage" + +#: templates/topologie/aff_switch.html:43 +msgid "Ports" +msgstr "Ports" + +#: templates/topologie/aff_switch.html:47 +#: templates/topologie/edit_stack_sw.html:33 +msgid "Stack ID" +msgstr "ID de la pile" + +#: templates/topologie/aff_switch.html:76 +msgid "Creation of ports" +msgstr "Création de ports" + +#: templates/topologie/aff_switch_bay.html:40 +msgid "Information" +msgstr "Informations" + +#: templates/topologie/aff_switch_bay.html:41 +msgid "Switches of the bay" +msgstr "Commutateurs réseau de la baie" + +#: templates/topologie/delete.html:29 templates/topologie/index.html:30 +#: templates/topologie/index_ap.html:30 +#: templates/topologie/index_model_switch.html:30 +#: templates/topologie/index_p.html:30 +#: templates/topologie/index_physical_grouping.html:30 +#: templates/topologie/index_portprofile.html:29 +#: templates/topologie/index_room.html:30 templates/topologie/switch.html:30 +#: templates/topologie/topo.html:30 templates/topologie/topo_more.html:30 +msgid "Topology" +msgstr "Topologie" + +#: templates/topologie/delete.html:35 +#, python-format +msgid "" +"Warning: are you sure you want to delete this %(objet_name)s object " +"( %(objet)s )?" +msgstr "" +"Attention : voulez-vous vraiment supprimer cet objet %(objet_name)s " +"( %(objet)s ) ?" + +#: templates/topologie/delete.html:36 +msgid "Confirm" +msgstr "Confirmer" + +#: templates/topologie/index.html:55 +msgid "Topology of the switches" +msgstr "Topologie des commutateurs réseau" + +#: templates/topologie/index.html:66 templates/topologie/sidebar.html:35 +msgid "Switches" +msgstr "Commutateurs réseau" + +#: templates/topologie/index.html:68 +msgid " Add a switch" +msgstr " Ajouter un commutateur réseau" + +#: templates/topologie/index_ap.html:33 templates/topologie/sidebar.html:43 +msgid "Access points" +msgstr "Points d'accès sans fil" + +#: templates/topologie/index_ap.html:35 +msgid " Add an access point" +msgstr " Ajouter un point d'accès sans fil" + +#: templates/topologie/index_model_switch.html:33 +msgid "Switch models" +msgstr "Modèles de commutateur réseau" + +#: templates/topologie/index_model_switch.html:36 +msgid " Add a switch model" +msgstr " Ajouter un modèle de commutateur réseau" + +#: templates/topologie/index_model_switch.html:42 +msgid "Switch constructors" +msgstr "Constructeurs de commutateur réseau" + +#: templates/topologie/index_model_switch.html:45 +msgid " Add a switch constructor" +msgstr " Ajouter un constructeur de commutateur réseau" + +#: templates/topologie/index_p.html:33 +msgid "Switch:" +msgstr "Commutateur réseau :" + +#: templates/topologie/index_p.html:34 +msgid " Edit" +msgstr " Modifier" + +#: templates/topologie/index_p.html:36 +msgid " Add a port" +msgstr " Ajouter un port" + +#: templates/topologie/index_p.html:37 +msgid " Add ports" +msgstr " Ajouter des ports" + +#: templates/topologie/index_physical_grouping.html:33 +msgid "Stacks" +msgstr "Piles" + +#: templates/topologie/index_physical_grouping.html:36 +msgid " Add a stack" +msgstr " Ajouter une pile" + +#: templates/topologie/index_physical_grouping.html:41 +msgid "Switch bays" +msgstr "Baies de brassage" + +#: templates/topologie/index_physical_grouping.html:44 +msgid " Add a switch bay" +msgstr " Ajouter une baie de brassage" + +#: templates/topologie/index_physical_grouping.html:50 +msgid "Buildings" +msgstr "Bâtiments" + +#: templates/topologie/index_physical_grouping.html:53 +msgid " Add a building" +msgstr " Ajouter un bâtiment" + +#: templates/topologie/index_portprofile.html:33 +#: templates/topologie/sidebar.html:39 +msgid "Port profiles" +msgstr "Profils de port" + +#: templates/topologie/index_portprofile.html:35 +msgid " Add a port profile" +msgstr " Ajouter un profil de port" + +#: templates/topologie/index_room.html:33 +msgid "Rooms" +msgstr "Chambres" + +#: templates/topologie/index_room.html:35 +msgid " Add a room" +msgstr " Ajouter une chambre" + +#: templates/topologie/sidebar.html:31 +msgid "Rooms and premises" +msgstr "Chambres et locaux" + +#: templates/topologie/sidebar.html:47 +msgid "Physical grouping" +msgstr "Groupements physiques" + +#: templates/topologie/sidebar.html:51 +msgid "Switch models and constructors" +msgstr "Modèles et constructeurs de commutateur réseau" + +#: templates/topologie/switch.html:39 templates/topologie/topo.html:36 +msgid " Go to the ports list" +msgstr " Aller à la liste des ports" + +#: templates/topologie/switch.html:43 +msgid "Specific settings for the switch" +msgstr "Réglages spécifiques pour le commutateur réseau" + +#: templates/topologie/switch.html:46 views.py:395 views.py:987 +msgid "Create" +msgstr "Créer" + +#: templates/topologie/topo_more.html:48 +#, python-format +msgid "Specific settings for the %(device)s object" +msgstr "Réglages spécifiques pour l'objet %(device)s" + +#: templates/topologie/topo_more.html:52 +#, python-format +msgid "General settings for the machine linked to the %(device)s object" +msgstr "Réglages généraux pour la machine liée à l'objet %(device)s" + +#: templates/topologie/topo_more.html:59 +msgid "Create or edit" +msgstr "Créer ou modifier" + +#: views.py:317 +msgid "Nonexistent switch." +msgstr "Commutateur réseau inexistant." + +#: views.py:325 +msgid "The port was added." +msgstr "Le port a été ajouté." + +#: views.py:327 +msgid "The port already exists." +msgstr "Le port existe déjà." + +#: views.py:333 views.py:705 views.py:760 views.py:819 views.py:874 +#: views.py:929 +msgid "Add" +msgstr "Ajouter" + +#: views.py:348 +msgid "The port was edited." +msgstr "Le port a été modifié." + +#: views.py:371 +msgid "The port was deleted." +msgstr "Le port a été supprimé." + +#: views.py:375 +#, python-format +msgid "The port %s is used by another object, impossible to delete it." +msgstr "Le port %s est utilisé par un autre objet, impossible de le supprimer." + +#: views.py:392 +msgid "The stack was created." +msgstr "La pile a été créée." + +#: views.py:424 +msgid "The stack was deleted." +msgstr "La pile a été supprimée." + +#: views.py:428 +#, python-format +msgid "The stack %s is used by another object, impossible to deleted it." +msgstr "" +"La pile %s est utilisée par un autre objet, impossible de la supprimer." + +#: views.py:470 views.py:611 views.py:666 +msgid "" +"The organisation's user doesn't exist yet, please create or link it in the " +"preferences." +msgstr "" +"L'utilisateur de l'association n'existe pas encore, veuillez le créer ou le " +"relier dans les préférences." + +#: views.py:485 +msgid "The switch was created." +msgstr "Le commutateur réseau a été créé." + +#: views.py:508 +msgid "Nonexistent switch" +msgstr "Commutateur réseau inexistant." + +#: views.py:528 +msgid "The ports were created." +msgstr "Les ports ont été créés." + +#: views.py:572 +msgid "The switch was edited." +msgstr "Le commutateur réseau a été modifié." + +#: views.py:626 +msgid "The access point was created." +msgstr "Le point d'accès sans fil a été créé." + +#: views.py:679 +msgid "The access point was edited." +msgstr "Le point d'accès sans fil a été modifié." + +#: views.py:702 +msgid "The room was created." +msgstr "La chambre a été créée." + +#: views.py:719 +msgid "The room was edited." +msgstr "La chambre a été modifiée." + +#: views.py:735 +msgid "The room was deleted." +msgstr "La chambre a été supprimée." + +#: views.py:739 +#, python-format +msgid "The room %s is used by another object, impossible to deleted it." +msgstr "" +"La chambre %s est utilisée par un autre objet, impossible de la supprimer." + +#: views.py:757 +msgid "The swich model was created." +msgstr "Le modèle de commutateur réseau a été créé." + +#: views.py:778 +msgid "The switch model was edited." +msgstr "Le modèle de commutateur réseau a été modifié." + +#: views.py:794 +msgid "The switch model was deleted." +msgstr "Le modèle de commutateur réseau a été supprimé." + +#: views.py:798 +#, python-format +msgid "The switch model %s is used by another object, impossible to delete it." +msgstr "" +"Le modèle de commutateur réseau %s est utilisé par un autre objet, " +"impossible de le supprimer." + +#: views.py:816 +msgid "The switch bay was created." +msgstr "La baie de brassage a été créée." + +#: views.py:833 +msgid "The switch bay was edited." +msgstr "La baie de brassage a été modifiée." + +#: views.py:849 +msgid "The switch bay was deleted." +msgstr "La baie de brassage a été supprimée." + +#: views.py:853 +#, python-format +msgid "The switch bay %s is used by another object, impossible to delete it." +msgstr "" +"La baie de brassage %s est utilisée par un autre objet, impossible de la " +"supprimer." + +#: views.py:871 +msgid "The building was created." +msgstr "Le bâtiment a été créé." + +#: views.py:888 +msgid "The building was edited." +msgstr "Le bâtiment a été modifié." + +#: views.py:904 +msgid "The building was deleted." +msgstr "Le bâtiment a été supprimé." + +#: views.py:908 +#, python-format +msgid "The building %s is used by another object, impossible to delete it." +msgstr "" +"Le bâtiment %s est utilisé par un autre objet, impossible de le supprimer." + +#: views.py:926 +msgid "The switch constructor was created." +msgstr "Le constructeur de commutateur réseau a été créé." + +#: views.py:947 +msgid "The switch constructor was edited." +msgstr "Le constructeur de commutateur réseau a été modifié." + +#: views.py:963 +msgid "The switch constructor was deleted." +msgstr "Le constructeur de commutateur réseau a été supprimé." + +#: views.py:967 +#, python-format +msgid "" +"The switch constructor %s is used by another object, impossible to delete it." +msgstr "" +"Le constructeur de commutateur réseau %s est utilisé par un autre objet, " +"impossible de le supprimer." + +#: views.py:984 +msgid "The port profile was created." +msgstr "Le profil de port a été créé." + +#: views.py:1002 +msgid "The port profile was edited." +msgstr "Le profil de port a été modifié." + +#: views.py:1019 +msgid "The port profile was deleted." +msgstr "Le profil de port a été supprimé." + +#: views.py:1022 +msgid "Impossible to delete the port profile." +msgstr "Impossible de supprimer le profil de port." + +#: views.py:1142 +msgid "" +"The default Django template isn't used. This can lead to rendering errors. " +"Check the parameters." +msgstr "" +"Le gabarit par défaut de Django n'est pas utilisé. Cela peut entraîner des " +"erreurs de rendu. Vérifiez les paramètres." diff --git a/topologie/migrations/0062_auto_20180815_1918.py b/topologie/migrations/0062_auto_20180815_1918.py new file mode 100644 index 00000000..fc100258 --- /dev/null +++ b/topologie/migrations/0062_auto_20180815_1918.py @@ -0,0 +1,146 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-08-15 17:18 +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='accesspoint', + options={'permissions': (('view_accesspoint', 'Can view an access point object'),), 'verbose_name': 'access point', 'verbose_name_plural': 'access points'}, + ), + migrations.AlterModelOptions( + name='building', + options={'permissions': (('view_building', 'Can view a building object'),), 'verbose_name': 'building', 'verbose_name_plural': 'buildings'}, + ), + migrations.AlterModelOptions( + name='constructorswitch', + options={'permissions': (('view_constructorswitch', 'Can view a switch constructor object'),), 'verbose_name': 'switch constructor', 'verbose_name_plural': 'switch constructors'}, + ), + migrations.AlterModelOptions( + name='modelswitch', + options={'permissions': (('view_modelswitch', 'Can view a switch model object'),), 'verbose_name': 'switch model', 'verbose_name_plural': 'switch models'}, + ), + migrations.AlterModelOptions( + name='port', + options={'permissions': (('view_port', 'Can view a port object'),), 'verbose_name': 'port', 'verbose_name_plural': 'ports'}, + ), + 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.AlterModelOptions( + name='room', + options={'ordering': ['name'], 'permissions': (('view_room', 'Can view a room object'),), 'verbose_name': 'room', 'verbose_name_plural': 'rooms'}, + ), + migrations.AlterModelOptions( + name='stack', + options={'permissions': (('view_stack', 'Can view a stack object'),), 'verbose_name': 'switches stack', 'verbose_name_plural': 'switches stacks'}, + ), + migrations.AlterModelOptions( + name='switch', + options={'permissions': (('view_switch', 'Can view a switch object'),), 'verbose_name': 'switch', 'verbose_name_plural': 'switches'}, + ), + migrations.AlterModelOptions( + name='switchbay', + options={'permissions': (('view_switchbay', 'Can view a switch bay object'),), 'verbose_name': 'switch bay', 'verbose_name_plural': 'switch bays'}, + ), + migrations.AlterField( + model_name='accesspoint', + name='location', + field=models.CharField(blank=True, help_text="Details about the AP's location", max_length=255, null=True), + ), + migrations.AlterField( + model_name='port', + name='state', + field=models.BooleanField(default=True, help_text='Port state Active', verbose_name='Port state Active'), + ), + migrations.AlterField( + model_name='portprofile', + name='arp_protect', + field=models.BooleanField(default=False, help_text='Check if IP adress is DHCP assigned', verbose_name='ARP protection'), + ), + migrations.AlterField( + model_name='portprofile', + name='dhcp_snooping', + field=models.BooleanField(default=False, help_text='Protect against rogue DHCP', verbose_name='DHCP snooping'), + ), + migrations.AlterField( + model_name='portprofile', + name='dhcpv6_snooping', + field=models.BooleanField(default=False, help_text='Protect against rogue DHCPv6', verbose_name='DHCPv6 snooping'), + ), + migrations.AlterField( + model_name='portprofile', + name='flow_control', + field=models.BooleanField(default=False, help_text='Flow control'), + ), + migrations.AlterField( + model_name='portprofile', + name='loop_protect', + field=models.BooleanField(default=False, help_text='Protect against loop', verbose_name='Loop protection'), + ), + migrations.AlterField( + model_name='portprofile', + name='mac_limit', + field=models.IntegerField(blank=True, help_text='Limit of MAC-address on this port', null=True, verbose_name='MAC limit'), + ), + migrations.AlterField( + model_name='portprofile', + name='profil_default', + field=models.CharField(blank=True, choices=[('room', 'room'), ('accespoint', 'accesspoint'), ('uplink', 'uplink'), ('asso_machine', 'asso_machine'), ('nothing', 'nothing')], max_length=32, null=True, unique=True, verbose_name='Default profile'), + ), + migrations.AlterField( + model_name='portprofile', + name='ra_guard', + field=models.BooleanField(default=False, help_text='Protect against rogue RA', verbose_name='RA guard'), + ), + migrations.AlterField( + model_name='portprofile', + name='radius_mode', + field=models.CharField(choices=[('STRICT', 'STRICT'), ('COMMON', 'COMMON')], default='COMMON', help_text='In case of MAC-authentication : mode COMMON or STRICT on this port', 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')], help_text='Type of RADIUS authentication : inactive, MAC-address or 802.1X', max_length=32, verbose_name='RADIUS type'), + ), + migrations.AlterField( + model_name='portprofile', + name='speed', + field=models.CharField(choices=[('10-half', '10-half'), ('100-half', '100-half'), ('10-full', '10-full'), ('100-full', '100-full'), ('1000-full', '1000-full'), ('auto', 'auto'), ('auto-10', 'auto-10'), ('auto-100', 'auto-100')], default='auto', help_text='Port speed limit', max_length=32), + ), + migrations.AlterField( + model_name='switch', + name='model', + field=models.ForeignKey(blank=True, help_text='Switch model', null=True, on_delete=django.db.models.deletion.SET_NULL, to='topologie.ModelSwitch'), + ), + migrations.AlterField( + model_name='switch', + name='number', + field=models.PositiveIntegerField(help_text='Number of ports'), + ), + migrations.AlterField( + model_name='switch', + name='stack_member_id', + field=models.PositiveIntegerField(blank=True, null=True), + ), + migrations.AlterField( + model_name='switch', + name='switchbay', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='topologie.SwitchBay'), + ), + migrations.AlterField( + model_name='switchbay', + name='info', + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/topologie/models.py b/topologie/models.py index 89b3e6a9..b63bece6 100644 --- a/topologie/models.py +++ b/topologie/models.py @@ -57,7 +57,6 @@ class Stack(AclMixin, RevMixin, models.Model): """Un objet stack. Regrouppe des switchs en foreign key ,contient une id de stack, un switch id min et max dans le stack""" - PRETTY_NAME = "Stack de switchs" name = models.CharField(max_length=32, blank=True, null=True) stack_id = models.CharField(max_length=32, unique=True) @@ -67,8 +66,10 @@ class Stack(AclMixin, RevMixin, models.Model): class Meta: permissions = ( - ("view_stack", "Peut voir un objet stack"), + ("view_stack", _("Can view a stack object")), ) + verbose_name = _("switches stack") + verbose_name_plural = _("switches stacks") def __str__(self): return " ".join([self.name, self.stack_id]) @@ -82,8 +83,10 @@ class Stack(AclMixin, RevMixin, models.Model): def clean(self): """ Verification que l'id_max < id_min""" if self.member_id_max < self.member_id_min: - raise ValidationError({'member_id_max': "L'id maximale est\ - inférieure à l'id minimale"}) + raise ValidationError( + {'member_id_max': _("The maximum ID is less than the" + " minimum ID.")} + ) class AccessPoint(AclMixin, Machine): @@ -91,19 +94,20 @@ class AccessPoint(AclMixin, Machine): Definition pour une borne wifi , hérite de machines.interfaces """ - PRETTY_NAME = "Borne WiFi" location = models.CharField( max_length=255, - help_text="Détails sur la localisation de l'AP", + help_text=_("Details about the AP's location"), blank=True, null=True ) class Meta: permissions = ( - ("view_accesspoint", "Peut voir une borne"), + ("view_accesspoint", _("Can view an access point object")), ) + verbose_name = _("access point") + verbose_name_plural = _("access points") def port(self): """Return the queryset of ports for this device""" @@ -197,10 +201,9 @@ class Switch(AclMixin, Machine): Validation au save que l'id du stack est bien dans le range id_min id_max de la stack parente""" - PRETTY_NAME = "Switch / Commutateur" number = models.PositiveIntegerField( - help_text="Nombre de ports" + help_text=_("Number of ports") ) stack = models.ForeignKey( 'topologie.Stack', @@ -210,29 +213,29 @@ class Switch(AclMixin, Machine): ) stack_member_id = models.PositiveIntegerField( blank=True, - null=True, - help_text="Baie de brassage du switch" + null=True ) model = models.ForeignKey( 'topologie.ModelSwitch', blank=True, null=True, on_delete=models.SET_NULL, - help_text="Modèle du switch" + help_text=_("Switch model") ) switchbay = models.ForeignKey( 'topologie.SwitchBay', blank=True, null=True, on_delete=models.SET_NULL, - help_text="Baie de brassage du switch" ) class Meta: unique_together = ('stack', 'stack_member_id') permissions = ( - ("view_switch", "Peut voir un objet switch"), + ("view_switch", _("Can view a switch object")), ) + verbose_name = _("switch") + verbose_name_plural = _("switches") def clean(self): """ Verifie que l'id stack est dans le bon range @@ -243,12 +246,14 @@ class Switch(AclMixin, Machine): if (self.stack_member_id > self.stack.member_id_max) or\ (self.stack_member_id < self.stack.member_id_min): raise ValidationError( - {'stack_member_id': "L'id de ce switch est en\ - dehors des bornes permises pas la stack"} - ) + {'stack_member_id': _("The switch ID exceeds the" + " limits allowed by the stack.")} + ) else: - raise ValidationError({'stack_member_id': "L'id dans la stack\ - ne peut être nul"}) + raise ValidationError( + {'stack_member_id': _("The stack member ID can't be" + " void.")} + ) def create_ports(self, begin, end): """ Crée les ports de begin à end si les valeurs données @@ -262,9 +267,10 @@ class Switch(AclMixin, Machine): s_end = ports.last().get('port') if end < begin: - raise ValidationError("Port de fin inférieur au port de début !") + raise ValidationError(_("The end port is less than the start" + " port.")) if end - begin > self.number: - raise ValidationError("Ce switch ne peut avoir autant de ports.") + raise ValidationError(_("This switch can't have that many ports.")) begin_range = range(begin, s_begin) end_range = range(s_end+1, end+1) for i in itertools.chain(begin_range, end_range): @@ -274,9 +280,9 @@ class Switch(AclMixin, Machine): try: with transaction.atomic(), reversion.create_revision(): port.save() - reversion.set_comment("Création") + reversion.set_comment(_("Creation")) except IntegrityError: - ValidationError("Création d'un port existant.") + ValidationError(_("Creation of an existing port.")) def main_interface(self): """ Returns the 'main' interface of the switch """ @@ -292,7 +298,7 @@ class Switch(AclMixin, Machine): class ModelSwitch(AclMixin, RevMixin, models.Model): """Un modèle (au sens constructeur) de switch""" - PRETTY_NAME = "Modèle de switch" + reference = models.CharField(max_length=255) constructor = models.ForeignKey( 'topologie.ConstructorSwitch', @@ -301,8 +307,10 @@ class ModelSwitch(AclMixin, RevMixin, models.Model): class Meta: permissions = ( - ("view_modelswitch", "Peut voir un objet modelswitch"), + ("view_modelswitch", _("Can view a switch model object")), ) + verbose_name = _("switch model") + verbose_name_plural = _("switch models") def __str__(self): return str(self.constructor) + ' ' + self.reference @@ -310,13 +318,16 @@ class ModelSwitch(AclMixin, RevMixin, models.Model): class ConstructorSwitch(AclMixin, RevMixin, models.Model): """Un constructeur de switch""" - PRETTY_NAME = "Constructeur de switch" + name = models.CharField(max_length=255) class Meta: permissions = ( - ("view_constructorswitch", "Peut voir un objet constructorswitch"), + ("view_constructorswitch", _("Can view a switch constructor" + " object")), ) + verbose_name = _("switch constructor") + verbose_name_plural = ("switch constructors") def __str__(self): return self.name @@ -324,7 +335,7 @@ class ConstructorSwitch(AclMixin, RevMixin, models.Model): class SwitchBay(AclMixin, RevMixin, models.Model): """Une baie de brassage""" - PRETTY_NAME = "Baie de brassage" + name = models.CharField(max_length=255) building = models.ForeignKey( 'Building', @@ -333,14 +344,15 @@ class SwitchBay(AclMixin, RevMixin, models.Model): info = models.CharField( max_length=255, blank=True, - null=True, - help_text="Informations particulières" + null=True ) class Meta: permissions = ( - ("view_switchbay", "Peut voir un objet baie de brassage"), + ("view_switchbay", _("Can view a switch bay object")), ) + verbose_name = _("switch bay") + verbose_name_plural = _("switch bays") def __str__(self): return self.name @@ -348,13 +360,15 @@ class SwitchBay(AclMixin, RevMixin, models.Model): class Building(AclMixin, RevMixin, models.Model): """Un batiment""" - PRETTY_NAME = "Batiment" + name = models.CharField(max_length=255) class Meta: permissions = ( - ("view_building", "Peut voir un objet batiment"), + ("view_building", _("Can view a building object")), ) + verbose_name = _("building") + verbose_name_plural = _("buildings") def __str__(self): return self.name @@ -376,7 +390,6 @@ class Port(AclMixin, RevMixin, models.Model): - vlan_force : override la politique générale de placement vlan, permet de forcer un port sur un vlan particulier. S'additionne à la politique RADIUS""" - PRETTY_NAME = "Port de switch" switch = models.ForeignKey( 'Switch', @@ -411,15 +424,17 @@ class Port(AclMixin, RevMixin, models.Model): state = models.BooleanField( default=True, help_text='Port state Active', - verbose_name=_("Port State Active") + verbose_name=_("Port state Active") ) details = models.CharField(max_length=255, blank=True) class Meta: unique_together = ('switch', 'port') permissions = ( - ("view_port", "Peut voir un objet port"), + ("view_port", _("Can view a port object")), ) + verbose_name = _("port") + verbose_name_plural = _("ports") @cached_property def get_port_profile(self): @@ -486,22 +501,21 @@ class Port(AclMixin, RevMixin, models.Model): if hasattr(self, 'switch'): if self.port > self.switch.number: raise ValidationError( - "Ce port ne peut exister, numero trop élevé" + _("The port can't exist, its number is too great.") ) if (self.room and self.machine_interface or self.room and self.related or self.machine_interface and self.related): raise ValidationError( - "Chambre, interface et related_port sont mutuellement " - "exclusifs" + _("Room, interface and related port are mutually exclusive.") ) if self.related == self: - raise ValidationError("On ne peut relier un port à lui même") + raise ValidationError(_("A port can't be related to itself.")) if self.related and not self.related.related: if self.related.machine_interface or self.related.room: raise ValidationError( - "Le port relié est déjà occupé, veuillez le libérer " - "avant de créer une relation" + _("The related port is already used, please clear it" + " before creating the relation.") ) else: self.make_port_related() @@ -514,7 +528,6 @@ class Port(AclMixin, RevMixin, models.Model): class Room(AclMixin, RevMixin, models.Model): """Une chambre/local contenant une prise murale""" - PRETTY_NAME = "Chambre/ Prise murale" name = models.CharField(max_length=255, unique=True) details = models.CharField(max_length=255, blank=True) @@ -522,8 +535,10 @@ class Room(AclMixin, RevMixin, models.Model): class Meta: ordering = ['name'] permissions = ( - ("view_room", "Peut voir un objet chambre"), + ("view_room", _("Can view a room object")), ) + verbose_name = _("room") + verbose_name_plural = _("rooms") def __str__(self): return self.name @@ -564,7 +579,7 @@ class PortProfile(AclMixin, RevMixin, models.Model): blank=True, null=True, unique=True, - verbose_name=_("profil default") + verbose_name=_("Default profile") ) vlan_untagged = models.ForeignKey( 'machines.Vlan', @@ -583,66 +598,66 @@ class PortProfile(AclMixin, RevMixin, models.Model): radius_type = models.CharField( max_length=32, choices=TYPES, - help_text="Type of radius auth : inactive, mac-address or 802.1X", + help_text=_("Type of RADIUS authentication : inactive, MAC-address or" + " 802.1X"), verbose_name=_("RADIUS type") ) radius_mode = models.CharField( max_length=32, choices=MODES, default='COMMON', - help_text="In case of mac-auth : mode common or strict on this port", + help_text=_("In case of MAC-authentication : mode COMMON or STRICT on" + " this port"), verbose_name=_("RADIUS mode") ) speed = models.CharField( max_length=32, choices=SPEED, default='auto', - help_text='Port speed limit', - verbose_name=_("Speed") + help_text=_("Port speed limit"), ) mac_limit = models.IntegerField( null=True, blank=True, - help_text='Limit of mac-address on this port', - verbose_name=_("Mac limit") + help_text=_("Limit of MAC-address on this port"), + verbose_name=_("MAC limit") ) flow_control = models.BooleanField( default=False, - help_text='Flow control', - verbose_name=_("Flow control") + help_text=_("Flow control"), ) dhcp_snooping = models.BooleanField( default=False, - help_text='Protect against rogue dhcp', - verbose_name=_("Dhcp snooping") + help_text=_("Protect against rogue DHCP"), + verbose_name=_("DHCP snooping") ) dhcpv6_snooping = models.BooleanField( default=False, - help_text='Protect against rogue dhcpv6', - verbose_name=_("Dhcpv6 snooping") + help_text=_("Protect against rogue DHCPv6"), + verbose_name=_("DHCPv6 snooping") ) arp_protect = models.BooleanField( default=False, - help_text='Check if ip is dhcp assigned', - verbose_name=_("Arp protect") + help_text=_("Check if IP adress is DHCP assigned"), + verbose_name=_("ARP protection") ) ra_guard = models.BooleanField( default=False, - help_text='Protect against rogue ra', - verbose_name=_("Ra guard") + help_text=_("Protect against rogue RA"), + verbose_name=_("RA guard") ) loop_protect = models.BooleanField( default=False, - help_text='Protect again loop', - verbose_name=_("Loop Protect") + help_text=_("Protect against loop"), + verbose_name=_("Loop protection") ) class Meta: permissions = ( ("view_port_profile", _("Can view a port profile object")), ) - verbose_name = _("Port profile") - verbose_name_plural = _("Port profiles") + verbose_name = _("port profile") + verbose_name_plural = _("port profiles") security_parameters_fields = [ 'loop_protect', @@ -727,3 +742,4 @@ def switch_post_save(**_kwargs): @receiver(post_delete, sender=Switch) def switch_post_delete(**_kwargs): regen("graph_topo") + diff --git a/topologie/templates/topologie/aff_ap.html b/topologie/templates/topologie/aff_ap.html index 18a419d3..4eadfede 100644 --- a/topologie/templates/topologie/aff_ap.html +++ b/topologie/templates/topologie/aff_ap.html @@ -24,50 +24,52 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load acl %} {% load logs_extra %} +{% load i18n %}
    {% if ap_list.paginator %} {% include "pagination.html" with list=ap_list %} {% endif %} - - - - - - - + {% trans "Access point" as tr_ap %} + + {% trans "MAC address" as tr_mac %} + + {% trans "IPv4 address" as tr_ip %} + + + {% for ap in ap_list %} - - - - - - - - + + + + + + + + {% endfor %}
    {% include "buttons/sort.html" with prefix='ap' col='name' text='Borne' %}{% include "buttons/sort.html" with prefix='ap' col='mac' text='Addresse mac' %}{% include "buttons/sort.html" with prefix='ap' col='ip' text='Ipv4' %}CommentaireLocalisation{% include "buttons/sort.html" with prefix='ap' col='name' text=tr_ap %}{% include "buttons/sort.html" with prefix='ap' col='mac' text=tr_mac %}{% include "buttons/sort.html" with prefix='ap' col='ip' text=tr_ip %}{% trans "Details" %}{% trans "Location" %}
    {{ap.interface_set.first}}{{ap.interface_set.first.mac_address}}{{ap.interface_set.first.ipv4}}{{ap.interface_set.first.details}}{{ap.location}} - {% history_button ap %} - {% can_edit ap %} - - - - {% acl_end %} - {% can_delete ap %} - - - - {% acl_end %} -
    {{ ap.interface_set.first }}{{ ap.interface_set.first.mac_address }}{{ ap.interface_set.first.ipv4 }}{{ ap.interface_set.first.details }}{{ ap.location }} + {% can_edit ap %} + + + + {% acl_end %} + {% history_button ap %} + {% can_delete ap %} + + + + {% acl_end %} +
    - {% if ap_list.paginator %} {% include "pagination.html" with list=ap_list %} {% endif %}
    + diff --git a/topologie/templates/topologie/aff_building.html b/topologie/templates/topologie/aff_building.html index f0e02135..368887f5 100644 --- a/topologie/templates/topologie/aff_building.html +++ b/topologie/templates/topologie/aff_building.html @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load acl %} {% load logs_extra %} +{% load i18n %} {% if building_list.paginator %} {% include "pagination.html" with list=building_list %} @@ -32,22 +33,23 @@ with this program; if not, write to the Free Software Foundation, Inc., - + {% trans "Building" as tr_building %} + {% for building in building_list %} - +
    {% include "buttons/sort.html" with prefix='building' col='name' text='Bâtiment' %}{% include "buttons/sort.html" with prefix='building' col='name' text=tr_building %}
    {{building.name}}{{ building.name }} - {% history_button building %} {% can_edit building %} - + {% acl_end %} + {% history_button building %} {% can_delete building %} - + {% acl_end %} @@ -59,3 +61,4 @@ with this program; if not, write to the Free Software Foundation, Inc., {% if building_list.paginator %} {% include "pagination.html" with list=building_list %} {% endif %} + diff --git a/topologie/templates/topologie/aff_chambres.html b/topologie/templates/topologie/aff_chambres.html index f71d6927..04c010f4 100644 --- a/topologie/templates/topologie/aff_chambres.html +++ b/topologie/templates/topologie/aff_chambres.html @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load acl %} {% load logs_extra %} +{% load i18n %} {% if room_list.paginator %} {% include "pagination.html" with list=room_list %} @@ -32,24 +33,25 @@ with this program; if not, write to the Free Software Foundation, Inc., - - + {% trans "Room" as tr_room %} + + {% for room in room_list %} - - + + {% can_edit_history %}
    {% include "buttons/sort.html" with prefix='room' col='name' text='Chambre' %}Commentaire{% include "buttons/sort.html" with prefix='room' col='name' text=tr_room %}{% trans "Details" %}
    {{room.name}}{{room.details}}{{ room.name }}{{ room.details }} - {% history_button room %} {% can_edit room %} - + {% acl_end %} + {% history_button room %} {% can_delete room %} - + {% acl_end %} @@ -61,3 +63,4 @@ with this program; if not, write to the Free Software Foundation, Inc., {% if room_list.paginator %} {% include "pagination.html" with list=room_list %} {% endif %} + diff --git a/topologie/templates/topologie/aff_constructor_switch.html b/topologie/templates/topologie/aff_constructor_switch.html index 1b9f5d19..6298f73d 100644 --- a/topologie/templates/topologie/aff_constructor_switch.html +++ b/topologie/templates/topologie/aff_constructor_switch.html @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load acl %} {% load logs_extra %} +{% load i18n %} {% if constructor_switch_list.paginator %} {% include "pagination.html" with list=constructor_switch_list %} @@ -32,22 +33,23 @@ with this program; if not, write to the Free Software Foundation, Inc., - + {% trans "Switch constructor" as tr_constructor %} + {% for constructor_switch in constructor_switch_list %} - +
    {% include "buttons/sort.html" with prefix='constructor-switch' col='name' text='Constructeur' %}{% include "buttons/sort.html" with prefix='constructor-switch' col='name' text=tr_constructor %}
    {{constructor_switch}}{{ constructor_switch }} - {% history_button constructor_switch %} {% can_edit constructor_switch %} - + {% acl_end %} + {% history_button constructor_switch %} {% can_delete constructor_switch %} - + {% acl_end %} @@ -59,3 +61,4 @@ with this program; if not, write to the Free Software Foundation, Inc., {% if constructor_switch_list.paginator %} {% include "pagination.html" with list=constructor_switch_list %} {% endif %} + diff --git a/topologie/templates/topologie/aff_model_switch.html b/topologie/templates/topologie/aff_model_switch.html index 3a62024f..6b3e7156 100644 --- a/topologie/templates/topologie/aff_model_switch.html +++ b/topologie/templates/topologie/aff_model_switch.html @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load acl %} {% load logs_extra %} +{% load i18n %} {% if model_switch_list.paginator %} {% include "pagination.html" with list=model_switch_list %} @@ -32,24 +33,26 @@ with this program; if not, write to the Free Software Foundation, Inc., - - + {% trans "Reference" as tr_ref %} + + {% trans "Switch constructor" as tr_constructor %} + {% for model_switch in model_switch_list %} - - + + - - + + {% for port_profile in port_profile_list %} - - + + + {% trans "RADIUS mode: " %}{{ port_profile.radius_mode }} {% endif %} - - - + + +
    {% include "buttons/sort.html" with prefix='model-switch' col='reference' text='Référence' %}{% include "buttons/sort.html" with prefix='model-switch' col='constructor' text='Constructeur' %}{% include "buttons/sort.html" with prefix='model-switch' col='reference' text=tr_ref %}{% include "buttons/sort.html" with prefix='model-switch' col='constructor' text=tr_constructor %}
    {{model_switch.reference}}{{model_switch.constructor}}{{ model_switch.reference }}{{ model_switch.constructor }} - {% history_button model_switch %} {% can_edit model_switch %} - + {% acl_end %} + {% history_button model_switch %} {% can_delete model_switch %} - + {% acl_end %} @@ -61,3 +64,4 @@ with this program; if not, write to the Free Software Foundation, Inc., {% if model_switch_list.paginator %} {% include "pagination.html" with list=model_switch_list %} {% endif %} + diff --git a/topologie/templates/topologie/aff_port.html b/topologie/templates/topologie/aff_port.html index ec02bcaa..c7c6a0b4 100644 --- a/topologie/templates/topologie/aff_port.html +++ b/topologie/templates/topologie/aff_port.html @@ -24,62 +24,83 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load acl %} {% load logs_extra %} +{% load i18n %}
    - - - - - - - - - - - {% for port in port_list %} - - - - - + {% trans "Port" as tr_port %} + + {% trans "Room" as tr_room %} + + {% trans "Interface" as tr_interface %} + + {% trans "Related port" as tr_related_port %} + + + + + + + + {% for port in port_list %} + + + + + - - - - - + {% acl_end %} + {% endif %} + + + + + + {% endfor %}
    {% include "buttons/sort.html" with prefix='port' col='port' text='Port' %}{% include "buttons/sort.html" with prefix='port' col='room' text='Room' %}{% include "buttons/sort.html" with prefix='port' col='interface' text='Interface machine' %}{% include "buttons/sort.html" with prefix='port' col='related' text='Related' %}Etat du portProfil du portDétails
    {{ port.port }} - {% if port.room %}{{ port.room }}{% endif %} - - {% if port.machine_interface %} - {% can_view port.machine_interface.machine.user %} - - {{ port.machine_interface }} - {% acl_end %} - {% endif %} - - {% if port.related %} - {% can_view port.related.switch %} - +
    {% include "buttons/sort.html" with prefix='port' col='port' text=tr_port %}{% include "buttons/sort.html" with prefix='port' col='room' text=tr_room %}{% include "buttons/sort.html" with prefix='port' col='interface' text=tr_interface %}{% include "buttons/sort.html" with prefix='port' col='related' text=tr_related_port %}{% trans "Port state" %}{% trans "Port profile" %}{% trans "Details" %}
    {{ port.port }} + {% if port.room %} + {{ port.room }} + {% endif %} + + {% if port.machine_interface %} + {% can_view port.machine_interface.machine.user %} + + {{ port.machine_interface }} + + {% acl_end %} + {% endif %} + + {% if port.related %} + {% can_view port.related.switch %} + + {{ port.related }} + + {% acl_else %} {{ port.related }} - - {% acl_else %} - {{ port.related }} - {% acl_end %} - {% endif %} - {% if port.state %} Actif{% else %}Désactivé{% endif %}{% if not port.custom_profil %}Par défaut : {% endif %}{{port.get_port_profil}}{{ port.details }} - {% history_button port %} - {% can_edit port %} - - - - {% acl_end %} - {% can_delete port %} - - - - {% acl_end %} -
    + {% if port.state %} + {% trans "Active" %} + {% else %} + {% trans "Disabled" %} + {% endif %} + + {% if not port.custom_profil %} + {% trans "Default: " %} + {% endif %} + {{ port.get_port_profil }} + {{ port.details }} + {% can_edit port %} + + + + {% acl_end %} + {% history_button port %} + {% can_delete port %} + + + + {% acl_end %} +
    + diff --git a/topologie/templates/topologie/aff_port_profile.html b/topologie/templates/topologie/aff_port_profile.html index 4ba59ff5..9714f1d9 100644 --- a/topologie/templates/topologie/aff_port_profile.html +++ b/topologie/templates/topologie/aff_port_profile.html @@ -38,39 +38,39 @@ with this program; if not, write to the Free Software Foundation, Inc.,
    {% trans "Default for" %} {% trans "VLANs" %} {% trans "RADIUS settings" %}{% trans "Speed" %}{% trans "Mac address limit" %}{% trans "Speed limit" %}{% trans "MAC address limit" %} {% trans "Security" %}
    {{port_profile.name}}{{port_profile.profil_default}}{{ port_profile.name }}{{ port_profile.profil_default }} {% if port_profile.vlan_untagged %} - Untagged : {{port_profile.vlan_untagged}} + {% trans "Untagged: " %}{{ port_profile.vlan_untagged }}
    {% endif %} {% if port_profile.vlan_tagged.all %} - Tagged : {{port_profile.vlan_tagged.all|join:", "}} + {% trans "Tagged: " %}{{ port_profile.vlan_tagged.all|join:", " }} {% endif %}
    - Type : {{port_profile.radius_type}} + {% trans "RADIUS type: " %}{{ port_profile.radius_type }} {% if port_profile.radius_type == "MAC-radius" %}
    - Mode : {{port_profile.radius_mode}}
    {{port_profile.speed}}{{port_profile.mac_limit}}{{port_profile.security_parameters_enabled|join:"
    "}}
    {{ port_profile.speed }}{{ port_profile.mac_limit }}{{ port_profile.security_parameters_enabled|join:"
    " }}
    - {% history_button port_profile %} {% can_edit port_profile %} {% include 'buttons/edit.html' with href='topologie:edit-port-profile' id=port_profile.pk %} {% acl_end %} + {% history_button port_profile %} {% can_delete port_profile %} {% include 'buttons/suppr.html' with href='topologie:del-port-profile' id=port_profile.pk %} {% acl_end %} @@ -84,3 +84,4 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endif %} + diff --git a/topologie/templates/topologie/aff_stacks.html b/topologie/templates/topologie/aff_stacks.html index 4dd0f6ea..c5366cd7 100644 --- a/topologie/templates/topologie/aff_stacks.html +++ b/topologie/templates/topologie/aff_stacks.html @@ -24,37 +24,46 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load acl %} {% load logs_extra %} +{% load i18n %} - - - - + {% trans "Stack" as tr_stack %} + + {% trans "ID" as tr_id %} + + + {% for stack in stack_list %} - - - - - - - + + + + + + + {% endfor %}
    {% include "buttons/sort.html" with prefix='stack' col='name' text='Stack' %}{% include "buttons/sort.html" with prefix='stack' col='id' text='ID' %}DétailsMembres{% include "buttons/sort.html" with prefix='stack' col='name' text=tr_stack %}{% include "buttons/sort.html" with prefix='stack' col='id' text=id %}{% trans "Details" %}{% trans "Members" %}
    {{ stack.name }}{{stack.stack_id}}{{stack.details}}{% for switch in stack.switch_set.all %}{{switch }} {% endfor %} - {% history_button stack %} - {% can_edit stack %} - - - - {% acl_end %} - {% can_delete stack %} - - - - {% acl_end %} -
    {{ stack.name }}{{ stack.stack_id }}{{ stack.details }} + {% for switch in stack.switch_set.all %} + + {{ switch }} + + {% endfor %} + + {% can_edit stack %} + + + + {% acl_end %} + {% history_button stack %} + {% can_delete stack %} + + + + {% acl_end %} +
    diff --git a/topologie/templates/topologie/aff_switch.html b/topologie/templates/topologie/aff_switch.html index 1f4dcbcf..d900a1f9 100644 --- a/topologie/templates/topologie/aff_switch.html +++ b/topologie/templates/topologie/aff_switch.html @@ -24,59 +24,65 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load acl %} {% load logs_extra %} +{% load i18n %}
    {% if switch_list.paginator %} {% include "pagination.html" with list=switch_list %} {% endif %} - - - - - - - - - + {% trans "DNS name" as tr_dns %} + + {% trans "IPv4 address" as tr_ip %} + + {% trans "Switch bay" as tr_bay %} + + {% trans "Ports" as tr_ports %} + + {% trans "Stack" as tr_stack %} + + + + {% for switch in switch_list %} - - - - - - - - - - - + + + + + + + + + + + {% endfor %}
    {% include "buttons/sort.html" with prefix='switch' col='dns' text='Dns' %}{% include "buttons/sort.html" with prefix='switch' col='ip' text='Ipv4' %}{% include "buttons/sort.html" with prefix='switch' col='loc' text='Emplacement' %}{% include "buttons/sort.html" with prefix='switch' col='ports' text='Ports' %}{% include "buttons/sort.html" with prefix='switch' col='stack' text='Stack' %}Id stackModèleDétails{% include "buttons/sort.html" with prefix='switch' col='dns' text=tr_dns %}{% include "buttons/sort.html" with prefix='switch' col='ip' text=tr_ip %}{% include "buttons/sort.html" with prefix='switch' col='loc' text=tr_bay %}{% include "buttons/sort.html" with prefix='switch' col='ports' text=tr_ports %}{% include "buttons/sort.html" with prefix='switch' col='stack' text=tr_stack %}{% trans "Stack ID" %}{% trans "Switch model" %}{% trans "Details" %}
    - - {{switch}} - - {{switch.interface_set.first.ipv4}}{{switch.switchbay}}{{switch.number}}{{switch.stack.name}}{{switch.stack_member_id}}{{switch.model}}{{switch.interface_set.first.details}} - {% history_button switch %} - {% can_edit switch %} - {% include 'buttons/edit.html' with href='topologie:edit-switch' id=switch.pk %} - {% acl_end %} - {% can_delete switch %} - {% include 'buttons/suppr.html' with href='machines:del-machine' id=switch.id %} - {% acl_end %} - {% can_create Port %} - {% include 'buttons/add.html' with href='topologie:create-ports' id=switch.pk desc='Création de ports'%} - {% acl_end %} -
    + + {{ switch }} + + {{ switch.interface_set.first.ipv4 }}{{ switch.switchbay }}{{ switch.number }}{{ switch.stack.name }}{{ switch.stack_member_id }}{{ switch.model }}{{ switch.interface_set.first.details }} + {% can_edit switch %} + {% include 'buttons/edit.html' with href='topologie:edit-switch' id=switch.pk %} + {% acl_end %} + {% history_button switch %} + {% can_delete switch %} + {% include 'buttons/suppr.html' with href='machines:del-machine' id=switch.id %} + {% acl_end %} + {% can_create Port %} + {% trans "Creation of ports" as tr_creation %} + {% include 'buttons/add.html' with href='topologie:create-ports' id=switch.pk desc=tr_creation %} + {% acl_end %} +
    {% if switch_list.paginator %} {% include "pagination.html" with list=switch_list %} {% endif %} -
    + diff --git a/topologie/templates/topologie/aff_switch_bay.html b/topologie/templates/topologie/aff_switch_bay.html index c73bdf5f..0909c264 100644 --- a/topologie/templates/topologie/aff_switch_bay.html +++ b/topologie/templates/topologie/aff_switch_bay.html @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load acl %} {% load logs_extra %} +{% load i18n %} {% if switch_bay_list.paginator %} {% include "pagination.html" with list=switch_bay_list %} @@ -32,36 +33,45 @@ with this program; if not, write to the Free Software Foundation, Inc., - - - - + {% trans "Switch bay" as tr_bay %} + + {% trans "Building" as tr_building %} + + + {% for switch_bay in switch_bay_list %} - - - - - - - + + + + + + + {% endfor %}
    {% include "buttons/sort.html" with prefix='switch-bay' col='name' text='Baie' %}{% include "buttons/sort.html" with prefix='switch-bay' col='building' text='Bâtiment' %}Info particulièresSwitchs de la baie{% include "buttons/sort.html" with prefix='switch-bay' col='name' text=tr_bay %}{% include "buttons/sort.html" with prefix='switch-bay' col='building' text=tr_building %}{% trans "Information" %}{% trans "Switches of the bay" %}
    {{switch_bay.name}}{{switch_bay.building}}{{switch_bay.info}}{% for switch in switch_bay.switch_set.all %}{{switch }} {% endfor %} - {% history_button switch_bay %} - {% can_edit switch_bay %} - - - - {% acl_end %} - {% can_delete switch_bay %} - - - - {% acl_end %} -
    {{ switch_bay.name }}{{ switch_bay.building }}{{ switch_bay.info }} + {% for switch in switch_bay.switch_set.all %} + + {{ switch }} + + {% endfor %} + + {% can_edit switch_bay %} + + + + {% acl_end %} + {% history_button switch_bay %} + {% can_delete switch_bay %} + + + + {% acl_end %} +
    {% if switch_bay_list.paginator %} {% include "pagination.html" with list=switch_bay_list %} {% endif %} + diff --git a/topologie/templates/topologie/delete.html b/topologie/templates/topologie/delete.html index ce277647..97834564 100644 --- a/topologie/templates/topologie/delete.html +++ b/topologie/templates/topologie/delete.html @@ -24,17 +24,20 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load bootstrap3 %} +{% load i18n %} -{% block title %}Création et modification de machines{% endblock %} +{% block title %}{% trans "Topology" %}{% endblock %} {% block content %}
    {% csrf_token %} -

    Attention, voulez-vous vraiment supprimer cet objet {{ objet_name }} ( {{ objet }} ) ?

    - {% bootstrap_button "Confirmer" button_type="submit" icon="trash" %} +

    {% blocktrans %}Warning: are you sure you want to delete this {{ objet_name }} object ( {{ objet }} )?{% endblocktrans %}

    + {% trans "Confirm" as tr_confirm %} + {% bootstrap_button tr_confirm button_type="submit" icon="trash" %}



    {% endblock %} + diff --git a/topologie/templates/topologie/edit_stack_sw.html b/topologie/templates/topologie/edit_stack_sw.html index 37a25cd0..8c29c85c 100644 --- a/topologie/templates/topologie/edit_stack_sw.html +++ b/topologie/templates/topologie/edit_stack_sw.html @@ -24,33 +24,35 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load acl %} {% load logs_extra %} +{% load i18n %} - - - + + + + + + {% for stack in stack_list %} + + + + + - - {% for stack in stack_list %} - - - - - - {% endfor %}
    StackIDDetails{% trans "Stack" %}{% trans "Stack ID" %}{% trans "Details" %}
    {{ stack.name }}{{ stack.stack_id }}{{ stack.details }} + {% can_edit stack %} + + + + {% acl_end %} + {% history_button stack %} + {% can_delete stack %} + + + + {% acl_end %} +
    {{stack.name}}{{stack.stack_id}}{{stack.details}} - {% history_button stack %} - {% can_edit stack %} - - - - {% acl_end %} - {% can_delete stack %} - - - - {% acl_end %} -
    + diff --git a/topologie/templates/topologie/index.html b/topologie/templates/topologie/index.html index f596e6a5..1c13b928 100644 --- a/topologie/templates/topologie/index.html +++ b/topologie/templates/topologie/index.html @@ -25,8 +25,9 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} {% load acl %} +{% load i18n %} -{% block title %}Switchs{% endblock %} +{% block title %}{% trans "Topology" %}{% endblock %} {% block content %} @@ -51,7 +52,7 @@ function toggle_graph() { @@ -62,9 +63,9 @@ Topologie des Switchs -

    Switchs

    +

    {% trans "Switches" %}

    {% can_create Switch %} -
    Ajouter un switch +{% trans " Add a switch" %}
    {% acl_end %} {% include "topologie/aff_switch.html" with switch_list=switch_list %} @@ -73,3 +74,4 @@ Topologie des Switchs
    {% endblock %} + diff --git a/topologie/templates/topologie/index_ap.html b/topologie/templates/topologie/index_ap.html index 19499998..e1292bb9 100644 --- a/topologie/templates/topologie/index_ap.html +++ b/topologie/templates/topologie/index_ap.html @@ -25,13 +25,14 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} {% load acl %} +{% load i18n %} -{% block title %}Bornes WiFi{% endblock %} +{% block title %}{% trans "Topology" %}{% endblock %} {% block content %} -

    Points d'accès WiFi

    +

    {% trans "Access points" %}

    {% can_create AccessPoint %} - Ajouter une borne +{% trans " Add an access point" %}
    {% acl_end %} {% include "topologie/aff_ap.html" with ap_list=ap_list %} @@ -39,3 +40,4 @@ with this program; if not, write to the Free Software Foundation, Inc.,

    {% endblock %} + diff --git a/topologie/templates/topologie/index_model_switch.html b/topologie/templates/topologie/index_model_switch.html index 7c1a15f8..b89b7415 100644 --- a/topologie/templates/topologie/index_model_switch.html +++ b/topologie/templates/topologie/index_model_switch.html @@ -25,25 +25,27 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} {% load acl %} +{% load i18n %} -{% block title %}Modèles de switches{% endblock %} +{% block title %}{% trans "Topology" %}{% endblock %} {% block content %} -

    Modèles de switches

    +

    {% trans "Switch models" %}

    {% can_create ModelSwitch %} - Ajouter un modèle + {% trans " Add a switch model" %}
    {% acl_end %} {% include "topologie/aff_model_switch.html" with model_switch_list=model_switch_list %} -

    Constructeurs de switches

    +

    {% trans "Switch constructors" %}

    {% can_create ConstructorSwitch %} - Ajouter un constructeur + {% trans " Add a switch constructor" %}
    {% acl_end %} {% include "topologie/aff_constructor_switch.html" with constructor_switch_list=constructor_switch_list %} {% endblock %} + diff --git a/topologie/templates/topologie/index_p.html b/topologie/templates/topologie/index_p.html index 138cc62c..aabb9e21 100644 --- a/topologie/templates/topologie/index_p.html +++ b/topologie/templates/topologie/index_p.html @@ -25,15 +25,16 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} {% load acl %} +{% load i18n %} -{% block title %}Ports du switch{% endblock %} +{% block title %}{% trans "Topology" %}{% endblock %} {% block content %} -

    Switch {{ nom_switch }}

    - Editer +

    {% trans "Switch:"%} {{ nom_switch }}

    +{% trans " Edit" %} {% can_create Port %} - Ajouter un port - Ajouter des ports +{% trans " Add a port" %} +{% trans " Add ports" %} {% acl_end %}
    {% include "topologie/aff_repr_switch.html" with port_list=port_list %} @@ -42,3 +43,4 @@ with this program; if not, write to the Free Software Foundation, Inc.,

    {% endblock %} + diff --git a/topologie/templates/topologie/index_physical_grouping.html b/topologie/templates/topologie/index_physical_grouping.html index 2470c019..763d7820 100644 --- a/topologie/templates/topologie/index_physical_grouping.html +++ b/topologie/templates/topologie/index_physical_grouping.html @@ -25,33 +25,35 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} {% load acl %} +{% load i18n %} -{% block title %}Stacks{% endblock %} +{% block title %}{% trans "Topology" %}{% endblock %} {% block content %} -

    Stacks

    +

    {% trans "Stacks" %}

    {% can_create Stack %} - Ajouter une stack + {% trans " Add a stack" %} {% acl_end %} {% include "topologie/aff_stacks.html" with stack_list=stack_list %} -

    Baie de brassage

    +

    {% trans "Switch bays" %}

    {% can_create SwitchBay %} - Ajouter une baie de brassage + {% trans " Add a switch bay" %}
    {% acl_end %} {% include "topologie/aff_switch_bay.html" with switch_bay_list=switch_bay_list %} -

    Batiment

    +

    {% trans "Buildings" %}

    {% can_create Building %} - Ajouter un bâtiment + {% trans " Add a building" %}
    {% acl_end %} {% include "topologie/aff_building.html" with building_list=building_list %} {% endblock %} + diff --git a/topologie/templates/topologie/index_portprofile.html b/topologie/templates/topologie/index_portprofile.html index f95415c8..730823f6 100644 --- a/topologie/templates/topologie/index_portprofile.html +++ b/topologie/templates/topologie/index_portprofile.html @@ -26,7 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load acl %} {% load i18n %} -{% block title %}Switchs{% endblock %} +{% block title %}{% trans "Topology" %}{% endblock %} {% block content %} @@ -41,3 +41,4 @@ with this program; if not, write to the Free Software Foundation, Inc.,
    {% endblock %} + diff --git a/topologie/templates/topologie/index_room.html b/topologie/templates/topologie/index_room.html index 0d586452..ea6ed6f3 100644 --- a/topologie/templates/topologie/index_room.html +++ b/topologie/templates/topologie/index_room.html @@ -25,13 +25,14 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} {% load acl %} +{% load i18n %} -{% block title %}Chambres{% endblock %} +{% block title %}{% trans "Topology" %}{% endblock %} {% block content %} -

    Chambres

    +

    {% trans "Rooms" %}

    {% can_create Room %} - Ajouter une chambre +{% trans " Add a room" %}
    {% acl_end %} {% include "topologie/aff_chambres.html" with room_list=room_list %} @@ -39,3 +40,4 @@ with this program; if not, write to the Free Software Foundation, Inc.,

    {% endblock %} + diff --git a/topologie/templates/topologie/sidebar.html b/topologie/templates/topologie/sidebar.html index 04ee5202..a35721f9 100644 --- a/topologie/templates/topologie/sidebar.html +++ b/topologie/templates/topologie/sidebar.html @@ -23,30 +23,32 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. {% endcomment %} +{% load i18n %} {% block sidebar %} - Chambres et locaux + {% trans "Rooms and premises" %} - Switchs + {% trans "Switches" %} - Config des ports switchs + {% trans "Port profiles" %} - Bornes WiFi + {% trans "Access points" %} - Groupements physiques + {% trans "Physical grouping" %} - Modèles switches et constructeurs + {% trans "Switch models and constructors" %} {% endblock %} + diff --git a/topologie/templates/topologie/switch.html b/topologie/templates/topologie/switch.html index 4b334896..0e4995ba 100644 --- a/topologie/templates/topologie/switch.html +++ b/topologie/templates/topologie/switch.html @@ -25,8 +25,9 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} {% load massive_bootstrap_form %} +{% load i18n %} -{% block title %}Création et modification d'un switch{% endblock %} +{% block title %}{% trans "Topology" %}{% endblock %} {% block content %} {% if topoform %} @@ -35,16 +36,18 @@ with this program; if not, write to the Free Software Foundation, Inc., -{% bootstrap_icon "list" %} Aller à la liste des ports +{% bootstrap_icon "list" %}{% trans " Go to the ports list" %}
    {% csrf_token %} {% if topoform %} -

    Réglage spécifiques du switch

    +

    {% trans "Specific settings for the switch" %}

    {% massive_bootstrap_form topoform 'switch_interface' %} {% endif %} - {% bootstrap_button "Créer" button_type="submit" icon="ok" %} + {% trans "Create" as tr_create %} + {% bootstrap_button tr_create button_type="submit" icon="ok" %}



    {% endblock %} + diff --git a/topologie/templates/topologie/topo.html b/topologie/templates/topologie/topo.html index 7d1662f0..d02d0c37 100644 --- a/topologie/templates/topologie/topo.html +++ b/topologie/templates/topologie/topo.html @@ -25,14 +25,15 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} {% load massive_bootstrap_form %} +{% load i18n %} -{% block title %}Modification de la topologie{% endblock %} +{% block title %}{% trans "Topology" %}{% endblock %} {% block content %} {% bootstrap_form_errors topoform %} {% if id_switch %} -{% bootstrap_icon "list" %} Aller à la liste des ports +{% bootstrap_icon "list" %}{% trans " Go to the ports list" %} {% endif %}
    {% csrf_token %} @@ -43,3 +44,4 @@ with this program; if not, write to the Free Software Foundation, Inc.,

    {% endblock %} + diff --git a/topologie/templates/topologie/topo_more.html b/topologie/templates/topologie/topo_more.html index d1147ff8..70e71910 100644 --- a/topologie/templates/topologie/topo_more.html +++ b/topologie/templates/topologie/topo_more.html @@ -25,8 +25,9 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} {% load massive_bootstrap_form %} +{% load i18n %} -{% block title %}Création et modification d'un objet topologie{% endblock %} +{% block title %}{% trans "Topology" %}{% endblock %} {% block content %} {% if topoform %} @@ -44,20 +45,22 @@ with this program; if not, write to the Free Software Foundation, Inc., {% csrf_token %} {% if topoform %} -

    Réglage spécifiques du {{ device }}

    +

    {% blocktrans %}Specific settings for the {{ device }} object{% endblocktrans %}

    {% massive_bootstrap_form topoform 'ipv4,machine' mbf_param=i_mbf_param%} {% endif %} {% if machineform %} -

    Réglages généraux de la machine associée au {{ device }}

    +

    {% blocktrans %}General settings for the machine linked to the {{ device }} object{% endblocktrans %}

    {% massive_bootstrap_form machineform 'user' %} {% endif %} {% if domainform %} -

    Nom de la machine

    +

    {% trans "DNS name" %}

    {% bootstrap_form domainform %} {% endif %} - {% bootstrap_button "Créer ou modifier" button_type="submit" icon="ok" %} + {% trans "Create or edit" as tr_create_or_edit %} + {% bootstrap_button tr_create_or_edit button_type="submit" icon="ok" %}



    {% endblock %} + diff --git a/topologie/views.py b/topologie/views.py index a6c6ab69..1e8bfee4 100644 --- a/topologie/views.py +++ b/topologie/views.py @@ -314,7 +314,7 @@ def new_port(request, switchid): try: switch = Switch.objects.get(pk=switchid) except Switch.DoesNotExist: - messages.error(request, u"Switch inexistant") + messages.error(request, _("Nonexistent switch.")) return redirect(reverse('topologie:index')) port = AddPortForm(request.POST or None) if port.is_valid(): @@ -322,15 +322,15 @@ def new_port(request, switchid): port.switch = switch try: port.save() - messages.success(request, "Port ajouté") + messages.success(request, _("The port was added.")) except IntegrityError: - messages.error(request, "Ce port existe déjà") + messages.error(request, _("The port already exists.")) return redirect(reverse( 'topologie:index-port', kwargs={'switchid': switchid} )) return form( - {'id_switch': switchid, 'topoform': port, 'action_name': 'Ajouter'}, + {'id_switch': switchid, 'topoform': port, 'action_name': _("Add")}, 'topologie/topo.html', request) @@ -345,7 +345,7 @@ def edit_port(request, port_object, **_kwargs): if port.is_valid(): if port.changed_data: port.save() - messages.success(request, "Le port a bien été modifié") + messages.success(request, _("The port was edited.")) return redirect(reverse( 'topologie:index-port', kwargs={'switchid': str(port_object.switch.id)} @@ -354,7 +354,7 @@ def edit_port(request, port_object, **_kwargs): { 'id_switch': str(port_object.switch.id), 'topoform': port, - 'action_name': 'Editer' + 'action_name': _("Edit") }, 'topologie/topo.html', request @@ -368,12 +368,12 @@ def del_port(request, port, **_kwargs): if request.method == "POST": try: port.delete() - messages.success(request, "Le port a été détruit") + messages.success(request, _("The port was deleted.")) except ProtectedError: messages.error( request, - ("Le port %s est affecté à un autre objet, impossible " - "de le supprimer" % port) + (_("The port %s is used by another object, impossible to" + " delete it.") % port) ) return redirect(reverse( 'topologie:index-port', @@ -389,10 +389,10 @@ def new_stack(request): stack = StackForm(request.POST or None) if stack.is_valid(): stack.save() - messages.success(request, "Stack crée") + messages.success(request, _("The stack was created.")) return redirect(reverse('topologie:index-physical-grouping')) return form( - {'topoform': stack, 'action_name': 'Créer'}, + {'topoform': stack, 'action_name': _("Create")}, 'topologie/topo.html', request ) @@ -408,7 +408,7 @@ def edit_stack(request, stack, **_kwargs): stack.save() return redirect(reverse('topologie:index-physical-grouping')) return form( - {'topoform': stack, 'action_name': 'Editer'}, + {'topoform': stack, 'action_name': _("Edit")}, 'topologie/topo.html', request ) @@ -421,12 +421,12 @@ def del_stack(request, stack, **_kwargs): if request.method == "POST": try: stack.delete() - messages.success(request, "La stack a eté détruite") + messages.success(request, _("The stack was deleted.")) except ProtectedError: messages.error( request, - ("La stack %s est affectée à un autre objet, impossible " - "de la supprimer" % stack) + (_("The stack %s is used by another object, impossible to" + " deleted it.") % stack) ) return redirect(reverse('topologie:index-physical-grouping')) return form({'objet': stack}, 'topologie/delete.html', request) @@ -467,8 +467,8 @@ def new_switch(request): if not user: messages.error( request, - ("L'user association n'existe pas encore, veuillez le " - "créer ou le linker dans preferences") + (_("The organisation's user doesn't exist yet, please create" + " or link it in the preferences.")) ) return redirect(reverse('topologie:index')) new_switch_obj = switch.save(commit=False) @@ -482,7 +482,7 @@ def new_switch(request): new_interface_obj.save() new_domain_obj.interface_parent = new_interface_obj new_domain_obj.save() - messages.success(request, "Le switch a été créé") + messages.success(request, _("The switch was created.")) return redirect(reverse('topologie:index')) i_mbf_param = generate_ipv4_mbf_param(interface, False) return form( @@ -505,7 +505,7 @@ def create_ports(request, switchid): try: switch = Switch.objects.get(pk=switchid) except Switch.DoesNotExist: - messages.error(request, u"Switch inexistant") + messages.error(request, _("Nonexistent switch")) return redirect(reverse('topologie:index')) s_begin = s_end = 0 @@ -525,7 +525,7 @@ def create_ports(request, switchid): end = port_form.cleaned_data['end'] try: switch.create_ports(begin, end) - messages.success(request, "Ports créés.") + messages.success(request, _("The ports were created.")) except ValidationError as e: messages.error(request, ''.join(e)) return redirect(reverse( @@ -569,7 +569,7 @@ def edit_switch(request, switch, switchid): new_interface_obj.save() if domain_form.changed_data: new_domain_obj.save() - messages.success(request, "Le switch a bien été modifié") + messages.success(request, _("The switch was edited.")) return redirect(reverse('topologie:index')) i_mbf_param = generate_ipv4_mbf_param(interface_form, False) return form( @@ -608,8 +608,8 @@ def new_ap(request): if not user: messages.error( request, - ("L'user association n'existe pas encore, veuillez le " - "créer ou le linker dans preferences") + (_("The organisation's user doesn't exist yet, please create" + " or link it in the preferences.")) ) return redirect(reverse('topologie:index')) new_ap_obj = ap.save(commit=False) @@ -623,7 +623,7 @@ def new_ap(request): new_interface_obj.save() new_domain_obj.interface_parent = new_interface_obj new_domain_obj.save() - messages.success(request, "La borne a été créé") + messages.success(request, _("The access point was created.")) return redirect(reverse('topologie:index-ap')) i_mbf_param = generate_ipv4_mbf_param(interface, False) return form( @@ -663,8 +663,8 @@ def edit_ap(request, ap, **_kwargs): if not user: messages.error( request, - ("L'user association n'existe pas encore, veuillez le " - "créer ou le linker dans preferences") + (_("The organisation's user doesn't exist yet, please create" + " or link it in the preferences.")) ) return redirect(reverse('topologie:index-ap')) new_ap_obj = ap_form.save(commit=False) @@ -676,7 +676,7 @@ def edit_ap(request, ap, **_kwargs): new_interface_obj.save() if domain_form.changed_data: new_domain_obj.save() - messages.success(request, "La borne a été modifiée") + messages.success(request, _("The access point was edited.")) return redirect(reverse('topologie:index-ap')) i_mbf_param = generate_ipv4_mbf_param(interface_form, False) return form( @@ -699,10 +699,10 @@ def new_room(request): room = EditRoomForm(request.POST or None) if room.is_valid(): room.save() - messages.success(request, "La chambre a été créé") + messages.success(request, _("The room was created.")) return redirect(reverse('topologie:index-room')) return form( - {'topoform': room, 'action_name': 'Ajouter'}, + {'topoform': room, 'action_name': _("Add")}, 'topologie/topo.html', request ) @@ -716,10 +716,10 @@ def edit_room(request, room, **_kwargs): if room.is_valid(): if room.changed_data: room.save() - messages.success(request, "La chambre a bien été modifiée") + messages.success(request, _("The room was edited.")) return redirect(reverse('topologie:index-room')) return form( - {'topoform': room, 'action_name': 'Editer'}, + {'topoform': room, 'action_name': _("Edit")}, 'topologie/topo.html', request ) @@ -732,16 +732,16 @@ def del_room(request, room, **_kwargs): if request.method == "POST": try: room.delete() - messages.success(request, "La chambre/prise a été détruite") + messages.success(request, _("The room was deleted.")) except ProtectedError: messages.error( request, - ("La chambre %s est affectée à un autre objet, impossible " - "de la supprimer (switch ou user)" % room) + (_("The room %s is used by another object, impossible to" + " deleted it.") % room) ) return redirect(reverse('topologie:index-room')) return form( - {'objet': room, 'objet_name': 'Chambre'}, + {'objet': room, 'objet_name': _("Room")}, 'topologie/delete.html', request ) @@ -754,10 +754,10 @@ def new_model_switch(request): model_switch = EditModelSwitchForm(request.POST or None) if model_switch.is_valid(): model_switch.save() - messages.success(request, "Le modèle a été créé") + messages.success(request, _("The swich model was created.")) return redirect(reverse('topologie:index-model-switch')) return form( - {'topoform': model_switch, 'action_name': 'Ajouter'}, + {'topoform': model_switch, 'action_name': _("Add")}, 'topologie/topo.html', request ) @@ -775,10 +775,10 @@ def edit_model_switch(request, model_switch, **_kwargs): if model_switch.is_valid(): if model_switch.changed_data: model_switch.save() - messages.success(request, "Le modèle a bien été modifié") + messages.success(request, _("The switch model was edited.")) return redirect(reverse('topologie:index-model-switch')) return form( - {'topoform': model_switch, 'action_name': 'Editer'}, + {'topoform': model_switch, 'action_name': _("Edit")}, 'topologie/topo.html', request ) @@ -791,16 +791,16 @@ def del_model_switch(request, model_switch, **_kwargs): if request.method == "POST": try: model_switch.delete() - messages.success(request, "Le modèle a été détruit") + messages.success(request, _("The switch model was deleted.")) except ProtectedError: messages.error( request, - ("Le modèle %s est affectée à un autre objet, impossible " - "de la supprimer (switch ou user)" % model_switch) + (_("The switch model %s is used by another object," + " impossible to delete it.") % model_switch) ) return redirect(reverse('topologie:index-model-switch')) return form( - {'objet': model_switch, 'objet_name': 'Modèle de switch'}, + {'objet': model_switch, 'objet_name': _("Switch model")}, 'topologie/delete.html', request ) @@ -813,10 +813,10 @@ def new_switch_bay(request): switch_bay = EditSwitchBayForm(request.POST or None) if switch_bay.is_valid(): switch_bay.save() - messages.success(request, "La baie a été créé") + messages.success(request, _("The switch bay was created.")) return redirect(reverse('topologie:index-physical-grouping')) return form( - {'topoform': switch_bay, 'action_name': 'Ajouter'}, + {'topoform': switch_bay, 'action_name': _("Add")}, 'topologie/topo.html', request ) @@ -830,10 +830,10 @@ def edit_switch_bay(request, switch_bay, **_kwargs): if switch_bay.is_valid(): if switch_bay.changed_data: switch_bay.save() - messages.success(request, "Le switch a bien été modifié") + messages.success(request, _("The switch bay was edited.")) return redirect(reverse('topologie:index-physical-grouping')) return form( - {'topoform': switch_bay, 'action_name': 'Editer'}, + {'topoform': switch_bay, 'action_name': _("Edit")}, 'topologie/topo.html', request ) @@ -846,16 +846,16 @@ def del_switch_bay(request, switch_bay, **_kwargs): if request.method == "POST": try: switch_bay.delete() - messages.success(request, "La baie a été détruite") + messages.success(request, _("The switch bay was deleted.")) except ProtectedError: messages.error( request, - ("La baie %s est affecté à un autre objet, impossible " - "de la supprimer (switch ou user)" % switch_bay) + (_("The switch bay %s is used by another object," + " impossible to delete it.") % switch_bay) ) return redirect(reverse('topologie:index-physical-grouping')) return form( - {'objet': switch_bay, 'objet_name': 'Baie de switch'}, + {'objet': switch_bay, 'objet_name': _("Switch bay")}, 'topologie/delete.html', request ) @@ -868,10 +868,10 @@ def new_building(request): building = EditBuildingForm(request.POST or None) if building.is_valid(): building.save() - messages.success(request, "Le batiment a été créé") + messages.success(request, _("The building was created.")) return redirect(reverse('topologie:index-physical-grouping')) return form( - {'topoform': building, 'action_name': 'Ajouter'}, + {'topoform': building, 'action_name': _("Add")}, 'topologie/topo.html', request ) @@ -885,10 +885,10 @@ def edit_building(request, building, **_kwargs): if building.is_valid(): if building.changed_data: building.save() - messages.success(request, "Le batiment a bien été modifié") + messages.success(request, _("The building was edited.")) return redirect(reverse('topologie:index-physical-grouping')) return form( - {'topoform': building, 'action_name': 'Editer'}, + {'topoform': building, 'action_name': _("Edit")}, 'topologie/topo.html', request ) @@ -901,16 +901,16 @@ def del_building(request, building, **_kwargs): if request.method == "POST": try: building.delete() - messages.success(request, "La batiment a été détruit") + messages.success(request, _("The building was deleted.")) except ProtectedError: messages.error( request, - ("Le batiment %s est affecté à un autre objet, impossible " - "de la supprimer (switch ou user)" % building) + (_("The building %s is used by another object, impossible" + " to delete it.") % building) ) return redirect(reverse('topologie:index-physical-grouping')) return form( - {'objet': building, 'objet_name': 'Bâtiment'}, + {'objet': building, 'objet_name': _("Building")}, 'topologie/delete.html', request ) @@ -923,10 +923,10 @@ def new_constructor_switch(request): constructor_switch = EditConstructorSwitchForm(request.POST or None) if constructor_switch.is_valid(): constructor_switch.save() - messages.success(request, "Le constructeur a été créé") + messages.success(request, _("The switch constructor was created.")) return redirect(reverse('topologie:index-model-switch')) return form( - {'topoform': constructor_switch, 'action_name': 'Ajouter'}, + {'topoform': constructor_switch, 'action_name': _("Add")}, 'topologie/topo.html', request ) @@ -944,10 +944,10 @@ def edit_constructor_switch(request, constructor_switch, **_kwargs): if constructor_switch.is_valid(): if constructor_switch.changed_data: constructor_switch.save() - messages.success(request, "Le modèle a bien été modifié") + messages.success(request, _("The switch constructor was edited.")) return redirect(reverse('topologie:index-model-switch')) return form( - {'topoform': constructor_switch, 'action_name': 'Editer'}, + {'topoform': constructor_switch, 'action_name': _("Edit")}, 'topologie/topo.html', request ) @@ -960,17 +960,17 @@ def del_constructor_switch(request, constructor_switch, **_kwargs): if request.method == "POST": try: constructor_switch.delete() - messages.success(request, "Le constructeur a été détruit") + messages.success(request, _("The switch constructor was deleted.")) except ProtectedError: messages.error( request, - ("Le constructeur %s est affecté à un autre objet, impossible " - "de la supprimer (switch ou user)" % constructor_switch) + (_("The switch constructor %s is used by another object," + " impossible to delete it.") % constructor_switch) ) return redirect(reverse('topologie:index-model-switch')) return form({ 'objet': constructor_switch, - 'objet_name': 'Constructeur de switch' + 'objet_name': _("Switch constructor") }, 'topologie/delete.html', request) @@ -981,7 +981,7 @@ def new_port_profile(request): port_profile = EditPortProfileForm(request.POST or None) if port_profile.is_valid(): port_profile.save() - messages.success(request, _("Port profile created")) + messages.success(request, _("The port profile was created.")) return redirect(reverse('topologie:index')) return form( {'topoform': port_profile, 'action_name': _("Create")}, @@ -999,7 +999,7 @@ def edit_port_profile(request, port_profile, **_kwargs): if port_profile.is_valid(): if port_profile.changed_data: port_profile.save() - messages.success(request, _("Port profile modified")) + messages.success(request, _("The port profile was edited.")) return redirect(reverse('topologie:index')) return form( {'topoform': port_profile, 'action_name': _("Edit")}, @@ -1016,10 +1016,10 @@ def del_port_profile(request, port_profile, **_kwargs): try: port_profile.delete() messages.success(request, - _("The port profile was successfully deleted")) + _("The port profile was deleted.")) except ProtectedError: messages.success(request, - _("Impossible to delete the port profile")) + _("Impossible to delete the port profile.")) return redirect(reverse('topologie:index')) return form( {'objet': port_profile, 'objet_name': _("Port profile")}, @@ -1139,9 +1139,8 @@ def generate_dot(data, template): t = loader.get_template(template) if not isinstance(t, Template) and \ not (hasattr(t, 'template') and isinstance(t.template, Template)): - raise Exception("Le template par défaut de Django n'est pas utilisé." - "Cela peut mener à des erreurs de rendu." - "Vérifiez les paramètres") + raise Exception(_("The default Django template isn't used. This can" + " lead to rendering errors. Check the parameters.")) c = Context(data).flatten() dot = t.render(c) return(dot) @@ -1183,3 +1182,4 @@ def recursive_switchs(switch_start, switch_before, detected): if link: links_return.append(link) return (links_return, detected) + From f6130c2335367863f59f6696fb1396af7106bbe7 Mon Sep 17 00:00:00 2001 From: Laouen Fernet Date: Sun, 5 Aug 2018 18:57:36 +0200 Subject: [PATCH 147/171] Translation of logs/ (front) --- logs/acl.py | 5 +- logs/locale/fr/LC_MESSAGES/django.mo | Bin 0 -> 5018 bytes logs/locale/fr/LC_MESSAGES/django.po | 338 +++++++++++++++++++++ logs/templates/logs/aff_stats_general.html | 17 +- logs/templates/logs/aff_stats_logs.html | 60 ++-- logs/templates/logs/aff_stats_models.html | 33 +- logs/templates/logs/aff_stats_users.html | 43 +-- logs/templates/logs/aff_summary.html | 87 +++--- logs/templates/logs/delete.html | 15 +- logs/templates/logs/index.html | 16 +- logs/templates/logs/sidebar.html | 14 +- logs/templates/logs/stats_general.html | 16 +- logs/templates/logs/stats_logs.html | 16 +- logs/templates/logs/stats_models.html | 16 +- logs/templates/logs/stats_users.html | 16 +- logs/views.py | 165 +++++----- 16 files changed, 619 insertions(+), 238 deletions(-) create mode 100644 logs/locale/fr/LC_MESSAGES/django.mo create mode 100644 logs/locale/fr/LC_MESSAGES/django.po diff --git a/logs/acl.py b/logs/acl.py index 1ec227d3..ee9a7b1b 100644 --- a/logs/acl.py +++ b/logs/acl.py @@ -25,6 +25,7 @@ Here are defined some functions to check acl on the application. """ +from django.utils.translation import ugettext as _ def can_view(user): @@ -38,4 +39,6 @@ def can_view(user): viewing is granted and msg is a message (can be None). """ can = user.has_module_perms('admin') - return can, None if can else "Vous ne pouvez pas voir cette application." + return can, None if can else _("You don't have the right to view this" + " application.") + diff --git a/logs/locale/fr/LC_MESSAGES/django.mo b/logs/locale/fr/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..030b0cac0059428ce6aeedd0cde9e2a0d4404c1b GIT binary patch literal 5018 zcmai%U2Ggz6@YI`Nx^L>q(Gsea0zj2lkV1beh5z6#BrQr>ZF$ABt?QocW3VIPO>|* zou73!eLx5#)K)xHs8S!08lXs7sZ!wqDitJ0Prb{}$Kb!=J#gopN|oUQ@N;k#egrn)2jDYMRDKiQ4Zj0ro)_SU;7gD{ z^}~W)DEfW|<@uZNUib$n^Slj3?~UUAdr)MGL-x0pvc#t`13jV z1^8ol7yK<0`~L`KegA-B?>hzm2`9Mz7yKlYiwD4ca34GbWxZdAvffKj;u=FtQ_n$( z|M#KH|5Nxu_;VWw~FUGFlwFayC5pnQ&8-yL&=LxI1hgU58kHKU!cTmCq_)e zJy7g=96ktRct3m@iXY#AvcBI#iR+)B`1J-9`Ts((Yd=b5y=S20jfL`F4CTEJl=XfO zV!C<}Vv4#3#qL+3*!2teQTRK!7ycEBA8sQ^@_Yvr{k!3(;T#lwk3&S&lf`wdUz%!p!ofDDE9vbid}z(qVMhE{(Df?c@M^Y3C=^Y^AbOjAJ0HU)XPxT z`&x0o2PMzmf&1Z|B$?QI07||c=SSAJ4#i&=pseF5C~{5sd3Y6y{vSYz+pnS6c>{|5 z??TC&|3F#q{RB_qxEG4tG3fjRzsU8I@BsWa6#cKmbMRMC*0GC4$U64$^AJ1?55OuU zja*JY3?+9DGsH%jM~*M&aQSnhIR7jZJ5Mp@i+i^h6o^haBqnmmUKM?k>nn^|##b2P z8#yq;<$;_pF%B}Meo0QqAu*nFXG$$X@qru>rO6Cnur!Uk>sAKE^UbVjywd%aHhq??M$NUCealWhSuMH-}*sneQiP=gsF|I# zX>_#7(s~pJ$<3xqqK2<0r^BpSvGGiDMb2|0(rEjnS<5$QSNkWY{VX1ILf1;;1gS4)V=h-CDX{6J-)uBx{qxkYn{`#1> zNs^$3cl63y`=DBky?W3d%znazig8(fL?+j{WJ1q2)DmXYqPU}$qGr>EDGaMY+!Us9 zP|4CDtm&o|ZzSrZNlnEhc5tGTltm|L;JJtHfw*5wRCrUJv<)j>kbB1Y1Hm9MmBzr5 ziv}9tdrs&O`*m zw+&s5vd}Nfb!<&SRL(@9y@KV;tr>Ag9u9BCwjCtc&0UuXV*ljyW!oXV1TsIr=m%ap zgO-5^mKfiUC2E715I&Mq7N5twnHx9qR^Kthc?K^JCzRC;^mvV|GJ4$)&`=_b!v8l( zl14F6kktocv%3t@)5WA%?`x-UMm^y|qti~ysx{M*j3GJLO}<)}6k88!bz*hat4C2o zogF5}@Kl>hl%+)$j@-tMoBbS&KCA%GLPMiB&2_W@@&yVNDa$`qvA@_u{GbiPI~m zmxpVbE6+|yNhIM*8)W~2PVJTSp;p5L;Zg0?O-$lGa&F^P>Bvw{%tCW%IrJjmh5Se* zNGH@-Z!@m51zT@Ywt{NV>*_2VDfOhrRzaGX^fDw}hqG*6SID<0p@{k!S(E4cSYzv5 z+d5~Hl;~a4m!d4**4lz?jm=i8q(Ii|Qbf+B0eMJZvpCP)cCVXImy6C^+_Q8z(~)c^ z347PMo9P%qYqFV}3Fnne6yzOlT#IXWqDC5AkQD3Z;_1we8&q)uYB3D62K$@l*Xz># z0^4tYCWz(SwLk!OrOJI(UMr=Ap0tGYwi~@)FrZ+bwgshMM#oe+NOIo z4{pAPC|IqQbPTqH_NV$gcWjA1tCI)LnWy`i(K4|s$dusXmdc+M6Zf8N_pXO78)%=s zF4jr|9W%GqzfJ7c8f*tlSXoo2&_-1nHqyn+%ROHFv-%z z^Qi}(9Nrk2i1s)#+2AFrb>3MAlMFIoATwsrE#{dbXT%u^-8Hr~Dzm#9&cS!paGy+@ zffh~CDtfJy8KU9pK7s8})$mu#1o{4^-|~4CM*%6>dzSAq<||TvG{WQIcG-zlqw2>c zP|?78X5tgAADwX#@-BZ+eE*Duk-a0^B=5w;KFONBu39Z(LDTGY*>Y?WQa<1j8B+9P z_FxgH)b(X{!Z2K;yLj|`OTDSMWUdBYAf?5hlKgO8`U-n%>^=RyqEboIjFzfg!(L3K z?7P<1jwl>gOCZth*aYqas924SwPz76nlCQ8b<5Vr_=itI&o rUi5@hwC<|}KG8(Pmx#MM;q|&cI@t&O$CEekTgzL`m+`>B(GLDUt^P$0 literal 0 HcmV?d00001 diff --git a/logs/locale/fr/LC_MESSAGES/django.po b/logs/locale/fr/LC_MESSAGES/django.po new file mode 100644 index 00000000..70c58073 --- /dev/null +++ b/logs/locale/fr/LC_MESSAGES/django.po @@ -0,0 +1,338 @@ +# 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 © 2018 Maël Kervella +# +# 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. +msgid "" +msgstr "" +"Project-Id-Version: 2.5\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-08-15 20:12+0200\n" +"PO-Revision-Date: 2018-06-23 16:01+0200\n" +"Last-Translator: Laouen Fernet \n" +"Language-Team: \n" +"Language: fr_FR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: acl.py:42 +msgid "You don't have the right to view this application." +msgstr "Vous n'avez pas le droit de voir cette application." + +#: templates/logs/aff_stats_logs.html:36 +msgid "Edited object" +msgstr "Objet modifié" + +#: templates/logs/aff_stats_logs.html:37 +#: templates/logs/aff_stats_models.html:32 +msgid "Object type" +msgstr "Type d'objet" + +#: templates/logs/aff_stats_logs.html:38 +msgid "Edited by" +msgstr "Modifié par" + +#: templates/logs/aff_stats_logs.html:40 +msgid "Date of editing" +msgstr "Date de modification" + +#: templates/logs/aff_stats_logs.html:42 +msgid "Comment" +msgstr "Commentaire" + +#: templates/logs/aff_stats_logs.html:58 templates/logs/aff_summary.html:62 +#: templates/logs/aff_summary.html:85 templates/logs/aff_summary.html:104 +#: templates/logs/aff_summary.html:123 templates/logs/aff_summary.html:142 +msgid "Cancel" +msgstr "Annuler" + +#: templates/logs/aff_stats_models.html:29 +#, python-format +msgid "Statistics of the set %(key)s" +msgstr "Statistiques de l'ensemble %(key)s" + +#: templates/logs/aff_stats_models.html:33 +msgid "Number of stored entries" +msgstr "Nombre d'entrées enregistrées" + +#: templates/logs/aff_stats_users.html:31 +#, python-format +msgid "Statistics per %(key_dict)s of %(key)s" +msgstr "Statistiques par %(key_dict)s de %(key)s" + +#: templates/logs/aff_stats_users.html:34 +#, python-format +msgid "Number of %(key)s per %(key_dict)s" +msgstr "Nombre de %(key)s par %(key_dict)s" + +#: templates/logs/aff_stats_users.html:35 +msgid "Rank" +msgstr "Rang" + +#: templates/logs/aff_summary.html:37 +msgid "Date" +msgstr "Date" + +#: templates/logs/aff_summary.html:39 +msgid "Editing" +msgstr "Modification" + +#: templates/logs/aff_summary.html:48 +#, python-format +msgid "%(username)s has banned" +msgstr "%(username)s a banni" + +#: templates/logs/aff_summary.html:52 templates/logs/aff_summary.html:75 +msgid "No reason" +msgstr "Aucun motif" + +#: templates/logs/aff_summary.html:71 +#, python-format +msgid "%(username)s has graciously authorised" +msgstr "%(username)s a autorisé gracieusement" + +#: templates/logs/aff_summary.html:94 +#, python-format +msgid "%(username)s has updated" +msgstr "%(username)s a mis à jour" + +#: templates/logs/aff_summary.html:113 +#, python-format +msgid "%(username)s has sold %(number)sx %(name)s to" +msgstr "%(username)s a vendu %(number)sx %(name)s à" + +#: templates/logs/aff_summary.html:116 +#, python-format +msgid "+%(duration)s months" +msgstr "+%(duration)s mois" + +#: templates/logs/aff_summary.html:132 +#, python-format +msgid "%(username)s has edited an interface of" +msgstr "%(username)s a modifié une interface de" + +#: templates/logs/delete.html:29 +msgid "Deletion of actions" +msgstr "Suppression d'actions" + +#: templates/logs/delete.html:35 +#, python-format +msgid "" +"Warning: are you sure you want to delete this action %(objet_name)s " +"( %(objet)s )?" +msgstr "" +"Attention: voulez-vous vraiment supprimer cette action %(objet_name)s " +"( %(objet)s ) ?" + +#: templates/logs/delete.html:36 +msgid "Confirm" +msgstr "Confirmer" + +#: templates/logs/index.html:29 templates/logs/stats_general.html:29 +#: templates/logs/stats_logs.html:29 templates/logs/stats_models.html:29 +#: templates/logs/stats_users.html:29 +msgid "Statistics" +msgstr "Statistiques" + +#: templates/logs/index.html:32 templates/logs/stats_logs.html:32 views.py:403 +msgid "Actions performed" +msgstr "Actions effectuées" + +#: templates/logs/sidebar.html:33 +msgid "Summary" +msgstr "Résumé" + +#: templates/logs/sidebar.html:37 +msgid "Events" +msgstr "Évènements" + +#: templates/logs/sidebar.html:41 +msgid "General" +msgstr "Général" + +#: templates/logs/sidebar.html:45 +msgid "Database" +msgstr "Base de données" + +#: templates/logs/sidebar.html:49 +msgid "Wiring actions" +msgstr "Actions de câblage" + +#: templates/logs/sidebar.html:53 views.py:325 +msgid "Users" +msgstr "Utilisateurs" + +#: templates/logs/stats_general.html:32 +msgid "General statistics" +msgstr "Statistiques générales" + +#: templates/logs/stats_models.html:32 +msgid "Database statistics" +msgstr "Statistiques sur la base de données" + +#: templates/logs/stats_users.html:32 +msgid "Statistics about users" +msgstr "Statistiques sur les utilisateurs" + +#: views.py:191 +msgid "Nonexistent revision." +msgstr "Révision inexistante." + +#: views.py:194 +msgid "The action was deleted." +msgstr "L'action a été supprimée." + +#: views.py:227 +msgid "Category" +msgstr "Catégorie" + +#: views.py:228 +msgid "Number of users (members and clubs)" +msgstr "Nombre d'utilisateurs (adhérents et clubs)" + +#: views.py:229 +msgid "Number of members" +msgstr "Nombre d'adhérents" + +#: views.py:230 +msgid "Number of clubs" +msgstr "Nombre de clubs" + +#: views.py:234 +msgid "Activated users" +msgstr "Utilisateurs activés" + +#: views.py:242 +msgid "Disabled users" +msgstr "Utilisateurs désactivés" + +#: views.py:250 +msgid "Archived users" +msgstr "Utilisateurs archivés" + +#: views.py:258 +msgid "Contributing members" +msgstr "Adhérents cotisants" + +#: views.py:264 +msgid "Users benefiting from a connection" +msgstr "Utilisateurs bénéficiant d'une connexion" + +#: views.py:270 +msgid "Banned users" +msgstr "Utilisateurs bannis" + +#: views.py:276 +msgid "Users benefiting from a free connection" +msgstr "Utilisateurs bénéficiant d'une connexion gratuite" + +#: views.py:282 +msgid "Active interfaces (with access to the network)" +msgstr "Interfaces actives (ayant accès au réseau)" + +#: views.py:292 +msgid "Active interfaces assigned IPv4" +msgstr "Interfaces actives assignées IPv4" + +#: views.py:305 +msgid "IP range" +msgstr "Plage d'IP" + +#: views.py:306 +msgid "VLAN" +msgstr "VLAN" + +#: views.py:307 +msgid "Total number of IP addresses" +msgstr "Nombre total d'adresses IP" + +#: views.py:308 +msgid "Number of assigned IP addresses" +msgstr "Nombre d'adresses IP non assignées" + +#: views.py:309 +msgid "Number of IP address assigned to an activated machine" +msgstr "Nombre d'adresses IP assignées à une machine activée" + +#: views.py:310 +msgid "Number of nonassigned IP addresses" +msgstr "Nombre d'adresses IP non assignées" + +#: views.py:337 +msgid "Subscriptions" +msgstr "Cotisations" + +#: views.py:359 views.py:420 +msgid "Machines" +msgstr "Machines" + +#: views.py:386 +msgid "Topology" +msgstr "Topologie" + +#: views.py:405 +msgid "Number of actions" +msgstr "Nombre d'actions" + +#: views.py:419 views.py:437 views.py:442 views.py:447 views.py:462 +msgid "User" +msgstr "Utilisateur" + +#: views.py:423 +msgid "Invoice" +msgstr "Facture" + +#: views.py:426 +msgid "Ban" +msgstr "Bannissement" + +#: views.py:429 +msgid "Whitelist" +msgstr "Accès gracieux" + +#: views.py:432 +msgid "Rights" +msgstr "Droits" + +#: views.py:436 +msgid "School" +msgstr "Établissement" + +#: views.py:441 +msgid "Payment method" +msgstr "Moyen de paiement" + +#: views.py:446 +msgid "Bank" +msgstr "Banque" + +#: views.py:463 +msgid "Action" +msgstr "Action" + +#: views.py:494 +msgid "No model found." +msgstr "Aucun modèle trouvé." + +#: views.py:500 +msgid "Nonexistent entry." +msgstr "Entrée inexistante." + +#: views.py:507 +msgid "You don't have the right to access this menu." +msgstr "Vous n'avez pas le droit d'accéder à ce menu." diff --git a/logs/templates/logs/aff_stats_general.html b/logs/templates/logs/aff_stats_general.html index 49d067d0..662efa54 100644 --- a/logs/templates/logs/aff_stats_general.html +++ b/logs/templates/logs/aff_stats_general.html @@ -22,7 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. {% endcomment %} - {% for stats in stats_list %} +{% for stats in stats_list %} @@ -32,11 +32,12 @@ with this program; if not, write to the Free Software Foundation, Inc., {% for key, stat in stats.1.items %} - - {% for item in stat %} - - {% endfor %} - - {% endfor %} + + {% for item in stat %} + + {% endfor %} + + {% endfor %}
    {{ item }}
    {{ item }}
    - {% endfor %} +{% endfor %} + diff --git a/logs/templates/logs/aff_stats_logs.html b/logs/templates/logs/aff_stats_logs.html index 77e9e9b4..1ca79df9 100644 --- a/logs/templates/logs/aff_stats_logs.html +++ b/logs/templates/logs/aff_stats_logs.html @@ -28,39 +28,43 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load logs_extra %} {% load acl %} +{% load i18n %} - - +
    + + + + + {% trans "Edited by" as tr_edited_by %} + + {% trans "Date of editing" as tr_date_of_editing %} + + + + + + {% for revision in revisions_list %} + {% for reversion in revision.version_set.all %} - - - - - - + + + + + + {% can_edit_history %} + + {% acl_end %} - - {% for revision in revisions_list %} - {% for reversion in revision.version_set.all %} - - - - - - - {% can_edit_history %} - - {% acl_end %} - - {% endfor %} {% endfor %} -
    {% trans "Edited object" %}{% trans "Object type" %}{% include "buttons/sort.html" with prefix='logs' col='author' text=tr_edited_by %}{% include "buttons/sort.html" with prefix='logs' col='date' text=tr_date_of_editing %}{% trans "Comment" %}
    Objet modifiéType de l'objet{% include "buttons/sort.html" with prefix='logs' col='author' text='Modification par' %}{% include "buttons/sort.html" with prefix='logs' col='date' text='Date de modification' %}Commentaire{{ reversion.object|truncatechars:20 }}{{ reversion.object|classname }}{{ revision.user }}{{ revision.date_created }}{{ revision.comment }} + + + {% trans "Cancel" %} + +
    {{ reversion.object|truncatechars:20 }}{{ reversion.object|classname }}{{ revision.user }}{{ revision.date_created }}{{ revision.comment }} - - - Annuler - -
    + {% endfor %} +
    {% if revisions_list.paginator %} {% include "pagination.html" with list=revisions_list %} {% endif %} + diff --git a/logs/templates/logs/aff_stats_models.html b/logs/templates/logs/aff_stats_models.html index bd035f82..7809dfdf 100644 --- a/logs/templates/logs/aff_stats_models.html +++ b/logs/templates/logs/aff_stats_models.html @@ -22,20 +22,23 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. {% endcomment %} - {% for key, stats in stats_list.items %} +{% load i18n %} + +{% for key, stats in stats_list.items %} -

    Statistiques de l'ensemble {{ key }}

    - - - - - - - {% for key, stat in stats.items %} - - - - - {% endfor %} +

    {% blocktrans %}Statistics of the set {{ key }}{% endblocktrans %}

    + + + + + + + {% for key, stat in stats.items %} + + + + + {% endfor %}
    Type d'objetNombre d'entrée stockées
    {{ stat.0 }}{{ stat.1 }}
    {% trans "Object type" %}{% trans "Number of stored entries" %}
    {{ stat.0 }}{{ stat.1 }}
    - {% endfor %} +{% endfor %} + diff --git a/logs/templates/logs/aff_stats_users.html b/logs/templates/logs/aff_stats_users.html index f5b21c7e..0ea6a426 100644 --- a/logs/templates/logs/aff_stats_users.html +++ b/logs/templates/logs/aff_stats_users.html @@ -22,24 +22,27 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. {% endcomment %} - {% for key_dict, stats_dict in stats_list.items %} +{% load i18n %} + +{% for key_dict, stats_dict in stats_list.items %} {% for key, stats in stats_dict.items %} - - -

    Statistiques par {{ key_dict }} de {{ key }}

    - - - - - - - {% for stat in stats %} - - - - - - {% endfor %} -
    {{ key_dict }}Nombre de {{ key }} par {{ key_dict }}Rang
    {{ stat|truncatechars:25 }}{{ stat.num }}{{ forloop.counter }}
    - {% endfor %} - {% endfor %} + + +

    {% blocktrans %}Statistics per {{ key_dict }} of {{ key }}{% endblocktrans %}

    + + + + + + + {% for stat in stats %} + + + + + + {% endfor %} +
    {{ key_dict }}{% blocktrans %}Number of {{ key }} per {{ key_dict }}{% endblocktrans %}{% trans "Rank" %}
    {{ stat|truncatechars:25 }}{{ stat.num }}{{ forloop.counter }}
    + {% endfor %} +{% endfor %} + diff --git a/logs/templates/logs/aff_summary.html b/logs/templates/logs/aff_summary.html index f743d637..c9ebfe55 100644 --- a/logs/templates/logs/aff_summary.html +++ b/logs/templates/logs/aff_summary.html @@ -28,122 +28,127 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load logs_extra %} {% load acl %} - - - - - - - - + +{% load i18n %} + +
    {% include "buttons/sort.html" with prefix='sum' col='date' text='Date' %}Modification
    + + + {% trans "Date" as tr_date %} + + + + + {% for v in versions_list %} {% if v.version.content_type.model == 'ban' %} - {% can_edit_history %} + {% can_edit_history %} - {% acl_end %} + {% acl_end %} {% elif v.version.content_type.model == 'whitelist' %} - {% can_edit_history%} + {% can_edit_history%} - {% acl_end %} + {% acl_end %} {% elif v.version.content_type.model == 'user' %} - {% can_edit_history %} + {% can_edit_history %} - {% acl_end %} + {% acl_end %} {% elif v.version.content_type.model == 'vente' %} - {% can_edit_history %} + {% can_edit_history %} - {% acl_end %} + {% acl_end %} {% elif v.version.content_type.model == 'interface' %} - {% can_edit_history %} + {% can_edit_history %} - {% acl_end %} + {% acl_end %} {% endif %} {% endfor %} -
    {% include "buttons/sort.html" with prefix='sum' col='date' text=tr_date %}{% trans "Editing" %}
    {{ v.datetime }} - {{ v.username }} a banni + {% blocktrans with username=v.username %}{{ username }} has banned{% endblocktrans %} {{ v.version.object.user.get_username }} - ( + ( {% if v.version.object.raison == '' %} - Aucune raison + {% trans "No reason" %} {% else %} {{ v.version.object.raison }} {% endif %} ) - Annuler + {% trans "Cancel" %}
    {{ v.datetime }} - {{ v.username }} a autorisé gracieusement + {% blocktrans with username=v.username %}{{ username }} has graciously authorised{% endblocktrans %} {{ v.version.object.user.get_username }} ( {% if v.version.object.raison == '' %} - Aucune raison + {% trans "No reason" %} {% else %} {{ v.version.object.raison }} {% endif %} ) - Annuler + {% trans "Cancel" %}
    {{ v.datetime }} - {{ v.username }} a mis à jour + {% blocktrans with username=v.username %}{{ username }} has updated{% endblocktrans %} {{ v.version.object.get_username }} - {% if v.comment != '' %} - ({{ v.comment }}) - {% endif %} + {% if v.comment != '' %} + ({{ v.comment }}) + {% endif %} - Annuler + {% trans "Cancel" %}
    {{ v.datetime }} - {{ v.username }} a vendu {{ v.version.object.number }}x {{ v.version.object.name }} à + {% blocktrans with username=v.username number=v.version.object.number name=v.version.object.name %}{{ username }} has sold {{ number }}x {{ name }} to{% endblocktrans %} {{ v.version.object.facture.user.get_username }} - {% if v.version.object.iscotisation %} - (+{{ v.version.object.duration }} mois) - {% endif %} + {% if v.version.object.iscotisation %} + ({% blocktrans with duration=v.version.object.duration %}+{{ duration }} months{% endblocktrans %}) + {% endif %} - Annuler + {% trans "Cancel" %}
    {{ v.datetime }} - {{ v.username }} a modifié une interface de + {% blocktrans with username=v.username %}{{ username }} has edited an interface of{% endblocktrans %} {{ v.version.object.machine.user.get_username }} - {% if v.comment != '' %} - ({{ v.comment }}) - {% endif %} + {% if v.comment != '' %} + ({{ v.comment }}) + {% endif %} - Annuler + {% trans "Cancel" %}
    +
    {% if versions_list.paginator %} {% include "pagination.html" with list=versions_list %} {% endif %} + diff --git a/logs/templates/logs/delete.html b/logs/templates/logs/delete.html index 8bda7cb6..6ad11195 100644 --- a/logs/templates/logs/delete.html +++ b/logs/templates/logs/delete.html @@ -24,17 +24,20 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load bootstrap3 %} +{% load i18n %} -{% block title %}Supression d'action{% endblock %} +{% block title %}{% trans "Deletion of actions" %}{% endblock %} {% block content %}
    {% csrf_token %} -

    Attention, voulez-vous vraiment annuler cette action {{ objet_name }} ( {{ objet }} ) ?

    - {% bootstrap_button "Confirmer" button_type="submit" icon="trash" %} +

    {% blocktrans %}Warning: are you sure you want to delete this action {{ objet_name }} ( {{ objet }} )?{% endblocktrans %}

    + {% trans "Confirm" as tr_confirm %} + {% bootstrap_button tr_confirm button_type="submit" icon="trash" %}
    -
    -
    -
    +
    +
    +
    {% endblock %} + diff --git a/logs/templates/logs/index.html b/logs/templates/logs/index.html index a120a531..dde47c7d 100644 --- a/logs/templates/logs/index.html +++ b/logs/templates/logs/index.html @@ -24,13 +24,15 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load bootstrap3 %} +{% load i18n %} -{% block title %}Statistiques{% endblock %} +{% block title %}{%trans "Statistics" %}{% endblock %} {% block content %} -

    Actions effectuées

    - {% include "logs/aff_summary.html" with versions_list=versions_list %} -
    -
    -
    - {% endblock %} +

    {% trans "Actions performed" %}

    + {% include "logs/aff_summary.html" with versions_list=versions_list %} +
    +
    +
    +{% endblock %} + diff --git a/logs/templates/logs/sidebar.html b/logs/templates/logs/sidebar.html index 0e3048e3..87011cfc 100644 --- a/logs/templates/logs/sidebar.html +++ b/logs/templates/logs/sidebar.html @@ -24,32 +24,34 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load acl %} +{% load i18n %} {% block sidebar %} {% can_view_app logs %} - Résumé + {% trans "Summary" %} - Évènements + {% trans "Events" %} - Général + {% trans "General" %} - Base de données + {% trans "Database" %} - Actions de cablage + {% trans "Wiring actions" %} - Utilisateurs + {% trans "Users" %} {% acl_end %} {% endblock %} + diff --git a/logs/templates/logs/stats_general.html b/logs/templates/logs/stats_general.html index b8590df1..07e3ec26 100644 --- a/logs/templates/logs/stats_general.html +++ b/logs/templates/logs/stats_general.html @@ -24,13 +24,15 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load bootstrap3 %} +{% load i18n %} -{% block title %}Statistiques générales{% endblock %} +{% block title %}{% trans "Statistics" %}{% endblock %} {% block content %} -

    Statistiques générales

    - {% include "logs/aff_stats_general.html" with stats_list=stats_list %} -
    -
    -
    - {% endblock %} +

    {% trans "General statistics" %}

    + {% include "logs/aff_stats_general.html" with stats_list=stats_list %} +
    +
    +
    +{% endblock %} + diff --git a/logs/templates/logs/stats_logs.html b/logs/templates/logs/stats_logs.html index 4db77c68..4f547cc3 100644 --- a/logs/templates/logs/stats_logs.html +++ b/logs/templates/logs/stats_logs.html @@ -24,13 +24,15 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load bootstrap3 %} +{% load i18n %} -{% block title %}Statistiques{% endblock %} +{% block title %}{% trans "Statistics" %}{% endblock %} {% block content %} -

    Actions effectuées

    - {% include "logs/aff_stats_logs.html" with revisions_list=revisions_list %} -
    -
    -
    - {% endblock %} +

    {% trans "Actions performed" %}

    + {% include "logs/aff_stats_logs.html" with revisions_list=revisions_list %} +
    +
    +
    +{% endblock %} + diff --git a/logs/templates/logs/stats_models.html b/logs/templates/logs/stats_models.html index 0ed28525..9b912da2 100644 --- a/logs/templates/logs/stats_models.html +++ b/logs/templates/logs/stats_models.html @@ -24,13 +24,15 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load bootstrap3 %} +{% load i18n %} -{% block title %}Statistiques des objets base de données{% endblock %} +{% block title %}{% trans "Statistics" %}{% endblock %} {% block content %} -

    Statistiques bdd

    - {% include "logs/aff_stats_models.html" with stats_list=stats_list %} -
    -
    -
    - {% endblock %} +

    {% trans "Database statistics" %}

    + {% include "logs/aff_stats_models.html" with stats_list=stats_list %} +
    +
    +
    +{% endblock %} + diff --git a/logs/templates/logs/stats_users.html b/logs/templates/logs/stats_users.html index fa0843ec..8cc645ab 100644 --- a/logs/templates/logs/stats_users.html +++ b/logs/templates/logs/stats_users.html @@ -24,13 +24,15 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load bootstrap3 %} +{% load i18n %} -{% block title %}Statistiques par utilisateur{% endblock %} +{% block title %}{% trans "Statistics" %}{% endblock %} {% block content %} -

    Statistiques par utilisateur

    - {% include "logs/aff_stats_users.html" with stats_list=stats_list %} -
    -
    -
    - {% endblock %} +

    {% trans "Statistics about users" %}

    + {% include "logs/aff_stats_users.html" with stats_list=stats_list %} +
    +
    +
    +{% endblock %} + diff --git a/logs/views.py b/logs/views.py index d7ba59f3..a9fe5418 100644 --- a/logs/views.py +++ b/logs/views.py @@ -188,10 +188,10 @@ def revert_action(request, revision_id): try: revision = Revision.objects.get(id=revision_id) except Revision.DoesNotExist: - messages.error(request, u"Revision inexistante") + messages.error(request, _("Nonexistent revision.")) if request.method == "POST": revision.revert() - messages.success(request, "L'action a été supprimée") + messages.success(request, _("The action was deleted.")) return redirect(reverse('logs:index')) return form({ 'objet': revision, @@ -224,14 +224,14 @@ def stats_general(request): stats = [ [ # First set of data (about users) [ # Headers - "Categorie", - "Nombre d'utilisateurs (total club et adhérents)", - "Nombre d'adhérents", - "Nombre de clubs" + _("Category"), + _("Number of users (members and clubs)"), + _("Number of members"), + _("Number of clubs") ], { # Data 'active_users': [ - "Users actifs", + _("Activated users"), User.objects.filter(state=User.STATE_ACTIVE).count(), (Adherent.objects .filter(state=Adherent.STATE_ACTIVE) @@ -239,7 +239,7 @@ def stats_general(request): Club.objects.filter(state=Club.STATE_ACTIVE).count() ], 'inactive_users': [ - "Users désactivés", + _("Disabled users"), User.objects.filter(state=User.STATE_DISABLED).count(), (Adherent.objects .filter(state=Adherent.STATE_DISABLED) @@ -247,7 +247,7 @@ def stats_general(request): Club.objects.filter(state=Club.STATE_DISABLED).count() ], 'archive_users': [ - "Users archivés", + _("Archived users"), User.objects.filter(state=User.STATE_ARCHIVE).count(), (Adherent.objects .filter(state=Adherent.STATE_ARCHIVE) @@ -255,31 +255,31 @@ def stats_general(request): Club.objects.filter(state=Club.STATE_ARCHIVE).count() ], 'adherent_users': [ - "Cotisant à l'association", + _("Contributing members"), _all_adherent.count(), _all_adherent.exclude(adherent__isnull=True).count(), _all_adherent.exclude(club__isnull=True).count() ], 'connexion_users': [ - "Utilisateurs bénéficiant d'une connexion", + _("Users benefiting from a connection"), _all_has_access.count(), _all_has_access.exclude(adherent__isnull=True).count(), _all_has_access.exclude(club__isnull=True).count() ], 'ban_users': [ - "Utilisateurs bannis", + _("Banned users"), _all_baned.count(), _all_baned.exclude(adherent__isnull=True).count(), _all_baned.exclude(club__isnull=True).count() ], 'whitelisted_user': [ - "Utilisateurs bénéficiant d'une connexion gracieuse", + _("Users benefiting from a free connection"), _all_whitelisted.count(), _all_whitelisted.exclude(adherent__isnull=True).count(), _all_whitelisted.exclude(club__isnull=True).count() ], 'actives_interfaces': [ - "Interfaces actives (ayant accès au reseau)", + _("Active interfaces (with access to the network)"), _all_active_interfaces_count.count(), (_all_active_interfaces_count .exclude(machine__user__adherent__isnull=True) @@ -289,7 +289,7 @@ def stats_general(request): .count()) ], 'actives_assigned_interfaces': [ - "Interfaces actives et assignées ipv4", + _("Active interfaces assigned IPv4"), _all_active_assigned_interfaces_count.count(), (_all_active_assigned_interfaces_count .exclude(machine__user__adherent__isnull=True) @@ -302,12 +302,12 @@ def stats_general(request): ], [ # Second set of data (about ip adresses) [ # Headers - "Range d'ip", - "Vlan", - "Nombre d'ip totales", - "Ip assignées", - "Ip assignées à une machine active", - "Ip non assignées" + _("IP range"), + _("VLAN"), + _("Total number of IP addresses"), + _("Number of assigned IP addresses"), + _("Number of IP address assigned to an activated machine"), + _("Number of nonassigned IP addresses") ], ip_dict # Data already prepared ] @@ -322,79 +322,87 @@ def stats_models(request): nombre d'users, d'écoles, de droits, de bannissements, de factures, de ventes, de banque, de machines, etc""" stats = { - 'Users': { - 'users': [User.PRETTY_NAME, User.objects.count()], - 'adherents': [Adherent.PRETTY_NAME, Adherent.objects.count()], - 'clubs': [Club.PRETTY_NAME, Club.objects.count()], - 'serviceuser': [ServiceUser.PRETTY_NAME, + _("Users"): { + 'users': [User._meta.verbose_name, User.objects.count()], + 'adherents': [Adherent._meta.verbose_name, Adherent.objects.count()], + 'clubs': [Club._meta.verbose_name, Club.objects.count()], + 'serviceuser': [ServiceUser._meta.verbose_name, ServiceUser.objects.count()], - 'school': [School.PRETTY_NAME, School.objects.count()], - 'listright': [ListRight.PRETTY_NAME, ListRight.objects.count()], - 'listshell': [ListShell.PRETTY_NAME, ListShell.objects.count()], - 'ban': [Ban.PRETTY_NAME, Ban.objects.count()], - 'whitelist': [Whitelist.PRETTY_NAME, Whitelist.objects.count()] + 'school': [School._meta.verbose_name, School.objects.count()], + 'listright': [ListRight._meta.verbose_name, ListRight.objects.count()], + 'listshell': [ListShell._meta.verbose_name, ListShell.objects.count()], + 'ban': [Ban._meta.verbose_name, Ban.objects.count()], + 'whitelist': [Whitelist._meta.verbose_name, Whitelist.objects.count()] }, - 'Cotisations': { + _("Subscriptions"): { 'factures': [ - Facture._meta.verbose_name.title(), + Facture._meta.verbose_name, Facture.objects.count() ], 'vente': [ - Vente._meta.verbose_name.title(), + Vente._meta.verbose_name, Vente.objects.count() ], 'cotisation': [ - Cotisation._meta.verbose_name.title(), + Cotisation._meta.verbose_name, Cotisation.objects.count() ], 'article': [ - Article._meta.verbose_name.title(), + Article._meta.verbose_name, Article.objects.count() ], 'banque': [ - Banque._meta.verbose_name.title(), + Banque._meta.verbose_name, Banque.objects.count() ], }, - 'Machines': { - 'machine': [Machine.PRETTY_NAME, Machine.objects.count()], - 'typemachine': [MachineType.PRETTY_NAME, + _("Machines"): { + 'machine': [Machine._meta.verbose_name, + Machine.objects.count()], + 'typemachine': [MachineType._meta.verbose_name, MachineType.objects.count()], - 'typeip': [IpType.PRETTY_NAME, IpType.objects.count()], - 'extension': [Extension.PRETTY_NAME, Extension.objects.count()], - 'interface': [Interface.PRETTY_NAME, Interface.objects.count()], - 'alias': [Domain.PRETTY_NAME, + 'typeip': [IpType._meta.verbose_name, + IpType.objects.count()], + 'extension': [Extension._meta.verbose_name, + Extension.objects.count()], + 'interface': [Interface._meta.verbose_name, + Interface.objects.count()], + 'alias': [Domain._meta.verbose_name, Domain.objects.exclude(cname=None).count()], - 'iplist': [IpList.PRETTY_NAME, IpList.objects.count()], - 'service': [Service.PRETTY_NAME, Service.objects.count()], + 'iplist': [IpList._meta.verbose_name, + IpList.objects.count()], + 'service': [Service._meta.verbose_name, + Service.objects.count()], 'ouvertureportlist': [ - OuverturePortList.PRETTY_NAME, + OuverturePortList._meta.verbose_name, OuverturePortList.objects.count() ], - 'vlan': [Vlan.PRETTY_NAME, Vlan.objects.count()], - 'SOA': [SOA.PRETTY_NAME, SOA.objects.count()], - 'Mx': [Mx.PRETTY_NAME, Mx.objects.count()], - 'Ns': [Ns.PRETTY_NAME, Ns.objects.count()], - 'nas': [Nas.PRETTY_NAME, Nas.objects.count()], + 'vlan': [Vlan._meta.verbose_name, Vlan.objects.count()], + 'SOA': [SOA._meta.verbose_name, SOA.objects.count()], + 'Mx': [Mx._meta.verbose_name, Mx.objects.count()], + 'Ns': [Ns._meta.verbose_name, Ns.objects.count()], + 'nas': [Nas._meta.verbose_name, Nas.objects.count()], }, - 'Topologie': { - 'switch': [Switch.PRETTY_NAME, Switch.objects.count()], - 'bornes': [AccessPoint.PRETTY_NAME, AccessPoint.objects.count()], - 'port': [Port.PRETTY_NAME, Port.objects.count()], - 'chambre': [Room.PRETTY_NAME, Room.objects.count()], - 'stack': [Stack.PRETTY_NAME, Stack.objects.count()], + _("Topology"): { + 'switch': [Switch._meta.verbose_name, + Switch.objects.count()], + 'bornes': [AccessPoint._meta.verbose_name, + AccessPoint.objects.count()], + 'port': [Port._meta.verbose_name, Port.objects.count()], + 'chambre': [Room._meta.verbose_name, Room.objects.count()], + 'stack': [Stack._meta.verbose_name, Stack.objects.count()], 'modelswitch': [ - ModelSwitch.PRETTY_NAME, + ModelSwitch._meta.verbose_name, ModelSwitch.objects.count() ], 'constructorswitch': [ - ConstructorSwitch.PRETTY_NAME, + ConstructorSwitch._meta.verbose_name, ConstructorSwitch.objects.count() ], }, - 'Actions effectuées sur la base': + _("Actions performed"): { - 'revision': ["Nombre d'actions", Revision.objects.count()], + 'revision': [_("Number of actions"), Revision.objects.count()], }, } return render(request, 'logs/stats_models.html', {'stats_list': stats}) @@ -408,35 +416,35 @@ def stats_users(request): de moyens de paiements par user, de banque par user, de bannissement par user, etc""" stats = { - 'Utilisateur': { - 'Machines': User.objects.annotate( + _("User"): { + _("Machines"): User.objects.annotate( num=Count('machine') ).order_by('-num')[:10], - 'Facture': User.objects.annotate( + _("Invoice"): User.objects.annotate( num=Count('facture') ).order_by('-num')[:10], - 'Bannissement': User.objects.annotate( + _("Ban"): User.objects.annotate( num=Count('ban') ).order_by('-num')[:10], - 'Accès gracieux': User.objects.annotate( + _("Whitelist"): User.objects.annotate( num=Count('whitelist') ).order_by('-num')[:10], - 'Droits': User.objects.annotate( + _("Rights"): User.objects.annotate( num=Count('groups') ).order_by('-num')[:10], }, - 'Etablissement': { - 'Utilisateur': School.objects.annotate( + _("School"): { + _("User"): School.objects.annotate( num=Count('user') ).order_by('-num')[:10], }, - 'Moyen de paiement': { - 'Utilisateur': Paiement.objects.annotate( + _("Payment method"): { + _("User"): Paiement.objects.annotate( num=Count('facture') ).order_by('-num')[:10], }, - 'Banque': { - 'Utilisateur': Banque.objects.annotate( + _("Bank"): { + _("User"): Banque.objects.annotate( num=Count('facture') ).order_by('-num')[:10], }, @@ -451,8 +459,8 @@ def stats_actions(request): utilisateurs. Affiche le nombre de modifications aggrégées par utilisateurs""" stats = { - 'Utilisateur': { - 'Action': User.objects.annotate( + _("User"): { + _("Action"): User.objects.annotate( num=Count('revision') ).order_by('-num')[:40], }, @@ -489,14 +497,14 @@ def history(request, application, object_name, object_id): try: instance = model.get_instance(**kwargs) except model.DoesNotExist: - messages.error(request, _("No entry found.")) + messages.error(request, _("Nonexistent entry.")) return redirect(reverse( 'users:profil', kwargs={'userid': str(request.user.id)} )) can, msg = instance.can_view(request.user) if not can: - messages.error(request, msg or _("You cannot acces to this menu")) + messages.error(request, msg or _("You don't have the right to access this menu.")) return redirect(reverse( 'users:profil', kwargs={'userid': str(request.user.id)} @@ -513,3 +521,4 @@ def history(request, application, object_name, object_id): 're2o/history.html', {'reversions': reversions, 'object': instance} ) + From 6f1ccaf3061c305a4802ba82a7fe72c50e9599f7 Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Sun, 19 Aug 2018 17:26:11 +0200 Subject: [PATCH 148/171] =?UTF-8?q?Pas=20de=20redondance=20dans=20les=20dr?= =?UTF-8?q?oits=20h=C3=A9rit=C3=A9s=20sur=20Club=20et=20Adherent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- users/migrations/0076_auto_20180818_1321.py | 4 +-- users/models.py | 34 ++------------------- 2 files changed, 5 insertions(+), 33 deletions(-) diff --git a/users/migrations/0076_auto_20180818_1321.py b/users/migrations/0076_auto_20180818_1321.py index 45fd1a62..95af1422 100644 --- a/users/migrations/0076_auto_20180818_1321.py +++ b/users/migrations/0076_auto_20180818_1321.py @@ -18,7 +18,7 @@ class Migration(migrations.Migration): operations = [ migrations.AlterModelOptions( name='adherent', - options={'permissions': (('change_user_password', 'Can change the password of a user'), ('change_user_state', 'Can edit the state of a user'), ('change_user_force', 'Can force the move'), ('change_user_shell', 'Can edit the shell of a user'), ('change_user_groups', 'Can edit the groups of rights of a user (critical permission)'), ('change_all_users', 'Can edit all users, including those with rights.'), ('view_user', 'Can view a user object')), 'verbose_name': 'member', 'verbose_name_plural': 'members'}, + options={'verbose_name': 'member', 'verbose_name_plural': 'members'}, ), migrations.AlterModelOptions( name='ban', @@ -26,7 +26,7 @@ class Migration(migrations.Migration): ), migrations.AlterModelOptions( name='club', - options={'permissions': (('change_user_password', 'Can change the password of a user'), ('change_user_state', 'Can edit the state of a user'), ('change_user_force', 'Can force the move'), ('change_user_shell', 'Can edit the shell of a user'), ('change_user_groups', 'Can edit the groups of rights of a user (critical permission)'), ('change_all_users', 'Can edit all users, including those with rights.'), ('view_user', 'Can view a user object')), 'verbose_name': 'club', 'verbose_name_plural': 'clubs'}, + options={'verbose_name': 'club', 'verbose_name_plural': 'clubs'}, ), migrations.AlterModelOptions( name='emailaddress', diff --git a/users/models.py b/users/models.py index b5874512..de342031 100755 --- a/users/models.py +++ b/users/models.py @@ -1027,21 +1027,7 @@ class Adherent(User): )] ) - class Meta: - permissions = ( - ("change_user_password", - _("Can change the password of a user")), - ("change_user_state", _("Can edit the state of a user")), - ("change_user_force", _("Can force the move")), - ("change_user_shell", _("Can edit the shell of a user")), - ("change_user_groups", - _("Can edit the groups of rights of a user (critical" - " permission)")), - ("change_all_users", - _("Can edit all users, including those with rights.")), - ("view_user", - _("Can view a user object")), - ) + class Meta(User.Meta): verbose_name = _("member") verbose_name_plural = _("members") @@ -1100,21 +1086,7 @@ class Club(User): default=False ) - class Meta: - permissions = ( - ("change_user_password", - _("Can change the password of a user")), - ("change_user_state", _("Can edit the state of a user")), - ("change_user_force", _("Can force the move")), - ("change_user_shell", _("Can edit the shell of a user")), - ("change_user_groups", - _("Can edit the groups of rights of a user (critical" - " permission)")), - ("change_all_users", - _("Can edit all users, including those with rights.")), - ("view_user", - _("Can view a user object")), - ) + class Meta(User.Meta): verbose_name = _("club") verbose_name_plural = _("clubs") @@ -1847,7 +1819,7 @@ class EMailAddress(RevMixin, AclMixin, models.Model): if self.local_part == self.user.pseudo.lower(): return False, _("You can't delete a local email account whose" " local part is the same as the username.") - if user_request.has_perm('users.delete_emailaddress'): + if user_request.has_perm('users.delete_emailaddress'): return True, None if not OptionalUser.get_cached_value('local_email_accounts_enabled'): return False, _("The local email accounts are not enabled.") From 4a9f643339efe6bef9987893664e1c328e9f4401 Mon Sep 17 00:00:00 2001 From: grisel-davy Date: Thu, 16 Aug 2018 11:53:24 +0200 Subject: [PATCH 149/171] =?UTF-8?q?l=20au=20lieux=20de=20user.mail=20pour?= =?UTF-8?q?=20g=C3=A9nerer=20la=20ml?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/serializers.py b/api/serializers.py index 2e938a35..65a82eb6 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -872,7 +872,7 @@ class MailingMemberSerializer(UserSerializer): """Serialize the data about a mailing member. """ class Meta(UserSerializer.Meta): - fields = ('name', 'pseudo', 'email') + fields = ('name', 'pseudo', 'get_mail') class MailingSerializer(ClubSerializer): """Serialize the data about a mailing. From 615c20830fbff660de81dd59677bd7b2030992ec Mon Sep 17 00:00:00 2001 From: grisel-davy Date: Sat, 18 Aug 2018 10:27:41 +0200 Subject: [PATCH 150/171] jout d'un message sur la page des services --- re2o/templates/re2o/index.html | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/re2o/templates/re2o/index.html b/re2o/templates/re2o/index.html index 28adf055..db6eb119 100644 --- a/re2o/templates/re2o/index.html +++ b/re2o/templates/re2o/index.html @@ -30,7 +30,19 @@ with this program; if not, write to the Free Software Foundation, Inc., {% block title %}{% trans "Home" %}{% endblock %} {% block content %} -

    {% blocktrans %}Welcome to {{ name_website }}!{% endblocktrans %}

    + +
    +
    +

    {% blocktrans %}Welcome to {{ name_website }} !{% endblocktrans %}

    +
    +
    +{% if request.user.is_authenticated %} +Vous trouverez ci-dessous les principaux services proposés par le {{ assoname }}. Pour toute action concernant l'administration de votre adhésion/connexion ou de vos machines, rendez vous sur votre profil. +{% else %} +Pour bénéficier d'une connexion internet et profiter des services de l'association, connectez vous avec vos identifiants. Si vous n'avez pas de compte, créez en un. +{% endif %} +
    +
    {% for service_list in services_urls %} From dd79b866013865900b3cdf06b07679c2bd2e1f3c Mon Sep 17 00:00:00 2001 From: grisel-davy Date: Sat, 18 Aug 2018 13:10:36 +0200 Subject: [PATCH 151/171] Clarification mineur du AdherentForm --- users/forms.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/users/forms.py b/users/forms.py index f1b50652..fb70ec8b 100644 --- a/users/forms.py +++ b/users/forms.py @@ -316,6 +316,8 @@ class AdherentForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): self.fields['room'].label = _("Room") self.fields['room'].empty_label = _("No room") self.fields['school'].empty_label = _("Select a school") + self.fields['shell'].empty_label = _("Shell par défaut") + self.fields['gpg_fingerprint'].widget.attrs['placeholder'] = _("Laissez vide si vous ne disposez pas de clef GPG") def clean_email(self): if not OptionalUser.objects.first().local_email_domain in self.cleaned_data.get('email'): @@ -334,9 +336,9 @@ class AdherentForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): 'email', 'school', 'comment', + 'telephone', 'room', 'shell', - 'telephone', 'gpg_fingerprint' ] From c7f00a11974563b5c2af12cefe78352bcdc5243a Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Sat, 18 Aug 2018 23:51:14 +0200 Subject: [PATCH 152/171] More clear welcome page --- re2o/templates/re2o/index.html | 50 ++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/re2o/templates/re2o/index.html b/re2o/templates/re2o/index.html index db6eb119..b2d2944b 100644 --- a/re2o/templates/re2o/index.html +++ b/re2o/templates/re2o/index.html @@ -30,20 +30,52 @@ with this program; if not, write to the Free Software Foundation, Inc., {% block title %}{% trans "Home" %}{% endblock %} {% block content %} - -
    -
    -

    {% blocktrans %}Welcome to {{ name_website }} !{% endblocktrans %}

    +
    +

    {% blocktrans %}Welcome to {{ name_website }}{% endblocktrans %}

    +
    +{% if not request.user.is_authenticated %} +
    +
    +
    +
    +
    +

    Inscription

    +

    Si vous n'avez pas encore de compte et que vous souhaitez accéder à internet et aux services de l'association, créez votre espace personnel

    +

    Inscription

    +
    +
    +
    +
    +
    +
    +
    +
    +

    Indentification

    +

    Si avez déjà un compte, identifiez-vous. Vous pouvez gérer votre cotisation à l'association, vos machines, et l'ensemble de vos services

    +

    Identification

    +
    +
    +
    -
    -{% if request.user.is_authenticated %} -Vous trouverez ci-dessous les principaux services proposés par le {{ assoname }}. Pour toute action concernant l'administration de votre adhésion/connexion ou de vos machines, rendez vous sur votre profil. {% else %} -Pour bénéficier d'une connexion internet et profiter des services de l'association, connectez vous avec vos identifiants. Si vous n'avez pas de compte, créez en un. -{% endif %} +
    +
    +
    +
    +
    +

    Mon profil

    +

    Pour gérer votre cotisation, vos machines et l'ensemble de vos services, accédez à votre profil

    +

    Accéder à mon profil

    +
    +
    +
    +{% endif %}
    +
    +

    {% blocktrans %}Les services de l'association{% endblocktrans %}

    +
    {% for service_list in services_urls %}
    From 7f021621aded21e95bf98e427c3aff22ee4a516d Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Thu, 23 Aug 2018 02:18:24 +0200 Subject: [PATCH 153/171] Marquage trad + fix si autoregister disabled --- re2o/templates/re2o/index.html | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/re2o/templates/re2o/index.html b/re2o/templates/re2o/index.html index b2d2944b..e87b2bb7 100644 --- a/re2o/templates/re2o/index.html +++ b/re2o/templates/re2o/index.html @@ -35,24 +35,26 @@ with this program; if not, write to the Free Software Foundation, Inc.,
    {% if not request.user.is_authenticated %}
    +{% if var_sa %}
    -

    Inscription

    -

    Si vous n'avez pas encore de compte et que vous souhaitez accéder à internet et aux services de l'association, créez votre espace personnel

    -

    Inscription

    +

    {% blocktrans %}Inscription{% endblocktrans %}

    +

    {% blocktrans %}Si vous n'avez pas encore de compte et que vous souhaitez accéder à internet et aux services de l'association, créez votre espace personnel{% endblocktrans %}

    +

    {% blocktrans %}Inscription{% endblocktrans %}

    +{% endif %}
    -

    Indentification

    -

    Si avez déjà un compte, identifiez-vous. Vous pouvez gérer votre cotisation à l'association, vos machines, et l'ensemble de vos services

    -

    Identification

    +

    {% blocktrans %}Indentification{% endblocktrans %}

    +

    {% blocktrans %}Si avez déjà un compte, identifiez-vous. Vous pouvez gérer votre cotisation à l'association, vos machines, et l'ensemble de vos services{% endblocktrans %}

    +

    {% blocktrans %}Indentification{% endblocktrans %}

    @@ -63,9 +65,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
    -

    Mon profil

    -

    Pour gérer votre cotisation, vos machines et l'ensemble de vos services, accédez à votre profil

    -

    Accéder à mon profil

    +

    {% blocktrans %}Mon profil{% endblocktrans %}

    +

    {% blocktrans %}Pour gérer votre cotisation, vos machines et l'ensemble de vos services, accédez à votre profil{% endblocktrans %}

    +

    {% blocktrans %}Accéder à mon profil{% endblocktrans %}

    From 547e6d0915c53efb12079300ce8283d16d1cc93a Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Thu, 23 Aug 2018 02:54:28 +0200 Subject: [PATCH 154/171] Prolongation de connexion avant expiration --- users/templates/users/profil.html | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/users/templates/users/profil.html b/users/templates/users/profil.html index 0d391551..09d499d0 100644 --- a/users/templates/users/profil.html +++ b/users/templates/users/profil.html @@ -49,7 +49,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% blocktrans with end_ban=users.end_ban|date:"SHORT_DATE_FORMAT" %}End of the ban: {{ end_ban }}{% endblocktrans %}
    - {% elif not users.is_adherent %} + {% elif not users.has_access %}
    {% trans "No connection" %}
    @@ -64,9 +64,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
    {% else %}
    -
    {% trans "Connection" %}
    +
    {% trans "Connection" %}{% blocktrans with end_connection=user.end_access|date:"SHORT_DATE_FORMAT" %} ( Until {{ end_connection }} ){% endblocktrans %}
    - {% blocktrans with end_connection=user.end_adhesion|date:"SHORT_DATE_FORMAT" %}End of connection: {{ end_connection }}{% endblocktrans %} + {% can_create Facture %} + + {% trans "Extend my connection period" %} + + {% acl_end %}
    {% endif %} @@ -89,7 +93,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% if nb_machines %}
    - {% trans " Machines" %}{{ nb_machines }} + {% trans " Machines" %} {{ nb_machines }}
    From aa2d6365e76cd83b1d5c19490ed701d318fbc13f Mon Sep 17 00:00:00 2001 From: Laouen Fernet Date: Thu, 23 Aug 2018 15:05:04 +0200 Subject: [PATCH 155/171] translation --- re2o/locale/fr/LC_MESSAGES/django.mo | Bin 4617 -> 5657 bytes re2o/locale/fr/LC_MESSAGES/django.po | 54 ++++- re2o/templates/re2o/index.html | 20 +- users/forms.py | 4 +- users/locale/fr/LC_MESSAGES/django.mo | Bin 25257 -> 25505 bytes users/locale/fr/LC_MESSAGES/django.po | 306 +++++++++++++------------- users/templates/users/profil.html | 4 +- 7 files changed, 220 insertions(+), 168 deletions(-) diff --git a/re2o/locale/fr/LC_MESSAGES/django.mo b/re2o/locale/fr/LC_MESSAGES/django.mo index 7e9e55c389615faa748b4e0bb05a7c64997fdd46..72cde11123bdec09725205a6db29b466a77770f5 100644 GIT binary patch delta 1925 zcma))TZkM*6ozZF8?!MRvzyi3#OPYlBoocd?B?Q&3Na6PSV@AK7`%Yk_SDR_&Qy=x zJrg&GjRr3Pg}@MS5mX3ySnzJwqM0S!pr^hHF!(_K5r#(+gve|0%k zbZvhGn4~Yp@x6J8{p(hrH0`Yl>Gv{1x`au)PDFX zdG-Kzzv4K#Cb!!UcVZXAJt=aZWw3>Xi?}rd|Au$LnYBvY4UfQ$ z@MS2{z5^%VhmfP{D=5lbfa2(na1#CrCGzo`@`yJ;vD*qYEWwnZ)FB2U@#7F@l?OM# zI+O#i!8hUC@GUs8PAP`!3>5p1q1b&6Z-!sH{fkf>yaJ_ues}XPkx%abZXEreWN?`U ziFhxkrPzEZ2Cu^@_&%J5UpQWcyO>W=2qNVpQ06CK37&&{;AM!wSz!Zp4}Fv_&TpfS zNd84Ci9#B+lSLr%;&9eP`U0I2$>fy9?Q|^z8gFFj%O?E9aZy9s9dv@uf)b@A7*Sg4 zXa{}#0Kd(2YOmjl{ZXE8nM$FFLZmd4RProM>O-pfR+)54gP)D<^L-P?x-qYtQCJP? zrt|CIC+Tce&xbAT)g$9o=JlM{Hrlh=^Zl@8lcKJNH67TpegY$(jfQ8vnvpY+j$6;h zeiSs5AhbFObuwpk7}Y!*#Gb4bbF+r$&jr@R+*{$Hx;t#5cHpNbE-U9lCA5WPxd(dQ zB-ugn@|b56c?j<$6nSsfCML4ZX1N1}SjX8jCN6592ovXdc+BdiiQ;gx_3BH(meql< zRtu~ItPU>8)O~O0uNCQ|rWV9Wl!c`ZyE}DQy(VULB)qOh|3eRP)J2y`yp*biRjuy2 z;nM@dyLRiH_uec`6S&(Yo75Rt}}JwqMVZ<_Yz% ml>T)jDrUj|w<7&Q{C8xz#(CPxo#JTPd2nn~_x$L-fqwv+Ags6m delta 887 zcmYk)O-K}B9LMqh?5;1n=1bdJns2kZg^`luOM*xtbm*XJWj5hw$a(!P9z*2)aa2s0aQ2#z*jBW`^$ei{YiN*e*Qh$O?m|>9mcR6okj`{%B z;X|aW&0y&}`|_9=)1R_~?4`?+TcKOasuwnY26G4B+9)KG^2 diff --git a/re2o/locale/fr/LC_MESSAGES/django.po b/re2o/locale/fr/LC_MESSAGES/django.po index 86ed6316..f54fdcc1 100644 --- a/re2o/locale/fr/LC_MESSAGES/django.po +++ b/re2o/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-08-15 16:11+0200\n" +"POT-Creation-Date: 2018-08-23 15:03+0200\n" "PO-Revision-Date: 2018-03-31 16:09+0002\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" @@ -185,12 +185,56 @@ msgstr "Historique de %(object)s" msgid "Home" msgstr "Accueil" -#: templates/re2o/index.html:33 +#: templates/re2o/index.html:34 #, python-format -msgid "Welcome to %(name_website)s!" -msgstr "Bienvenue sur %(name_website)s !" +msgid "Welcome to %(name_website)s" +msgstr "Bienvenue sur %(name_website)s" -#: templates/re2o/index.html:47 +#: templates/re2o/index.html:43 templates/re2o/index.html:45 +msgid "Registration" +msgstr "Inscription" + +#: templates/re2o/index.html:44 +msgid "" +"If you don't have an account yet and you want to access the Internet and the " +"organisation's services, create your own personal account." +msgstr "" +"Si vous n'avez pas encore de compte et que vous voulez accéder à Internet et " +"aux services de l'association, créez votre espace personnel." + +#: templates/re2o/index.html:55 templates/re2o/index.html:57 +msgid "Logging in" +msgstr "Identification" + +#: templates/re2o/index.html:56 +msgid "" +"If you already have an account, log in. You can manage your subscription to " +"the organisation, your machines and all your services." +msgstr "" +"Si vous avez déjà un compte, identifiez-vous. Vous pouvez gérer votre " +"cotisation à l'association, vos machines et tous vos services." + +#: templates/re2o/index.html:68 +msgid "My profile" +msgstr "Mon profil" + +#: templates/re2o/index.html:69 +msgid "" +"To manage your subscription, your machines and all your services, access " +"your profile." +msgstr "" +"Pour gérer votre cotisation, vos machines et tous vos services, accéder à " +"votre profil." + +#: templates/re2o/index.html:70 +msgid "Access my profile" +msgstr "Accéder à mon profil" + +#: templates/re2o/index.html:79 +msgid "Services of the organisation" +msgstr "Serices de l'association" + +#: templates/re2o/index.html:93 msgid "Go there" msgstr "Accéder" diff --git a/re2o/templates/re2o/index.html b/re2o/templates/re2o/index.html index e87b2bb7..f7adacbc 100644 --- a/re2o/templates/re2o/index.html +++ b/re2o/templates/re2o/index.html @@ -40,9 +40,9 @@ with this program; if not, write to the Free Software Foundation, Inc., @@ -52,9 +52,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
    -

    {% blocktrans %}Indentification{% endblocktrans %}

    -

    {% blocktrans %}Si avez déjà un compte, identifiez-vous. Vous pouvez gérer votre cotisation à l'association, vos machines, et l'ensemble de vos services{% endblocktrans %}

    -

    {% blocktrans %}Indentification{% endblocktrans %}

    +

    {% trans "Logging in" %}

    +

    {% trans "If you already have an account, log in. You can manage your subscription to the organisation, your machines and all your services." %}

    +

    {% trans "Logging in" %}

    @@ -65,9 +65,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
    -

    {% blocktrans %}Mon profil{% endblocktrans %}

    -

    {% blocktrans %}Pour gérer votre cotisation, vos machines et l'ensemble de vos services, accédez à votre profil{% endblocktrans %}

    -

    {% blocktrans %}Accéder à mon profil{% endblocktrans %}

    +

    {% trans "My profile" %}

    +

    {% trans "To manage your subscription, your machines and all your services, access your profile." %}

    +

    {% trans "Access my profile" %}

    @@ -76,7 +76,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
    -

    {% blocktrans %}Les services de l'association{% endblocktrans %}

    +

    {% trans "Services of the organisation" %}

    {% for service_list in services_urls %} diff --git a/users/forms.py b/users/forms.py index fb70ec8b..6af499bd 100644 --- a/users/forms.py +++ b/users/forms.py @@ -316,8 +316,8 @@ class AdherentForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): self.fields['room'].label = _("Room") self.fields['room'].empty_label = _("No room") self.fields['school'].empty_label = _("Select a school") - self.fields['shell'].empty_label = _("Shell par défaut") - self.fields['gpg_fingerprint'].widget.attrs['placeholder'] = _("Laissez vide si vous ne disposez pas de clef GPG") + self.fields['shell'].empty_label = _("Default shell") + self.fields['gpg_fingerprint'].widget.attrs['placeholder'] = _("Leave empty if you don't have any GPG key.") def clean_email(self): if not OptionalUser.objects.first().local_email_domain in self.cleaned_data.get('email'): diff --git a/users/locale/fr/LC_MESSAGES/django.mo b/users/locale/fr/LC_MESSAGES/django.mo index ef4052cee9d92180376084d26911a13ad70e05f8..9391372760cb02dcc767caa08a1d0571089bd2f5 100644 GIT binary patch delta 7030 zcmZwLd0bXi9>?(mir|7EDuSR~7G+To#eD&B-?vK59py1m1Q9T;v1eSz(ozR)TFi1u z!(1lOnamaq)2tk`Y_ZIxG54`dZFF+R`Fy#D#y{r1`0+mHJoldEcg}tAW$6b#>kj#N zPlZ%nZMfR_7!!#HsvEO`a&BF<8go0`m{7cfwK2G%F@YF`x+NKFVJ6ncK^TwO*cg}D z_Sdl<_5B!xUm`SisaF4Cu zLT!OxoI5~6)QXy0yP#gjLAE{?yHn4@A-EIOZxs%ZCR!Jj;Yid35>ONHrcp?u&;^y! zDX6_$fa+ikvM93^HQ-Uy#7-eU<_5pC(r^xl2I_!1L)}ml?uR#J95us}$PMNKR>jbGH?wc*FvauTGptf`ds=p_!PofU% zD)iU;zlDN2+KKAmJzGD3dhi%(Z%?9DcmvhJT~tOw65NbLBR{6Cbv(AGz64o=*^dEu z5&iKxMlrs*LqVwtPjpw@4z+^GsDY;2`T|rcpF*YZB~&JM*w1&P7Vx%pKWZxv+Rsm; z20m}=*U+myzC}R;`Ed}GvItbiiKq;;KuxHlt!JV>AAq`l7;;|C1gwq=Q3E}LIs?z* zhqxKFfEmr)j4fzp-~Z=m$l!ym_JgZvVrkgC6_^)nRB0_fWOLiZ2@ML;F-zrpizQ97dgu zv#2e+Z4FIz-}~06w`C~mzR9TP=UPj=6f$TiMRj-y^?*-HPB@05_HYntB9EX>`+R%= z*PyoICsbyBK@HS_ZyUNYLog8Mp;EsbgK<6Tta!^P)S>VuDgz%O**BLl07KKjp}$c4#Q2zZkRjBkBM*X-ZvU`MrLCTz5nwm_|ot+K8Pz&9sGcU zv3(o&y?teV59mHvuVFpPhND20*MXjF!pVh}b*Wu!CK#erB4vruo-Ec9o5 zGuJjOQUmp6s4t{dsJ&Z<>Uan0!QEIN-^MWf4Eg(IuAmNcU`PH0`>_A02`?vY8h9(} z4DH7TcoMzZ^P3dHv0i6pjVY+qjz&GO81+`HL!Fg4Vu@Lz&yZEJXe(pm4wc@CBcZKn&4w6wb9EcID4G zdr^TH>)U&!tFQ$tM_&H zJ_~hf3s5V49M!=x>no_&XcxxfSyblyIN};O8a44ws7w^1o?n42a3fZ{|Hml=&~OuV zIR1@VS*`x=-p8XuJp;9p3D^>6Vs+ezHE_GF??JvKO*vM!m8PvqCp$^||tc7(3x|wK<+UiaN z$-f31OG6thzy#ck?1uRiwbF2obWQA!)o=vrZ5W3-RC6&EU$LJbLuK%5RAz6W{#xF( z1`T!>7UQL$4qKvD&>oeEbkqR7ur_)y2#ZjuUWhuJ%Tar~1C`QGPy?MsorRmI1qKgs z`-wx1lZIN5w*v*OuqOuNqo_Swf_kl%;di(J9h^7RUGaKU3O~RH@dE15b=Kc*A7h52 z-l7o?xc}6eiyQ;98Asy{Wb3_VFi$t7p$PTBi|F8f)Yi_r4aGOno?Ns}`b8 z|29m;W9XpoaQC@b97(-DYQh^)_aDR$d$kYNdBk zuWcy3E7b+4EqNL{;5O9RxPV$njfdTO3sjPZVNJ$2uTtoSZ=k;6Zen)~q4z)>j_NoY z!*L;M@7ALBehWHy2-EQ*>P*Fx=M+pqo&K@t;8cvjrRYthu$e*_9z(XpT*gkAKF00v zG1Ot)gF*PU^&4Dd~@G zsjo%OllcNWV*CVm&nKcjUx9o_nM0_FhGe;amIt90G#`28%~sU?cd#L*GCO6WKPs~$ zCwkpKtCMNaYcUOju>kAfeCsN#OMMgSi{&lUYj^hN@O73?8eVV@!&28Hh{FKmPYRN-uY`Z47gE&GwPYfjf zNa(6cko}4)mS?7yYZirLL@*C)3v@k4aAZufo%wLe&l0~Q z77*IP%1Z}a*DV*XSw$tjoQ+-f%H5Su4^0>zYe6)rxc?#}buSwk=%8r-(<0$B5c=d>JE&29%rFfvB3} z#8-sg_b}QM@B3^rZ7&iZ5ML5HxSNP=gvPJ9^gim_uNo0Rd_rs?bbU@FSCsfy3q}yF z8K63`i*fHgcAB%9YwSt{zOC*8;A&Qu7dg! z*+&Etjfp#iuGYjxB2N{r2V5%N#U1; z3zLXLww`4bf!v>ggKe81{z`NuJ|a31TZt6zJB*u&hLpWGDFjgX1Fj=5jf%q6j-_chFWa zo~&6O$1{CK(HtjdvNI>Y*vZb%i!XAfs68uhj?;TcZ)cikPDsN@=_*VR5q&ncHfeeUQ-&BKUu^AXL_96EGN5oW_hW{$z}m1%e#e^ZRpk8H#sY( vu+a0EGb@MY!W?H-esQ6b7tdTDqr<|An{&%cHR%*Hpd@2L{jz&~wuJu|IiMze delta 6761 zcmZwLeSFUK9>?+TwL3F5?1s(FcFoOfw%JU^tj!p+MY`EC5i>SXnWA$}le#)PRBF^| zDWn^e5K$MRl0!vqA|d9Wk~+<)NOBV$=k>mR-#t!$obO|w>-qituHWx_^Z9+hzstko z9j+B0yF4et1Lhe@PnR+6aa~hm7Em7=p|i%UjWMP*zJ+1<35Mcf)Ft0ybG(dE7}VC7 zuGk)9aUkmac#OnqY=Lu;t~}-?DlIv&96RAAA?Jg8o=+!7)=DT)C5woIsKb#D$O`ih#FZ5YGxIfh!0>eE=D!D5(nZs zRL7UmA6@hsfPt8cAsCNEn2vX24lY7&F-Il+n~PM!F_LL&q+L-r=AdR+hS4|+`7H`IWG-Od2wQ3K0BJ)!}q3>Ue{zeX~e6H4(U%*Uyyk#0bB z{2pqi+tIf+Py@JxYAA?wX%V(X&A2NnBYo^~32K6ssDV6$TFi6eJx)W5IH3ktVi9gc zvT6J}8pC8vJo0Dy^Pz!OpdQU+Y=w2G0ltRn_M+L8bCB48*0VXS@Qnk2j#M`wWBd5b6;gL$!C>`YUSD>aC=K zwnnuRivimI$@WA#>c$+@vn)i-ung6}L{vr|MrC9UCgKw7ZtTPHd8AL1%9pyo1e@S! zROTvCnVE&%>EA4+5`>?lI;ywFXHcoUfJ$N0BqtN?P}j$!GLUFZMLo(4`~479$G6(! zk*G&pj_T(=^eAPIQBjKLp)&9Ss)J?rc(wh0BkKAOkacJFU{gGc>gYGr8u07J?*vAm zCU6Xuu`{Uc8jx&EKa5T$|N3GCCmL`vYVnn(IH{VB12|rYx8QNqwoFSk#*M>JDV>7a z-%lay*t~AtgBs}1$kyB%(?KuP+PD$* z3@fZPsCW7+s0nRGUH3Wa{s!wc)XbyOo%U|?P|*#Ou{YMBp5eQwfgC^$>?bV7;2zGS zx&xJ&ap-&TAeWf;F%*A9rTz-G#IOu!t;As~jyt0=;K`&y*3EDX#u{V+nf}5%cc_fGj)9Ex*gaQPhklDgnoD#OSJ#5Qc(l9TxZNp_$q4O zpGUnJTlO-h0EZ&$$JALrz(E{0pgK;=bbgjcBTvmNK}~GGJ^me);S@gT(o|rE_Wyh; zx^Oq<;6)^BrWc!8Gq@A|aVBcJJ#L+Y%Fr{|0_USLvK%9D6Gq~vsNHlDwM!c8@y|M@ ze{+dSbJRa;^z1@W4YxzzKF27I6EPZdk>4j%idxOpIL5_q25P`p`Z^s)lRmAXRBVHV zsAoPFWAG95u%paEDoX7R)D7oQyCU>@XR)N9o?$L(riG|&H3HS(4Aiz;gxVEbF%Q2) zWz@}w`Wb`Dz>}y6*5#0YHL!pa8sR2vhx@TT{)kFtNUk&DSk%D!paxost#K;Smzj-P z#M@B=-iJ3}J*vab{hUm9LuI;mKMyYvDuXzoMf5D{hHci+JZG_tz%HDx#SZv3#^GnE zOq@k!AhN&nXtFSY<58&lYA_a;qaN7~)WA=BsA%S{0ZwXTP|rLGwb;__`Ch0Q3_`7i zA=nJdQ4LK-&3LADE~?%67=~*x9=D(-@HMI(&owGq3{m+`s?$(6=A#-Mg<8dxs1B!F zA4O&E8C1%@MlH_Ms7L2N(CNsHdQ`cnjNNXnMjo}tETGbnFV>^pOh-^7JdNr&c#yNL zI-q9S9kp8q;b6QK)!{d5(%wy$tmN zS&M35tMw3e;c?rT~Qgj9-Ck}YJiha6Mbk1 z`PYc%azegnzgT6DH=|Ox2LtgWD)rx^26h3p=*&=OO~jxwk$_t5`KS&nu@_cjBCbJ_ zX^spf|C(w1jm}~zM$MoMwO=Qo7Sk+D$L03>dQ=L(LZ$W!hGPI*LbgFos2i%iEYt+@ zP?;z|wLi>5C5*~s)T?y{D%G{9XZJj6M(?0fdJxq?18OZ?Ma?k!Ca0YgR7cs^4Ev)d zcq1z19@Hay8nv4|&r&%~?3{jQWRi8AC-4--9t&i@I?c>e;`I zZrq3bkeRcnMU}?rx??6b$4YeLRMdd$FbUUSG}a@@Hs>%K3wTZ*HCRJMt8pu~z^|-l z(T`(u2mgNq{x}Zr#Id*wlQDU;^XyBJWnz}1&Y#B&bdPZ|QjD1#FGDS(<73EwUn*&3 z&NIIYb-{e(-D37(Hntt>{0t9A&FBea3!8PQ>s{r}cI<=7L@|cqSPZ}_)UKF@EwLI~ z;S=Q^r?Q9>5uA7v^TQ0Fr+4hyg&PC(r^+qwW< z9ILcch5pTa9ocd}UPq|$r-|o?zY<4?cEqDZ8o?Se{Rx#Pi6|NiRf80DxRE$UtRNbT zCiId$(1PWiN`Ha@YS4;<4{gCr%|O`MUP|^kw^SQs3==XwaPT&AhDdNC3yV4 zf6`yUM~HRA1H^Qqqryf1i>SOy#L`h8Y%F_h#f`IVUHf0%f*) zHXBKpNIb;(8X|~#dwb1V&3}MBD8NBPjJ@%7>MT@W`GrrtG*m7*`1bgA>RN#9xaTUa zC(@}ez^TONgvt_PxUc4X?1Nlljh_FPL;^Q`i*FE13H^8MY3z)=;>~YFV`fg7;+VJHv>P?7Q#A}2~TjE!u8{d~; zCsdhB=$B7LTQ1t3??C-|B97zk1n(+ynOICTmKnD4!yne439t=3$d~@aXGA>b>hL0= z^1Xw36(1)uh?B%9Lgn8?Juy=Uw)Dlji4(+ogqP?>sC=yXzeA-pkxC>JhlwJh8J%Ci zqlD7boA{Y%%K3+}vBcTR*Z3k)N$e#uxGn+UScDI8Ug=6FJ0PI-rEm!4SMgtR^NCjpfb83MO;i6!gQluq&!;Av|~RF_aigY$j6d zmmg98goq*L5GpPBt`qv>2K*=Crrr~`5@m$SLI<-SC)#=kuIoXlq*=RY|G&VAaYQuH znlJ9fFbpRm?S-83l^uLG5Zj2q`OY{$T&2`MAc7mO$NzAAhWHDi5>E6Ys)>2TDdLao z&jfPQpL{2skH5pS#BD@{bIQDoD{Wm=5xWQ%_lDs}Vkgm90&OL~@dSQK93YB`bLD)f zyiNEKsWg;`XNi796OL0*WfReiXhl3md_{ansAM^qJFVhoqP@#|A?BCBxCE1Sv diff --git a/users/locale/fr/LC_MESSAGES/django.po b/users/locale/fr/LC_MESSAGES/django.po index 6c637db9..d1cb14c6 100644 --- a/users/locale/fr/LC_MESSAGES/django.po +++ b/users/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-08-18 13:36+0200\n" +"POT-Creation-Date: 2018-08-23 15:10+0200\n" "PO-Revision-Date: 2018-06-27 23:35+0200\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" @@ -39,7 +39,7 @@ msgstr "Vous n'avez pas le droit de voir cette application." msgid "Current password" msgstr "Mot de passe actuel" -#: forms.py:75 forms.py:444 +#: forms.py:75 forms.py:446 msgid "New password" msgstr "Nouveau mot de passe" @@ -55,7 +55,7 @@ msgstr "Les nouveaux mots de passe ne correspondent pas." msgid "The current password is incorrect." msgstr "Le mot de passe actuel est incorrect." -#: forms.py:125 forms.py:178 models.py:1578 +#: forms.py:125 forms.py:178 models.py:1550 msgid "Password" msgstr "Mot de passe" @@ -83,8 +83,8 @@ msgstr "L'utilisateur est admin : %s" #: forms.py:284 templates/users/aff_clubs.html:38 #: templates/users/aff_listright.html:63 templates/users/aff_listright.html:168 -#: templates/users/aff_users.html:39 templates/users/profil.html:165 -#: templates/users/profil.html:279 templates/users/profil.html:298 +#: templates/users/aff_users.html:39 templates/users/profil.html:169 +#: templates/users/profil.html:283 templates/users/profil.html:302 msgid "Username" msgstr "Pseudo" @@ -94,111 +94,119 @@ msgstr "" "Impossible d'archiver des utilisateurs dont la date de fin de connexion est " "dans le futur." -#: forms.py:311 templates/users/profil.html:278 templates/users/profil.html:297 +#: forms.py:311 templates/users/profil.html:282 templates/users/profil.html:301 msgid "First name" msgstr "Prénom" #: forms.py:312 templates/users/aff_users.html:37 -#: templates/users/profil.html:161 templates/users/profil.html:277 -#: templates/users/profil.html:296 +#: templates/users/profil.html:165 templates/users/profil.html:281 +#: templates/users/profil.html:300 msgid "Surname" msgstr "Nom" -#: forms.py:313 models.py:1579 templates/users/aff_emailaddress.html:36 -#: templates/users/profil.html:167 +#: forms.py:313 models.py:1551 templates/users/aff_emailaddress.html:36 +#: templates/users/profil.html:171 msgid "Email address" msgstr "Adresse mail" -#: forms.py:314 forms.py:382 forms.py:513 templates/users/aff_schools.html:37 -#: templates/users/profil.html:177 +#: forms.py:314 forms.py:384 forms.py:515 templates/users/aff_schools.html:37 +#: templates/users/profil.html:181 msgid "School" msgstr "Établissement" -#: forms.py:315 forms.py:383 models.py:1232 -#: templates/users/aff_serviceusers.html:34 templates/users/profil.html:179 +#: forms.py:315 forms.py:385 models.py:1204 +#: templates/users/aff_serviceusers.html:34 templates/users/profil.html:183 msgid "Comment" msgstr "Commentaire" -#: forms.py:316 forms.py:384 templates/users/aff_clubs.html:40 -#: templates/users/aff_users.html:41 templates/users/profil.html:171 +#: forms.py:316 forms.py:386 templates/users/aff_clubs.html:40 +#: templates/users/aff_users.html:41 templates/users/profil.html:175 msgid "Room" msgstr "Chambre" -#: forms.py:317 forms.py:385 +#: forms.py:317 forms.py:387 msgid "No room" msgstr "Pas de chambre" -#: forms.py:318 forms.py:386 +#: forms.py:318 forms.py:388 msgid "Select a school" msgstr "Sélectionnez un établissement" -#: forms.py:325 forms.py:653 +#: forms.py:319 +msgid "Default shell" +msgstr "Interface système par défaut" + +#: forms.py:320 +msgid "Leave empty if you don't have any GPG key." +msgstr "Laissez vide si vous n'avez pas de clé GPG." + +#: forms.py:327 forms.py:655 msgid "You can't use a {} address." msgstr "Vous ne pouvez pas utiliser une adresse {}." -#: forms.py:350 forms.py:408 +#: forms.py:352 forms.py:410 msgid "A valid telephone number is required." msgstr "Un numéro de téléphone valide est requis." -#: forms.py:361 +#: forms.py:363 msgid "Force the move?" msgstr "Forcer le déménagement ?" -#: forms.py:381 templates/users/aff_clubs.html:36 +#: forms.py:383 templates/users/aff_clubs.html:36 #: templates/users/aff_serviceusers.html:32 msgid "Name" msgstr "Nom" -#: forms.py:387 +#: forms.py:389 msgid "Use a mailing list" msgstr "Utiliser une liste de diffusion" -#: forms.py:501 templates/users/aff_listright.html:38 +#: forms.py:503 templates/users/aff_listright.html:38 msgid "Superuser" msgstr "Superutilisateur" -#: forms.py:525 +#: forms.py:527 msgid "Shell name" msgstr "Nom de l'interface système" -#: forms.py:544 +#: forms.py:546 msgid "Name of the group of rights" msgstr "Nom du groupe de droits" -#: forms.py:555 +#: forms.py:557 msgid "GID. Warning: this field must not be edited after creation." msgstr "GID. Attention : ce champ ne doit pas être modifié après création." -#: forms.py:563 +#: forms.py:565 msgid "Current groups of rights" msgstr "Groupes de droits actuels" -#: forms.py:580 +#: forms.py:582 msgid "Current schools" msgstr "Établissements actuels" -#: forms.py:598 forms.py:612 templates/users/aff_bans.html:41 +#: forms.py:600 forms.py:614 templates/users/aff_bans.html:41 #: templates/users/aff_whitelists.html:41 msgid "End date" msgstr "Date de fin" -#: forms.py:626 models.py:1776 +#: forms.py:628 models.py:1748 msgid "Local part of the email address" msgstr "Partie locale de l'adresse mail" -#: forms.py:627 +#: forms.py:629 msgid "Can't contain @" msgstr "Ne peut pas contenir @" -#: forms.py:642 +#: forms.py:644 msgid "Main email address" msgstr "Adresse mail principale" -#: forms.py:644 +#: forms.py:646 msgid "Redirect local emails" msgstr "Rediriger les mails locaux" -#: forms.py:646 +#: forms.py:648 msgid "Use local emails" msgstr "Utiliser les mails locaux" @@ -215,7 +223,7 @@ msgstr "Les utilisateurs doivent avoir un pseudo." msgid "Username should only contain [a-z0-9-]." msgstr "Le pseudo devrait seulement contenir [a-z0-9-]" -#: models.py:199 models.py:1223 +#: models.py:199 models.py:1195 msgid "Must only contain letters, numerals or dashes." msgstr "Doit seulement contenir des lettres, chiffres ou tirets." @@ -237,33 +245,33 @@ msgstr "Activer le compte mail local" msgid "Comment, school year" msgstr "Commentaire, promotion" -#: models.py:255 models.py:1033 models.py:1106 +#: models.py:255 msgid "Can change the password of a user" msgstr "Peut changer le mot de passe d'un utilisateur" -#: models.py:256 models.py:1034 models.py:1107 +#: models.py:256 msgid "Can edit the state of a user" msgstr "Peut changer l'état d'un utilisateur" -#: models.py:257 models.py:1035 models.py:1108 +#: models.py:257 msgid "Can force the move" msgstr "Peut forcer le déménagement" -#: models.py:258 models.py:1036 models.py:1109 +#: models.py:258 msgid "Can edit the shell of a user" msgstr "Peut modifier l'interface système d'un utilisateur" -#: models.py:260 models.py:1038 models.py:1111 +#: models.py:260 msgid "Can edit the groups of rights of a user (critical permission)" msgstr "" "Peut modifier les groupes de droits d'un utilisateur (permission critique)" -#: models.py:263 models.py:1041 models.py:1114 +#: models.py:263 msgid "Can edit all users, including those with rights." msgstr "" "Peut modifier tous les utilisateurs, y compris ceux possédant des droits." -#: models.py:265 models.py:1043 models.py:1116 +#: models.py:265 msgid "Can view a user object" msgstr "Peut voir un objet utilisateur" @@ -357,7 +365,7 @@ msgstr "Vous n'avez pas le droit de voir ce club." msgid "You don't have the right to view another user." msgstr "Vous n'avez pas le droit de voir un autre utilisateur." -#: models.py:956 models.py:1155 +#: models.py:956 models.py:1127 msgid "You don't have the right to view the list of users." msgstr "Vous n'avez pas le droit de voir la liste des utilisateurs." @@ -385,174 +393,174 @@ msgstr "" msgid "A GPG fingerprint must contain 40 hexadecimal characters." msgstr "Une empreinte GPG doit contenir 40 caractères hexadécimaux." -#: models.py:1045 +#: models.py:1031 msgid "member" msgstr "adhérent" -#: models.py:1046 +#: models.py:1032 msgid "members" msgstr "adhérents" -#: models.py:1075 +#: models.py:1061 msgid "You don't have the right to create a user." msgstr "Vous n'avez pas le droit de créer un utilisateur." -#: models.py:1118 +#: models.py:1090 msgid "club" msgstr "club" -#: models.py:1119 +#: models.py:1091 msgid "clubs" msgstr "clubs" -#: models.py:1137 +#: models.py:1109 msgid "You don't have the right to create a club." msgstr "Vous n'avez pas le droit de créer un club." -#: models.py:1242 +#: models.py:1214 msgid "Can view a service user object" msgstr "Peut voir un objet utilisateur service" -#: models.py:1244 +#: models.py:1216 msgid "service user" msgstr "utilisateur service" -#: models.py:1245 +#: models.py:1217 msgid "service users" msgstr "utilisateurs service" -#: models.py:1249 +#: models.py:1221 #, python-brace-format msgid "Service user <{name}>" msgstr "Utilisateur service <{name}>" -#: models.py:1311 +#: models.py:1283 msgid "Can view a school object" msgstr "Peut voir un objet établissement" -#: models.py:1313 +#: models.py:1285 msgid "school" msgstr "établissement" -#: models.py:1314 +#: models.py:1286 msgid "schools" msgstr "établissements" -#: models.py:1332 +#: models.py:1304 msgid "UNIX groups can only contain lower case letters." msgstr "Les groupes UNIX peuvent seulement contenir des lettres minuscules." -#: models.py:1338 +#: models.py:1310 msgid "Description" msgstr "Description" -#: models.py:1345 +#: models.py:1317 msgid "Can view a group of rights object" msgstr "Peut voir un objet groupe de droits" -#: models.py:1347 +#: models.py:1319 msgid "group of rights" msgstr "groupe de droits" -#: models.py:1348 +#: models.py:1320 msgid "groups of rights" msgstr "groupes de droits" -#: models.py:1395 +#: models.py:1367 msgid "Can view a shell object" msgstr "Peut voir un objet interface système" -#: models.py:1397 +#: models.py:1369 msgid "shell" msgstr "interface système" -#: models.py:1398 +#: models.py:1370 msgid "shells" msgstr "interfaces système" -#: models.py:1417 +#: models.py:1389 msgid "HARD (no access)" msgstr "HARD (pas d'accès)" -#: models.py:1418 +#: models.py:1390 msgid "SOFT (local access only)" msgstr "SOFT (accès local uniquement)" -#: models.py:1419 +#: models.py:1391 msgid "RESTRICTED (speed limitation)" msgstr "RESTRICTED (limitation de vitesse)" -#: models.py:1430 +#: models.py:1402 msgid "Can view a ban object" msgstr "Peut voir un objet bannissement" -#: models.py:1432 +#: models.py:1404 msgid "ban" msgstr "bannissement" -#: models.py:1433 +#: models.py:1405 msgid "bans" msgstr "bannissements" -#: models.py:1467 +#: models.py:1439 msgid "You don't have the right to view bans other than yours." msgstr "" "Vous n'avez pas le droit de voir des bannissements autres que les vôtres." -#: models.py:1515 +#: models.py:1487 msgid "Can view a whitelist object" msgstr "Peut voir un objet accès gracieux" -#: models.py:1517 +#: models.py:1489 msgid "whitelist (free of charge access)" msgstr "Accès gracieux" -#: models.py:1518 +#: models.py:1490 msgid "whitelists (free of charge access)" msgstr "Accès gracieux" -#: models.py:1534 +#: models.py:1506 msgid "You don't have the right to view whitelists other than yours." msgstr "" "Vous n'avez pas le droit de voir des accès gracieux autres que les vôtres." -#: models.py:1771 +#: models.py:1743 msgid "User of the local email account" msgstr "Utilisateur du compte mail local" -#: models.py:1781 +#: models.py:1753 msgid "Can view a local email account object" msgstr "Peut voir un objet compte mail local" -#: models.py:1783 +#: models.py:1755 msgid "local email account" msgstr "compte mail local" -#: models.py:1784 +#: models.py:1756 msgid "local email accounts" msgstr "comptes mail locaux" -#: models.py:1808 models.py:1831 models.py:1853 models.py:1875 +#: models.py:1780 models.py:1803 models.py:1825 models.py:1847 msgid "The local email accounts are not enabled." msgstr "Les comptes mail locaux ne sont pas activés." -#: models.py:1810 +#: models.py:1782 msgid "You don't have the right to add a local email account to another user." msgstr "" "Vous n'avez pas le droit d'ajouter un compte mail local à un autre " "utilisateur." -#: models.py:1813 +#: models.py:1785 msgid "You reached the limit of {} local email accounts." msgstr "Vous avez atteint la limite de {] comptes mail locaux." -#: models.py:1834 models.py:1878 +#: models.py:1806 models.py:1850 msgid "You don't have the right to edit another user's local email account." msgstr "" "Vous n'avez pas le droit de modifier le compte mail local d'un autre " "utilisateur." -#: models.py:1848 +#: models.py:1820 msgid "" "You can't delete a local email account whose local part is the same as the " "username." @@ -560,13 +568,13 @@ msgstr "" "Vous ne pouvez pas supprimer un compte mail local dont la partie locale est " "la même que le pseudo." -#: models.py:1856 +#: models.py:1828 msgid "You don't have the right to delete another user's local email account" msgstr "" "Vous n'avez pas le droit de supprimer le compte mail local d'un autre " "utilisateur." -#: models.py:1870 +#: models.py:1842 msgid "" "You can't edit a local email account whose local part is the same as the " "username." @@ -574,7 +582,7 @@ msgstr "" "Vous ne pouvez pas modifier un compte mail local dont la partie locale est " "la même que le pseudo." -#: models.py:1884 +#: models.py:1856 msgid "The local part must not contain @." msgstr "La partie locale ne doit pas contenir @." @@ -595,7 +603,7 @@ msgid "End of subscription on" msgstr "Fin de cotisation le" #: templates/users/aff_clubs.html:43 templates/users/aff_users.html:44 -#: templates/users/profil.html:218 +#: templates/users/profil.html:222 msgid "Internet access" msgstr "Accès Internet" @@ -605,17 +613,17 @@ msgid "Profile" msgstr "Profil" #: templates/users/aff_clubs.html:53 templates/users/aff_users.html:54 -#: templates/users/profil.html:193 +#: templates/users/profil.html:197 msgid "Not a member" msgstr "Non adhérent" #: templates/users/aff_clubs.html:55 templates/users/aff_users.html:57 -#: templates/users/profil.html:210 +#: templates/users/profil.html:214 msgid "Active" msgstr "Actif" #: templates/users/aff_clubs.html:57 templates/users/aff_users.html:59 -#: templates/users/profil.html:212 templates/users/profil.html:222 +#: templates/users/profil.html:216 templates/users/profil.html:226 msgid "Disabled" msgstr "Désactivé" @@ -682,11 +690,11 @@ msgstr "Utilisateurs dans %(right_name)s" msgid "Role" msgstr "Rôle" -#: templates/users/aff_shell.html:32 templates/users/profil.html:248 +#: templates/users/aff_shell.html:32 templates/users/profil.html:252 msgid "Shell" msgstr "Interface système" -#: templates/users/aff_users.html:35 templates/users/profil.html:158 +#: templates/users/aff_users.html:35 templates/users/profil.html:162 msgid "Firt name" msgstr "Prénom" @@ -718,7 +726,7 @@ msgstr "Confirmer" msgid "Users" msgstr "Utilisateurs" -#: templates/users/index_ban.html:32 templates/users/profil.html:373 +#: templates/users/index_ban.html:32 templates/users/profil.html:377 #: templates/users/sidebar.html:58 msgid "Bans" msgstr "Bannissements" @@ -789,7 +797,7 @@ msgstr "Liste des interaces système" msgid " Add a shell" msgstr " Ajouter une interface système" -#: templates/users/index_whitelist.html:32 templates/users/profil.html:398 +#: templates/users/index_whitelist.html:32 templates/users/profil.html:402 #: templates/users/sidebar.html:64 msgid "Whitelists" msgstr "Accès gracieux" @@ -841,196 +849,196 @@ msgstr "" "Demandez à quelqu'un ayant les droits appropriés de payer une connexion." #: templates/users/profil.html:67 -msgid "Connection" -msgstr "Connexion" - -#: templates/users/profil.html:69 #, python-format -msgid "End of connection: %(end_connection)s" -msgstr "Fin de connexion : %(end_connection)s" +msgid "Connection (until %(end_connection)s )" +msgstr "Connexion (jusqu'au %(end_connection)s)" -#: templates/users/profil.html:82 +#: templates/users/profil.html:71 +msgid "Extend the connection period" +msgstr "Étendre la durée de connexion" + +#: templates/users/profil.html:86 msgid "Refill the balance" msgstr "Recharger le solde" -#: templates/users/profil.html:92 +#: templates/users/profil.html:96 msgid " Machines" msgstr " Machines" -#: templates/users/profil.html:96 templates/users/profil.html:105 +#: templates/users/profil.html:100 templates/users/profil.html:109 msgid " Add a machine" msgstr " Ajouter une machine" -#: templates/users/profil.html:102 templates/users/profil.html:333 +#: templates/users/profil.html:106 templates/users/profil.html:337 msgid "No machine" msgstr "Pas de machine" -#: templates/users/profil.html:118 +#: templates/users/profil.html:122 msgid " Detailed information" msgstr " Informations détaillées" -#: templates/users/profil.html:125 +#: templates/users/profil.html:129 msgid "Edit" msgstr "Modifier" -#: templates/users/profil.html:129 views.py:282 views.py:1079 +#: templates/users/profil.html:133 views.py:282 views.py:1079 msgid "Change the password" msgstr "Changer le mot de passe" -#: templates/users/profil.html:134 +#: templates/users/profil.html:138 msgid "Change the state" msgstr "Changer l'état" -#: templates/users/profil.html:140 views.py:260 +#: templates/users/profil.html:144 views.py:260 msgid "Edit the groups" msgstr "Modifier les groupes" -#: templates/users/profil.html:151 +#: templates/users/profil.html:155 msgid "Mailing" msgstr "Envoi de mails" -#: templates/users/profil.html:155 +#: templates/users/profil.html:159 msgid "Mailing disabled" msgstr "Envoi de mails désactivé" -#: templates/users/profil.html:173 +#: templates/users/profil.html:177 msgid "Telephone number" msgstr "Numéro de téléphone" -#: templates/users/profil.html:183 +#: templates/users/profil.html:187 msgid "Registration date" msgstr "Date d'inscription" -#: templates/users/profil.html:185 +#: templates/users/profil.html:189 msgid "Last login" msgstr "Dernière connexion" -#: templates/users/profil.html:189 +#: templates/users/profil.html:193 msgid "End of membership" msgstr "Fin d'adhésion" -#: templates/users/profil.html:195 +#: templates/users/profil.html:199 msgid "Whitelist" msgstr "Accès gracieux" -#: templates/users/profil.html:199 templates/users/profil.html:228 +#: templates/users/profil.html:203 templates/users/profil.html:232 msgid "None" msgstr "Aucun" -#: templates/users/profil.html:202 +#: templates/users/profil.html:206 msgid "Ban" msgstr "Bannissement" -#: templates/users/profil.html:206 +#: templates/users/profil.html:210 msgid "Not banned" msgstr "Non banni" -#: templates/users/profil.html:208 +#: templates/users/profil.html:212 msgid "State" msgstr "État" -#: templates/users/profil.html:214 +#: templates/users/profil.html:218 msgid "Archived" msgstr "Archivé" -#: templates/users/profil.html:220 +#: templates/users/profil.html:224 #, python-format msgid "Active (until %(end_access)s)" msgstr "Actif (jusqu'au %(end_access)s)" -#: templates/users/profil.html:224 templates/users/sidebar.html:82 +#: templates/users/profil.html:228 templates/users/sidebar.html:82 msgid "Groups of rights" msgstr "Groupes de droits" -#: templates/users/profil.html:232 +#: templates/users/profil.html:236 msgid "Balance" msgstr "Solde" -#: templates/users/profil.html:237 +#: templates/users/profil.html:241 msgid "Refill" msgstr "Rechager" -#: templates/users/profil.html:242 +#: templates/users/profil.html:246 msgid "GPG fingerprint" msgstr "Empreinte GPG" -#: templates/users/profil.html:261 +#: templates/users/profil.html:265 msgid " Manage the club" msgstr " Gérer le club" -#: templates/users/profil.html:268 +#: templates/users/profil.html:272 msgid "Manage the admins and members" msgstr "Gérer les admins et les membres" -#: templates/users/profil.html:272 +#: templates/users/profil.html:276 msgid "Club admins" msgstr "Admins du clubs" -#: templates/users/profil.html:291 +#: templates/users/profil.html:295 msgid "Members" msgstr "Adhérents" -#: templates/users/profil.html:318 +#: templates/users/profil.html:322 msgid "Machines" msgstr "Machines" -#: templates/users/profil.html:326 +#: templates/users/profil.html:330 msgid "Add a machine" msgstr "Ajouter une machine" -#: templates/users/profil.html:342 +#: templates/users/profil.html:346 msgid "Subscriptions" msgstr "Cotisations" -#: templates/users/profil.html:350 +#: templates/users/profil.html:354 msgid "Add as subscription" msgstr "Ajouter une cotisation" -#: templates/users/profil.html:355 +#: templates/users/profil.html:359 msgid "Edit the balance" msgstr "Modifier le solde" -#: templates/users/profil.html:364 +#: templates/users/profil.html:368 msgid "No invoice" msgstr "Pas de facture" -#: templates/users/profil.html:381 views.py:384 +#: templates/users/profil.html:385 views.py:384 msgid "Add a ban" msgstr "Ajouter un bannissement" -#: templates/users/profil.html:389 +#: templates/users/profil.html:393 msgid "No ban" msgstr "Pas de bannissement" -#: templates/users/profil.html:406 +#: templates/users/profil.html:410 msgid "Grant a whitelist" msgstr "Donner un accès gracieux" -#: templates/users/profil.html:414 +#: templates/users/profil.html:418 msgid "No whitelist" msgstr "Pas d'accès gracieux" -#: templates/users/profil.html:422 +#: templates/users/profil.html:426 msgid " Email settings" msgstr " Paramètres mail" -#: templates/users/profil.html:429 +#: templates/users/profil.html:433 msgid " Edit email settings" msgstr " Modifier les paramètres mail" -#: templates/users/profil.html:438 templates/users/profil.html:464 +#: templates/users/profil.html:442 templates/users/profil.html:468 msgid "Contact email address" msgstr "Adresse mail de contact" -#: templates/users/profil.html:442 +#: templates/users/profil.html:446 msgid "Enable the local email account" msgstr "Activer le compte mail local" -#: templates/users/profil.html:444 +#: templates/users/profil.html:448 msgid "Enable the local email redirection" msgstr "Activer la redirection mail locale" -#: templates/users/profil.html:448 +#: templates/users/profil.html:452 msgid "" "The contact email address is the email address where we send emails to " "contact you. If you would like to use your external email address for that, " @@ -1042,7 +1050,7 @@ msgstr "" "cela, vous pouvez soit désactiver votre adresse mail locale soit activer la " "redirection des mails locaux." -#: templates/users/profil.html:453 +#: templates/users/profil.html:457 msgid " Add an email address" msgstr " Ajouter une adresse mail" diff --git a/users/templates/users/profil.html b/users/templates/users/profil.html index 09d499d0..85db2a5f 100644 --- a/users/templates/users/profil.html +++ b/users/templates/users/profil.html @@ -64,11 +64,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
    {% else %}
    -
    {% trans "Connection" %}{% blocktrans with end_connection=user.end_access|date:"SHORT_DATE_FORMAT" %} ( Until {{ end_connection }} ){% endblocktrans %}
    +
    {% blocktrans with end_connection=user.end_access|date:"SHORT_DATE_FORMAT" %}Connection (until {{ end_connection }} ){% endblocktrans %}
    From d574194ed75540d8002787438e693450e0bdd50e Mon Sep 17 00:00:00 2001 From: Laouen Fernet Date: Thu, 23 Aug 2018 15:38:36 +0200 Subject: [PATCH 156/171] fix bug with shell field for self registration --- users/forms.py | 3 +- users/locale/fr/LC_MESSAGES/django.mo | Bin 25505 -> 25505 bytes users/locale/fr/LC_MESSAGES/django.po | 56 +++++++++++++------------- 3 files changed, 30 insertions(+), 29 deletions(-) diff --git a/users/forms.py b/users/forms.py index 6af499bd..a3dcee57 100644 --- a/users/forms.py +++ b/users/forms.py @@ -316,8 +316,9 @@ class AdherentForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): self.fields['room'].label = _("Room") self.fields['room'].empty_label = _("No room") self.fields['school'].empty_label = _("Select a school") - self.fields['shell'].empty_label = _("Default shell") self.fields['gpg_fingerprint'].widget.attrs['placeholder'] = _("Leave empty if you don't have any GPG key.") + if 'shell' in self.fields: + self.fields['shell'].empty_label = _("Default shell") def clean_email(self): if not OptionalUser.objects.first().local_email_domain in self.cleaned_data.get('email'): diff --git a/users/locale/fr/LC_MESSAGES/django.mo b/users/locale/fr/LC_MESSAGES/django.mo index 9391372760cb02dcc767caa08a1d0571089bd2f5..059a73fcf5ef3e75001f0cd01be085abfedcdafa 100644 GIT binary patch delta 17 YcmZ2@oN?iC#tjWtOvYxLo2=wD0YW7PUjP6A delta 17 YcmZ2@oN?iC#tjWtOoj%Vo2=wD0YUQyR{#J2 diff --git a/users/locale/fr/LC_MESSAGES/django.po b/users/locale/fr/LC_MESSAGES/django.po index d1cb14c6..d3ff0f4f 100644 --- a/users/locale/fr/LC_MESSAGES/django.po +++ b/users/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-08-23 15:10+0200\n" +"POT-Creation-Date: 2018-08-23 15:36+0200\n" "PO-Revision-Date: 2018-06-27 23:35+0200\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" @@ -39,7 +39,7 @@ msgstr "Vous n'avez pas le droit de voir cette application." msgid "Current password" msgstr "Mot de passe actuel" -#: forms.py:75 forms.py:446 +#: forms.py:75 forms.py:447 msgid "New password" msgstr "Nouveau mot de passe" @@ -109,104 +109,104 @@ msgstr "Nom" msgid "Email address" msgstr "Adresse mail" -#: forms.py:314 forms.py:384 forms.py:515 templates/users/aff_schools.html:37 +#: forms.py:314 forms.py:385 forms.py:516 templates/users/aff_schools.html:37 #: templates/users/profil.html:181 msgid "School" msgstr "Établissement" -#: forms.py:315 forms.py:385 models.py:1204 +#: forms.py:315 forms.py:386 models.py:1204 #: templates/users/aff_serviceusers.html:34 templates/users/profil.html:183 msgid "Comment" msgstr "Commentaire" -#: forms.py:316 forms.py:386 templates/users/aff_clubs.html:40 +#: forms.py:316 forms.py:387 templates/users/aff_clubs.html:40 #: templates/users/aff_users.html:41 templates/users/profil.html:175 msgid "Room" msgstr "Chambre" -#: forms.py:317 forms.py:387 +#: forms.py:317 forms.py:388 msgid "No room" msgstr "Pas de chambre" -#: forms.py:318 forms.py:388 +#: forms.py:318 forms.py:389 msgid "Select a school" msgstr "Sélectionnez un établissement" #: forms.py:319 -msgid "Default shell" -msgstr "Interface système par défaut" - -#: forms.py:320 msgid "Leave empty if you don't have any GPG key." msgstr "Laissez vide si vous n'avez pas de clé GPG." -#: forms.py:327 forms.py:655 +#: forms.py:321 +msgid "Default shell" +msgstr "Interface système par défaut" + +#: forms.py:328 forms.py:656 msgid "You can't use a {} address." msgstr "Vous ne pouvez pas utiliser une adresse {}." -#: forms.py:352 forms.py:410 +#: forms.py:353 forms.py:411 msgid "A valid telephone number is required." msgstr "Un numéro de téléphone valide est requis." -#: forms.py:363 +#: forms.py:364 msgid "Force the move?" msgstr "Forcer le déménagement ?" -#: forms.py:383 templates/users/aff_clubs.html:36 +#: forms.py:384 templates/users/aff_clubs.html:36 #: templates/users/aff_serviceusers.html:32 msgid "Name" msgstr "Nom" -#: forms.py:389 +#: forms.py:390 msgid "Use a mailing list" msgstr "Utiliser une liste de diffusion" -#: forms.py:503 templates/users/aff_listright.html:38 +#: forms.py:504 templates/users/aff_listright.html:38 msgid "Superuser" msgstr "Superutilisateur" -#: forms.py:527 +#: forms.py:528 msgid "Shell name" msgstr "Nom de l'interface système" -#: forms.py:546 +#: forms.py:547 msgid "Name of the group of rights" msgstr "Nom du groupe de droits" -#: forms.py:557 +#: forms.py:558 msgid "GID. Warning: this field must not be edited after creation." msgstr "GID. Attention : ce champ ne doit pas être modifié après création." -#: forms.py:565 +#: forms.py:566 msgid "Current groups of rights" msgstr "Groupes de droits actuels" -#: forms.py:582 +#: forms.py:583 msgid "Current schools" msgstr "Établissements actuels" -#: forms.py:600 forms.py:614 templates/users/aff_bans.html:41 +#: forms.py:601 forms.py:615 templates/users/aff_bans.html:41 #: templates/users/aff_whitelists.html:41 msgid "End date" msgstr "Date de fin" -#: forms.py:628 models.py:1748 +#: forms.py:629 models.py:1748 msgid "Local part of the email address" msgstr "Partie locale de l'adresse mail" -#: forms.py:629 +#: forms.py:630 msgid "Can't contain @" msgstr "Ne peut pas contenir @" -#: forms.py:644 +#: forms.py:645 msgid "Main email address" msgstr "Adresse mail principale" -#: forms.py:646 +#: forms.py:647 msgid "Redirect local emails" msgstr "Rediriger les mails locaux" -#: forms.py:648 +#: forms.py:649 msgid "Use local emails" msgstr "Utiliser les mails locaux" From 8e7a1c5094316737aff2581cb982b55cf5ea0117 Mon Sep 17 00:00:00 2001 From: Maxime Bombar Date: Thu, 23 Aug 2018 04:07:23 +0200 Subject: [PATCH 157/171] Fix regression introduced in commit c3b3146f3910bd6fb7feec71fdac5c4f9a83ff6e --- preferences/forms.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/preferences/forms.py b/preferences/forms.py index 7da8b545..73731750 100644 --- a/preferences/forms.py +++ b/preferences/forms.py @@ -124,7 +124,8 @@ class EditGeneralOptionForm(ModelForm): prefix=prefix, **kwargs ) - self.fields['general_message'].label = _("General message") + self.fields['general_message_fr'].label = _("General message in French") + self.fields['general_message_en'].label = _("General message in English") self.fields['search_display_page'].label = _("Number of results" " displayed when" " searching") From dfc765f5e521d60410c92a3fc72d275505c03ff3 Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Thu, 23 Aug 2018 22:57:55 +0200 Subject: [PATCH 158/171] Fais la distinction entre un baseinvoice avec et sans objet facture. --- logs/templates/logs/aff_summary.html | 9 +++++++-- logs/templatetags/logs_extra.py | 6 +++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/logs/templates/logs/aff_summary.html b/logs/templates/logs/aff_summary.html index c9ebfe55..366e07e7 100644 --- a/logs/templates/logs/aff_summary.html +++ b/logs/templates/logs/aff_summary.html @@ -110,11 +110,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
    {{ v.datetime }} - {% blocktrans with username=v.username number=v.version.object.number name=v.version.object.name %}{{ username }} has sold {{ number }}x {{ name }} to{% endblocktrans %} - {{ v.version.object.facture.user.get_username }} + {% blocktrans with username=v.username number=v.version.object.number name=v.version.object.name %}{{ username }} has sold {{ number }}x {{ name }}{% endblocktrans %} + {% with invoice=v.version.object.facture %} + {% if invoice|is_facture %} + {% trans " to" %} + {{ v.version.object.facture.facture.user.get_username }} {% if v.version.object.iscotisation %} ({% blocktrans with duration=v.version.object.duration %}+{{ duration }} months{% endblocktrans %}) {% endif %} + {% endif %} + {% endwith %} diff --git a/logs/templatetags/logs_extra.py b/logs/templatetags/logs_extra.py index 0620f8f4..813a577f 100644 --- a/logs/templatetags/logs_extra.py +++ b/logs/templatetags/logs_extra.py @@ -19,7 +19,7 @@ # # 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. +# 51 Franklin Street, Fifth Floor, Boston, MA 021}10-1301 USA. """logs.templatetags.logs_extra A templatetag to get the class name for a given object """ @@ -34,6 +34,10 @@ def classname(obj): """ Returns the object class name """ return obj.__class__.__name__ +@register.filter +def is_facture(baseinvoice): + """Returns True if a baseinvoice has a `Facture` child.""" + return hasattr(baseinvoice, 'facture') @register.inclusion_tag('buttons/history.html') def history_button(instance, text=False, html_class=True): From 9d46031ae18d38c6bc891511ac1bff69e3ba5ab1 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Fri, 24 Aug 2018 17:00:02 +0200 Subject: [PATCH 159/171] Hotfix : users.end_conn + python2 header --- cotisations/tex.py | 2 ++ users/templates/users/profil.html | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cotisations/tex.py b/cotisations/tex.py index b7e1cb81..a7617114 100644 --- a/cotisations/tex.py +++ b/cotisations/tex.py @@ -1,3 +1,4 @@ +#-*- coding: utf-8 -*- # 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. @@ -24,6 +25,7 @@ Module in charge of rendering some LaTex templates. Used to generated PDF invoice. """ + import tempfile from subprocess import Popen, PIPE import os diff --git a/users/templates/users/profil.html b/users/templates/users/profil.html index 85db2a5f..0f4f8b2c 100644 --- a/users/templates/users/profil.html +++ b/users/templates/users/profil.html @@ -64,7 +64,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% else %}
    -
    {% blocktrans with end_connection=user.end_access|date:"SHORT_DATE_FORMAT" %}Connection (until {{ end_connection }} ){% endblocktrans %}
    +
    {% blocktrans with end_connection=users.end_access|date:"SHORT_DATE_FORMAT" %}Connection (until {{ end_connection }} ){% endblocktrans %}
    {% can_create Facture %} From 74a2e265fa4dfb487ea62923527d1431abaf6d15 Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Sat, 25 Aug 2018 11:10:18 +0200 Subject: [PATCH 160/171] Donne la bonne date lors de l'import des factures dans BaseInvoice. --- cotisations/migrations/0032_custom_invoice.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cotisations/migrations/0032_custom_invoice.py b/cotisations/migrations/0032_custom_invoice.py index e143ae13..68963bf5 100644 --- a/cotisations/migrations/0032_custom_invoice.py +++ b/cotisations/migrations/0032_custom_invoice.py @@ -14,7 +14,9 @@ def reattribute_ids(apps, schema_editor): BaseInvoice = apps.get_model('cotisations', 'BaseInvoice') for f in Facture.objects.all(): - base = BaseInvoice.objects.create(id=f.pk, date=f.date) + base = BaseInvoice.objects.create(id=f.pk) + base.date = f.date + base.save() f.baseinvoice_ptr = base f.save() From 3eda283f64e197daafa76bc4376d659f570aeb30 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Sun, 26 Aug 2018 17:13:29 +0200 Subject: [PATCH 161/171] =?UTF-8?q?Correction=20du=20probl=C3=A8me=20de=20?= =?UTF-8?q?vente=20d'articles=20+=20simplification=20views=20et=20forms?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cotisations/forms.py | 34 +++++----------------------------- cotisations/models.py | 19 ++++++++++++++----- cotisations/views.py | 33 +++++++++++---------------------- 3 files changed, 30 insertions(+), 56 deletions(-) diff --git a/cotisations/forms.py b/cotisations/forms.py index 341ccc4c..4c79fe0e 100644 --- a/cotisations/forms.py +++ b/cotisations/forms.py @@ -84,15 +84,13 @@ class FactureForm(FieldPermissionFormMixin, FormRevMixin, ModelForm): return cleaned_data -class SelectUserArticleForm(FormRevMixin, Form): +class SelectArticleForm(FormRevMixin, Form): """ Form used to select an article during the creation of an invoice for a member. """ article = forms.ModelChoiceField( - queryset=Article.objects.filter( - Q(type_user='All') | Q(type_user='Adherent') - ), + queryset=Article.objects.none(), label=_("Article"), required=True ) @@ -104,31 +102,9 @@ class SelectUserArticleForm(FormRevMixin, Form): def __init__(self, *args, **kwargs): user = kwargs.pop('user') - super(SelectUserArticleForm, self).__init__(*args, **kwargs) - self.fields['article'].queryset = Article.find_allowed_articles(user) - - -class SelectClubArticleForm(Form): - """ - Form used to select an article during the creation of an invoice for a - club. - """ - article = forms.ModelChoiceField( - queryset=Article.objects.filter( - Q(type_user='All') | Q(type_user='Club') - ), - label=_("Article"), - required=True - ) - quantity = forms.IntegerField( - label=_("Quantity"), - validators=[MinValueValidator(1)], - required=True - ) - - def __init__(self, user, *args, **kwargs): - super(SelectClubArticleForm, self).__init__(*args, **kwargs) - self.fields['article'].queryset = Article.find_allowed_articles(user) + target_user = kwargs.pop('target_user') + super(SelectArticleForm, self).__init__(*args, **kwargs) + self.fields['article'].queryset = Article.find_allowed_articles(user, target_user) class CustomInvoiceForm(FormRevMixin, ModelForm): diff --git a/cotisations/models.py b/cotisations/models.py index ac601665..fe89aa5d 100644 --- a/cotisations/models.py +++ b/cotisations/models.py @@ -222,7 +222,7 @@ class Facture(BaseInvoice): return True, None if len(Paiement.find_allowed_payments(user_request)) <= 0: return False, _("There are no payment method which you can use.") - if len(Article.find_allowed_articles(user_request)) <= 0: + if len(Article.find_allowed_articles(user_request, user_request)) <= 0: return False, _("There are no article that you can buy.") return True, None @@ -595,15 +595,24 @@ class Article(RevMixin, AclMixin, models.Model): ) @classmethod - def find_allowed_articles(cls, user): - """Finds every allowed articles for an user. + def find_allowed_articles(cls, user, target_user): + """Finds every allowed articles for an user, on a target user. Args: user: The user requesting articles. + target_user: The user to sell articles """ + if target_user.is_class_club: + objects_pool = cls.objects.filter( + Q(type_user='All') | Q(type_user='Club') + ) + else: + objects_pool = cls.objects.filter( + Q(type_user='All') | Q(type_user='Adherent') + ) if user.has_perm('cotisations.buy_every_article'): - return cls.objects.all() - return cls.objects.filter(available_for_everyone=True) + return objects_pool + return objects_pool.filter(available_for_everyone=True) class Banque(RevMixin, AclMixin, models.Model): diff --git a/cotisations/views.py b/cotisations/views.py index 193f4321..82db074b 100644 --- a/cotisations/views.py +++ b/cotisations/views.py @@ -75,8 +75,7 @@ from .forms import ( DelPaiementForm, BanqueForm, DelBanqueForm, - SelectUserArticleForm, - SelectClubArticleForm, + SelectArticleForm, RechargeForm, CustomInvoiceForm ) @@ -110,16 +109,10 @@ def new_facture(request, user, userid): creation=True ) - if request.user.is_class_club: - article_formset = formset_factory(SelectClubArticleForm)( - request.POST or None, - form_kwargs={'user': request.user} - ) - else: - article_formset = formset_factory(SelectUserArticleForm)( - request.POST or None, - form_kwargs={'user': request.user} - ) + article_formset = formset_factory(SelectArticleForm)( + request.POST or None, + form_kwargs={'user': request.user, 'target_user': user} + ) if invoice_form.is_valid() and article_formset.is_valid(): new_invoice_instance = invoice_form.save(commit=False) @@ -199,16 +192,12 @@ def new_custom_invoice(request): ) # Building the invocie form and the article formset invoice_form = CustomInvoiceForm(request.POST or None) - if request.user.is_class_club: - articles_formset = formset_factory(SelectClubArticleForm)( - request.POST or None, - form_kwargs={'user': request.user} - ) - else: - articles_formset = formset_factory(SelectUserArticleForm)( - request.POST or None, - form_kwargs={'user': request.user} - ) + + article_formset = formset_factory(SelectArticleForm)( + request.POST or None, + form_kwargs={'user': request.user, 'target_user': user} + ) + if invoice_form.is_valid() and articles_formset.is_valid(): new_invoice_instance = invoice_form.save() for art_item in articles_formset: From 39b9c1b3a9fa7dd82e385bbe8bf41e36140d765e Mon Sep 17 00:00:00 2001 From: grisel-davy Date: Fri, 17 Aug 2018 11:05:40 +0200 Subject: [PATCH 162/171] Fix #155 confusion entre request.user et user --- cotisations/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cotisations/views.py b/cotisations/views.py index 82db074b..bc999c57 100644 --- a/cotisations/views.py +++ b/cotisations/views.py @@ -854,7 +854,7 @@ def credit_solde(request, user, **_kwargs): p = get_object_or_404(Paiement, is_balance=True) return form({ 'factureform': refill_form, - 'balance': request.user.solde, + 'balance': user.solde, 'title': _("Refill your balance"), 'action_name': _("Pay"), 'max_balance': p.payment_method.maximum_balance, From 25a710395f0401134a5e8150cdaa7cbfe75f8e84 Mon Sep 17 00:00:00 2001 From: grisel-davy Date: Mon, 20 Aug 2018 10:26:23 +0200 Subject: [PATCH 163/171] envoyer le bon user pour les verifications --- cotisations/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cotisations/views.py b/cotisations/views.py index bc999c57..365677a2 100644 --- a/cotisations/views.py +++ b/cotisations/views.py @@ -827,7 +827,7 @@ def credit_solde(request, user, **_kwargs): kwargs={'userid': user.id} )) - refill_form = RechargeForm(request.POST or None, user=request.user) + refill_form = RechargeForm(request.POST or None, user=user) if refill_form.is_valid(): price = refill_form.cleaned_data['value'] invoice = Facture(user=user) From db198c46ad90cef928fe3e828fdfe4f1097a9351 Mon Sep 17 00:00:00 2001 From: grisel-davy Date: Mon, 27 Aug 2018 18:57:23 +0200 Subject: [PATCH 164/171] =?UTF-8?q?D=C3=A9confusion=20entre=20la=20personn?= =?UTF-8?q?e=20qui=20fait=20l'action=20et=20le=20compte=20concern=C3=A9.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cotisations/forms.py | 4 ++-- cotisations/views.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cotisations/forms.py b/cotisations/forms.py index 4c79fe0e..5b463a99 100644 --- a/cotisations/forms.py +++ b/cotisations/forms.py @@ -243,12 +243,12 @@ class RechargeForm(FormRevMixin, Form): label=_("Payment method") ) - def __init__(self, *args, user=None, **kwargs): + def __init__(self, *args, user=None, user_source, **kwargs): self.user = user super(RechargeForm, self).__init__(*args, **kwargs) self.fields['payment'].empty_label = \ _("Select a payment method") - self.fields['payment'].queryset = Paiement.find_allowed_payments(user).exclude(is_balance=True) + self.fields['payment'].queryset = Paiement.find_allowed_payments(user_source).exclude(is_balance=True) def clean(self): """ diff --git a/cotisations/views.py b/cotisations/views.py index 365677a2..a4a35825 100644 --- a/cotisations/views.py +++ b/cotisations/views.py @@ -827,7 +827,7 @@ def credit_solde(request, user, **_kwargs): kwargs={'userid': user.id} )) - refill_form = RechargeForm(request.POST or None, user=user) + refill_form = RechargeForm(request.POST or None, user=user, user_source=request.user) if refill_form.is_valid(): price = refill_form.cleaned_data['value'] invoice = Facture(user=user) From 3331999434844822ff541ea92eba4e474964c0fb Mon Sep 17 00:00:00 2001 From: chirac Date: Mon, 27 Aug 2018 19:04:27 +0200 Subject: [PATCH 165/171] Update forms.py, user_source=None --- cotisations/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cotisations/forms.py b/cotisations/forms.py index 5b463a99..9194597a 100644 --- a/cotisations/forms.py +++ b/cotisations/forms.py @@ -243,7 +243,7 @@ class RechargeForm(FormRevMixin, Form): label=_("Payment method") ) - def __init__(self, *args, user=None, user_source, **kwargs): + def __init__(self, *args, user=None, user_source=None, **kwargs): self.user = user super(RechargeForm, self).__init__(*args, **kwargs) self.fields['payment'].empty_label = \ From e546a2228a6c5c851941d42261735751cd936520 Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Tue, 28 Aug 2018 12:31:43 +0200 Subject: [PATCH 166/171] Fix l'erreur sur l'utilisation du massive bootstrap form pour l'ipv4 --- machines/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/machines/views.py b/machines/views.py index 3d71acab..02c3c671 100644 --- a/machines/views.py +++ b/machines/views.py @@ -153,7 +153,7 @@ def generate_ipv4_choices(form_obj): """ f_ipv4 = form_obj.fields['ipv4'] used_mtype_id = [] - choices = '{"":[{key:"",value:'+_("Select a machine type first.},") + choices = '{"":[{key:"",value:"'+_("Select a machine type first.") + '"}' mtype_id = -1 for ip in (f_ipv4.queryset From 6b43e68ababc6427579bcd89f078753b4d7b87d6 Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Sun, 19 Aug 2018 21:26:10 +0200 Subject: [PATCH 167/171] =?UTF-8?q?Compatibilit=C3=A9=20Python=202=20dans?= =?UTF-8?q?=20cotisations/tex.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cotisations/tex.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cotisations/tex.py b/cotisations/tex.py index a7617114..0ee45bdb 100644 --- a/cotisations/tex.py +++ b/cotisations/tex.py @@ -1,4 +1,4 @@ -#-*- coding: utf-8 -*- +# coding: utf-8 # 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. From 01de1e9166a7f3df3cef251292afc8b10f354392 Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Sun, 19 Aug 2018 21:40:46 +0200 Subject: [PATCH 168/171] =?UTF-8?q?Erreur=20dans=20les=20pr=C3=A9f=C3=A9re?= =?UTF-8?q?nces=20association?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- preferences/forms.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/preferences/forms.py b/preferences/forms.py index 73731750..b9ccd531 100644 --- a/preferences/forms.py +++ b/preferences/forms.py @@ -168,9 +168,6 @@ class EditAssoOptionForm(ModelForm): self.fields['pseudo'].label = _("Usual name") self.fields['utilisateur_asso'].label = _("Account used for editing" " from /admin") - self.fields['payment'].label = _("Payment") - self.fields['payment_id'].label = _("Payment ID") - self.fields['payment_pass'].label = _("Payment password") self.fields['description'].label = _("Description") From dbe27699936ea692c92c6c1f50551afe1e5299a2 Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Sun, 19 Aug 2018 21:42:49 +0200 Subject: [PATCH 169/171] Fix graphique sur les boutons historiques des listes de factures --- cotisations/templates/cotisations/aff_cotisations.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cotisations/templates/cotisations/aff_cotisations.html b/cotisations/templates/cotisations/aff_cotisations.html index b7fe993b..30de85dc 100644 --- a/cotisations/templates/cotisations/aff_cotisations.html +++ b/cotisations/templates/cotisations/aff_cotisations.html @@ -73,7 +73,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% can_delete facture %} {% include 'buttons/suppr.html' with href='cotisations:del-facture' id=facture.id %} {% acl_end %} - {% history_button facture text=True html_class=False %} + {% history_button facture %}
    {% if facture.valid %} From 8bfef4716a1c3b8ebb0be34946a0c38ee7914fdd Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Tue, 21 Aug 2018 20:43:12 +0200 Subject: [PATCH 170/171] Fix erreur de default dans l'affichage des noms de machines. --- machines/templates/machines/aff_machines.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/machines/templates/machines/aff_machines.html b/machines/templates/machines/aff_machines.html index e3404036..33ae617a 100644 --- a/machines/templates/machines/aff_machines.html +++ b/machines/templates/machines/aff_machines.html @@ -52,7 +52,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% trans "No name" as tr_no_name %} {% trans "View the profile" as tr_view_the_profile %} - {{ machine.name|default:'tr_no_name' }} + {{ machine.name|default:tr_no_name }} {{ machine.user }} From c25a498140ab7e7dcfaf3b1d732809ddf219f071 Mon Sep 17 00:00:00 2001 From: Laouen Fernet Date: Mon, 20 Aug 2018 11:46:09 +0200 Subject: [PATCH 171/171] fix the display of the end of connection date --- users/templates/users/profil.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/users/templates/users/profil.html b/users/templates/users/profil.html index 0f4f8b2c..b898e21f 100644 --- a/users/templates/users/profil.html +++ b/users/templates/users/profil.html @@ -52,7 +52,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% elif not users.has_access %}
    {% trans "No connection" %}
    -
    +
    {% can_create Facture %} {% trans "Pay for a connection" %}