diff --git a/api/serializers.py b/api/serializers.py index 32fdd0e8..f2ff9954 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -1000,6 +1000,17 @@ class CNAMERecordSerializer(serializers.ModelSerializer): model = machines.Domain fields = ('alias', 'hostname') +class DNAMERecordSerializer(serializers.ModelSerializer): + """Serialize `machines.models.Domain` objects with the data needed to + generate a DNAME DNS record. + """ + alias = serializers.CharField(read_only=True) + zone = serializers.CharField(read_only=True) + + class Meta: + model = machines.DName + fields = ('alias', 'zone') + class DNSZonesSerializer(serializers.ModelSerializer): """Serialize the data about DNS Zones. @@ -1014,14 +1025,14 @@ class DNSZonesSerializer(serializers.ModelSerializer): a_records = ARecordSerializer(many=True, source='get_associated_a_records') aaaa_records = AAAARecordSerializer(many=True, source='get_associated_aaaa_records') cname_records = CNAMERecordSerializer(many=True, source='get_associated_cname_records') + dname_records = DNAMERecordSerializer(many=True, source='get_associated_dname_records') sshfp_records = SSHFPInterfaceSerializer(many=True, source='get_associated_sshfp_records') class Meta: model = machines.Extension fields = ('name', 'soa', 'ns_records', 'originv4', 'originv6', 'mx_records', 'txt_records', 'srv_records', 'a_records', - 'aaaa_records', 'cname_records', 'sshfp_records') - + 'aaaa_records', 'cname_records', 'dname_records', 'sshfp_records') #REMINDER diff --git a/api/views.py b/api/views.py index 3d7f4bc2..a2a376e9 100644 --- a/api/views.py +++ b/api/views.py @@ -541,8 +541,8 @@ class ServiceRegenViewSet(viewsets.ModelViewSet): # Config des switches class SwitchPortView(generics.ListAPIView): - """Exposes the associations between hostname, mac address and IPv4 in - order to build the DHCP lease files. + """Output each port of a switch, to be serialized with + additionnal informations (profiles etc) """ queryset = topologie.Switch.objects.all().select_related("switchbay").select_related("model__constructor").prefetch_related("ports__custom_profile__vlan_tagged").prefetch_related("ports__custom_profile__vlan_untagged").prefetch_related("ports__machine_interface__domain__extension").prefetch_related("ports__room") @@ -551,16 +551,14 @@ class SwitchPortView(generics.ListAPIView): # Rappel fin adhésion class ReminderView(generics.ListAPIView): - """Exposes the associations between hostname, mac address and IPv4 in - order to build the DHCP lease files. + """Output for users to remind an end of their subscription. """ queryset = preferences.Reminder.objects.all() serializer_class = serializers.ReminderSerializer class RoleView(generics.ListAPIView): - """Exposes the associations between hostname, mac address and IPv4 in - order to build the DHCP lease files. + """Output of roles for each server """ queryset = machines.Role.objects.all().prefetch_related('servers') serializer_class = serializers.RoleSerializer diff --git a/cotisations/views.py b/cotisations/views.py index 4cd76f93..68118711 100644 --- a/cotisations/views.py +++ b/cotisations/views.py @@ -47,7 +47,10 @@ from users.models import User from re2o.settings import LOGO_PATH from re2o import settings from re2o.views import form -from re2o.utils import SortTable, re2o_paginator +from re2o.base import ( + SortTable, + re2o_paginator, +) from re2o.acl import ( can_create, can_edit, diff --git a/logs/views.py b/logs/views.py index 21e3c470..a54edd56 100644 --- a/logs/views.py +++ b/logs/views.py @@ -102,15 +102,18 @@ from re2o.utils import ( all_baned, all_has_access, all_adherent, + all_active_assigned_interfaces_count, + all_active_interfaces_count, +) +from re2o.base import ( re2o_paginator, + SortTable ) from re2o.acl import ( can_view_all, can_view_app, can_edit_history, ) -from re2o.utils import all_active_assigned_interfaces_count -from re2o.utils import all_active_interfaces_count, SortTable @login_required diff --git a/machines/models.py b/machines/models.py index 48e50644..a6a1e1ae 100644 --- a/machines/models.py +++ b/machines/models.py @@ -741,6 +741,9 @@ class Extension(RevMixin, AclMixin, models.Model): .filter(cname__interface_parent__in=all_active_assigned_interfaces()) .prefetch_related('cname')) + def get_associated_dname_records(self): + return (DName.objects.filter(alias=self)) + @staticmethod def can_use_all(user_request, *_args, **_kwargs): """Superdroit qui permet d'utiliser toutes les extensions sans @@ -1631,18 +1634,6 @@ class Role(RevMixin, AclMixin, models.Model): verbose_name = _("server role") verbose_name_plural = _("server roles") - @classmethod - 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=roleid) - @classmethod def interface_for_roletype(cls, roletype): """Return interfaces for a roletype""" @@ -1657,14 +1648,6 @@ class Role(RevMixin, AclMixin, models.Model): machine__interface__role=cls.objects.filter(specific_role=roletype) ) - @classmethod - def get_instance(cls, roleid, *_args, **_kwargs): - """Get the Machine instance with machineid. - :param userid: The id - :return: The user - """ - return cls.objects.get(pk=roleid) - @classmethod def interface_for_roletype(cls, roletype): """Return interfaces for a roletype""" diff --git a/machines/templates/machines/machine.html b/machines/templates/machines/machine.html index 1a8ce8b1..1e2e3700 100644 --- a/machines/templates/machines/machine.html +++ b/machines/templates/machines/machine.html @@ -95,9 +95,9 @@ with this program; if not, write to the Free Software Foundation, Inc., {% if interfaceform %}

{% trans "Interface" %}

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

{% trans "Alias" %}

- {% bootstrap_form aliasform %} + {% massive_bootstrap_form aliasform 'extension' %} {% endif %} {% if serviceform %}

{% trans "Service" %}

diff --git a/machines/views.py b/machines/views.py index 8d395749..59d4bd5a 100644 --- a/machines/views.py +++ b/machines/views.py @@ -55,6 +55,8 @@ from re2o.acl import ( from re2o.utils import ( all_active_assigned_interfaces, filter_active_interfaces, +) +from re2o.base import ( SortTable, re2o_paginator, ) diff --git a/re2o/base.py b/re2o/base.py new file mode 100644 index 00000000..023a16ff --- /dev/null +++ b/re2o/base.py @@ -0,0 +1,267 @@ +# -*- 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. +# +# 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. + +# -*- coding: utf-8 -*- +""" +Regroupe les fonctions transversales utiles + +Et non corrélées/dépendantes des autres applications +""" + +import smtplib + +from django.utils.translation import ugettext_lazy as _ +from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger + +from re2o.settings import EMAIL_HOST + + +# Mapping of srtftime format for better understanding +# https://docs.python.org/3.6/library/datetime.html#strftime-strptime-behavior +datetime_mapping={ + '%a': '%a', + '%A': '%A', + '%w': '%w', + '%d': 'dd', + '%b': '%b', + '%B': '%B', + '%m': 'mm', + '%y': 'yy', + '%Y': 'yyyy', + '%H': 'HH', + '%I': 'HH(12h)', + '%p': 'AMPM', + '%M': 'MM', + '%S': 'SS', + '%f': 'µµ', + '%z': 'UTC(+/-HHMM)', + '%Z': 'UTC(TZ)', + '%j': '%j', + '%U': 'ww', + '%W': 'ww', + '%c': '%c', + '%x': '%x', + '%X': '%X', + '%%': '%%', +} + + +def smtp_check(local_part): + """Return True if the local_part is already taken + False if available""" + try: + srv = smtplib.SMTP(EMAIL_HOST) + srv.putcmd("vrfy", local_part) + reply_code = srv.getreply()[0] + srv.close() + if reply_code in [250, 252]: + return True, _("This domain is already taken") + except: + return True, _("Smtp unreachable") + return False, None + + +def convert_datetime_format(format): + i=0 + new_format = "" + while i < len(format): + if format[i] == '%': + char = format[i:i+2] + new_format += datetime_mapping.get(char, char) + i += 2 + else: + new_format += format[i] + i += 1 + return new_format + + +def get_input_formats_help_text(input_formats): + """Returns a help text about the possible input formats""" + if len(input_formats) > 1: + help_text_template="Format: {main} {more}" + else: + help_text_template="Format: {main}" + more_text_template="" + help_text = help_text_template.format( + main=convert_datetime_format(input_formats[0]), + more=more_text_template.format( + '\n'.join(map(convert_datetime_format, input_formats)) + ) + ) + return help_text + + +class SortTable: + """ Class gathering uselful stuff to sort the colums of a table, according + to the column and order requested. It's used with a dict of possible + values and associated model_fields """ + + # All the possible possible values + # The naming convention is based on the URL or the views function + # The syntax to describe the sort to apply is a dict where the keys are + # the url value and the values are a list of model field name to use to + # order the request. They are applied in the order they are given. + # A 'default' might be provided to specify what to do if the requested col + # doesn't match any keys. + + USERS_INDEX = { + 'user_name': ['name'], + 'user_surname': ['surname'], + 'user_pseudo': ['pseudo'], + 'user_room': ['room'], + 'default': ['state', 'pseudo'] + } + USERS_INDEX_BAN = { + 'ban_user': ['user__pseudo'], + 'ban_start': ['date_start'], + 'ban_end': ['date_end'], + 'default': ['-date_end'] + } + USERS_INDEX_WHITE = { + 'white_user': ['user__pseudo'], + 'white_start': ['date_start'], + 'white_end': ['date_end'], + 'default': ['-date_end'] + } + USERS_INDEX_SCHOOL = { + 'school_name': ['name'], + 'default': ['name'] + } + MACHINES_INDEX = { + 'machine_name': ['name'], + 'default': ['pk'] + } + COTISATIONS_INDEX = { + 'cotis_user': ['user__pseudo'], + 'cotis_paiement': ['paiement__moyen'], + 'cotis_date': ['date'], + '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'], + 'control_paiement': ['paiement'], + 'control_date': ['date'], + 'control_valid': ['valid'], + 'control_control': ['control'], + 'control_id': ['id'], + 'control_user-id': ['user__id'], + 'default': ['-date'] + } + TOPOLOGIE_INDEX = { + 'switch_dns': ['interface__domain__name'], + 'switch_ip': ['interface__ipv4__ipv4'], + 'switch_loc': ['switchbay__name'], + 'switch_ports': ['number'], + 'switch_stack': ['stack__name'], + 'default': ['switchbay', 'stack', 'stack_member_id'] + } + TOPOLOGIE_INDEX_PORT = { + 'port_port': ['port'], + 'port_room': ['room__name'], + 'port_interface': ['machine_interface__domain__name'], + 'port_related': ['related__switch__name'], + 'port_radius': ['radius'], + 'port_vlan': ['vlan_force__name'], + 'default': ['port'] + } + TOPOLOGIE_INDEX_ROOM = { + 'room_name': ['name'], + 'default': ['name'] + } + TOPOLOGIE_INDEX_BUILDING = { + 'building_name': ['name'], + 'default': ['name'] + } + TOPOLOGIE_INDEX_BORNE = { + 'ap_name': ['interface__domain__name'], + 'ap_ip': ['interface__ipv4__ipv4'], + 'ap_mac': ['interface__mac_address'], + 'default': ['interface__domain__name'] + } + TOPOLOGIE_INDEX_STACK = { + 'stack_name': ['name'], + 'stack_id': ['stack_id'], + 'default': ['stack_id'], + } + TOPOLOGIE_INDEX_MODEL_SWITCH = { + 'model-switch_name': ['reference'], + 'model-switch_contructor': ['constructor__name'], + 'default': ['reference'], + } + TOPOLOGIE_INDEX_SWITCH_BAY = { + 'switch-bay_name': ['name'], + 'switch-bay_building': ['building__name'], + 'default': ['name'], + } + TOPOLOGIE_INDEX_CONSTRUCTOR_SWITCH = { + 'constructor-switch_name': ['name'], + 'default': ['name'], + } + LOGS_INDEX = { + 'sum_date': ['revision__date_created'], + 'default': ['-revision__date_created'], + } + LOGS_STATS_LOGS = { + 'logs_author': ['user__name'], + 'logs_date': ['date_created'], + 'default': ['-date_created'] + } + + @staticmethod + def sort(request, col, order, values): + """ Check if the given values are possible and add .order_by() and + a .reverse() as specified according to those values """ + fields = values.get(col, None) + if not fields: + fields = values.get('default', []) + request = request.order_by(*fields) + if values.get(col, None) and order == 'desc': + return request.reverse() + else: + return request + + +def re2o_paginator(request, query_set, pagination_number): + """Paginator script for list display in re2o. + :request: + :query_set: Query_set to paginate + :pagination_number: Number of entries to display""" + paginator = Paginator(query_set, pagination_number) + page = request.GET.get('page') + try: + results = paginator.page(page) + except PageNotAnInteger: + # If page is not an integer, deliver first page. + results = paginator.page(1) + except EmptyPage: + # If page is out of range (e.g. 9999), deliver last page of results. + results = paginator.page(paginator.num_pages) + return results diff --git a/re2o/login.py b/re2o/login.py index 471c2e02..0b552239 100644 --- a/re2o/login.py +++ b/re2o/login.py @@ -114,9 +114,9 @@ class CryptPasswordHasher(hashers.BasePasswordHasher): Check password against encoded using CRYPT algorithm """ assert encoded.startswith(self.algorithm) - salt = hash_password_salt(challenge_password) - return constant_time_compare(crypt.crypt(password.encode(), salt), - challenge.encode()) + salt = hash_password_salt(encoded) + return constant_time_compare(crypt.crypt(password, salt), + encoded) def safe_summary(self, encoded): """ diff --git a/re2o/utils.py b/re2o/utils.py index 6f7870f0..9836a98c 100644 --- a/re2o/utils.py +++ b/re2o/utils.py @@ -38,55 +38,11 @@ from __future__ import unicode_literals from django.utils import timezone from django.db.models import Q -from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from cotisations.models import Cotisation, Facture, Vente from machines.models import Interface, Machine from users.models import Adherent, User, Ban, Whitelist -# Mapping of srtftime format for better understanding -# https://docs.python.org/3.6/library/datetime.html#strftime-strptime-behavior -datetime_mapping={ - '%a': '%a', - '%A': '%A', - '%w': '%w', - '%d': 'dd', - '%b': '%b', - '%B': '%B', - '%m': 'mm', - '%y': 'yy', - '%Y': 'yyyy', - '%H': 'HH', - '%I': 'HH(12h)', - '%p': 'AMPM', - '%M': 'MM', - '%S': 'SS', - '%f': 'µµ', - '%z': 'UTC(+/-HHMM)', - '%Z': 'UTC(TZ)', - '%j': '%j', - '%U': 'ww', - '%W': 'ww', - '%c': '%c', - '%x': '%x', - '%X': '%X', - '%%': '%%', -} - - -def convert_datetime_format(format): - i=0 - new_format = "" - while i < len(format): - if format[i] == '%': - char = format[i:i+2] - new_format += datetime_mapping.get(char, char) - i += 2 - else: - new_format += format[i] - i += 1 - return new_format - def all_adherent(search_time=None): """ Fonction renvoyant tous les users adherents. Optimisee pour n'est @@ -203,164 +159,6 @@ def all_active_assigned_interfaces_count(): return all_active_interfaces_count().filter(ipv4__isnull=False) -class SortTable: - """ Class gathering uselful stuff to sort the colums of a table, according - to the column and order requested. It's used with a dict of possible - values and associated model_fields """ - - # All the possible possible values - # The naming convention is based on the URL or the views function - # The syntax to describe the sort to apply is a dict where the keys are - # the url value and the values are a list of model field name to use to - # order the request. They are applied in the order they are given. - # A 'default' might be provided to specify what to do if the requested col - # doesn't match any keys. - - USERS_INDEX = { - 'user_name': ['name'], - 'user_surname': ['surname'], - 'user_pseudo': ['pseudo'], - 'user_room': ['room'], - 'default': ['state', 'pseudo'] - } - USERS_INDEX_BAN = { - 'ban_user': ['user__pseudo'], - 'ban_start': ['date_start'], - 'ban_end': ['date_end'], - 'default': ['-date_end'] - } - USERS_INDEX_WHITE = { - 'white_user': ['user__pseudo'], - 'white_start': ['date_start'], - 'white_end': ['date_end'], - 'default': ['-date_end'] - } - USERS_INDEX_SCHOOL = { - 'school_name': ['name'], - 'default': ['name'] - } - MACHINES_INDEX = { - 'machine_name': ['name'], - 'default': ['pk'] - } - COTISATIONS_INDEX = { - 'cotis_user': ['user__pseudo'], - 'cotis_paiement': ['paiement__moyen'], - 'cotis_date': ['date'], - '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'], - 'control_paiement': ['paiement'], - 'control_date': ['date'], - 'control_valid': ['valid'], - 'control_control': ['control'], - 'control_id': ['id'], - 'control_user-id': ['user__id'], - 'default': ['-date'] - } - TOPOLOGIE_INDEX = { - 'switch_dns': ['interface__domain__name'], - 'switch_ip': ['interface__ipv4__ipv4'], - 'switch_loc': ['switchbay__name'], - 'switch_ports': ['number'], - 'switch_stack': ['stack__name'], - 'default': ['switchbay', 'stack', 'stack_member_id'] - } - TOPOLOGIE_INDEX_PORT = { - 'port_port': ['port'], - 'port_room': ['room__name'], - 'port_interface': ['machine_interface__domain__name'], - 'port_related': ['related__switch__name'], - 'port_radius': ['radius'], - 'port_vlan': ['vlan_force__name'], - 'default': ['port'] - } - TOPOLOGIE_INDEX_ROOM = { - 'room_name': ['name'], - 'default': ['name'] - } - TOPOLOGIE_INDEX_BUILDING = { - 'building_name': ['name'], - 'default': ['name'] - } - TOPOLOGIE_INDEX_BORNE = { - 'ap_name': ['interface__domain__name'], - 'ap_ip': ['interface__ipv4__ipv4'], - 'ap_mac': ['interface__mac_address'], - 'default': ['interface__domain__name'] - } - TOPOLOGIE_INDEX_STACK = { - 'stack_name': ['name'], - 'stack_id': ['stack_id'], - 'default': ['stack_id'], - } - TOPOLOGIE_INDEX_MODEL_SWITCH = { - 'model-switch_name': ['reference'], - 'model-switch_contructor': ['constructor__name'], - 'default': ['reference'], - } - TOPOLOGIE_INDEX_SWITCH_BAY = { - 'switch-bay_name': ['name'], - 'switch-bay_building': ['building__name'], - 'default': ['name'], - } - TOPOLOGIE_INDEX_CONSTRUCTOR_SWITCH = { - 'constructor-switch_name': ['name'], - 'default': ['name'], - } - LOGS_INDEX = { - 'sum_date': ['revision__date_created'], - 'default': ['-revision__date_created'], - } - LOGS_STATS_LOGS = { - 'logs_author': ['user__name'], - 'logs_date': ['date_created'], - 'default': ['-date_created'] - } - - @staticmethod - def sort(request, col, order, values): - """ Check if the given values are possible and add .order_by() and - a .reverse() as specified according to those values """ - fields = values.get(col, None) - if not fields: - fields = values.get('default', []) - request = request.order_by(*fields) - if values.get(col, None) and order == 'desc': - return request.reverse() - else: - return request - - -def re2o_paginator(request, query_set, pagination_number): - """Paginator script for list display in re2o. - :request: - :query_set: Query_set to paginate - :pagination_number: Number of entries to display""" - paginator = Paginator(query_set, pagination_number) - page = request.GET.get('page') - try: - results = paginator.page(page) - except PageNotAnInteger: - # If page is not an integer, deliver first page. - results = paginator.page(1) - except EmptyPage: - # If page is out of range (e.g. 9999), deliver last page of results. - results = paginator.page(paginator.num_pages) - return results - - def remove_user_room(room): """ Déménage de force l'ancien locataire de la chambre """ try: @@ -370,18 +168,3 @@ def remove_user_room(room): user.room = None user.save() - -def get_input_formats_help_text(input_formats): - """Returns a help text about the possible input formats""" - if len(input_formats) > 1: - help_text_template="Format: {main} {more}" - else: - help_text_template="Format: {main}" - more_text_template="" - help_text = help_text_template.format( - main=convert_datetime_format(input_formats[0]), - more=more_text_template.format( - '\n'.join(map(convert_datetime_format, input_formats)) - ) - ) - return help_text diff --git a/search/forms.py b/search/forms.py index 5c98415f..5fa5fca8 100644 --- a/search/forms.py +++ b/search/forms.py @@ -27,7 +27,7 @@ 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 +from re2o.base import get_input_formats_help_text CHOICES_USER = ( ('0', _("Active")), diff --git a/search/views.py b/search/views.py index a92b0105..eb0027ec 100644 --- a/search/views.py +++ b/search/views.py @@ -46,7 +46,7 @@ from search.forms import ( CHOICES_AFF, initial_choices ) -from re2o.utils import SortTable +from re2o.base import SortTable from re2o.acl import can_view_all diff --git a/topologie/views.py b/topologie/views.py index 0bd0f6c2..a4db2dc6 100644 --- a/topologie/views.py +++ b/topologie/views.py @@ -48,7 +48,10 @@ from django.utils.translation import ugettext as _ import tempfile from users.views import form -from re2o.utils import re2o_paginator, SortTable +from re2o.base import ( + re2o_paginator, + SortTable, +) from re2o.acl import ( can_create, can_edit, diff --git a/users/forms.py b/users/forms.py index 8b496019..b96e3ad3 100644 --- a/users/forms.py +++ b/users/forms.py @@ -45,7 +45,8 @@ from django.utils.safestring import mark_safe from machines.models import Interface, Machine, Nas from topologie.models import Port from preferences.models import OptionalUser -from re2o.utils import remove_user_room, get_input_formats_help_text +from re2o.utils import remove_user_room +from re2o.base import get_input_formats_help_text from re2o.mixins import FormRevMixin from re2o.field_permissions import FieldPermissionFormMixin @@ -444,6 +445,7 @@ class ClubForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): 'school', 'comment', 'room', + 'email', 'telephone', 'email', 'shell', diff --git a/users/models.py b/users/models.py index 63d0a875..f1577a24 100755 --- a/users/models.py +++ b/users/models.py @@ -81,6 +81,7 @@ from re2o.settings import LDAP, GID_RANGES, UID_RANGES from re2o.login import hashNT from re2o.field_permissions import FieldPermissionModelMixin from re2o.mixins import AclMixin, RevMixin +from re2o.base import smtp_check from cotisations.models import Cotisation, Facture, Paiement, Vente from machines.models import Domain, Interface, Machine, regen @@ -576,7 +577,8 @@ 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""" - if sys.version_info[0] >= 3: + if sys.version_info[0] >= 3 and self.state != self.STATE_ARCHIVE and\ + self.state != self.STATE_DISABLED: self.refresh_from_db() try: user_ldap = LdapUser.objects.get(uidNumber=self.uid_number) @@ -1889,6 +1891,9 @@ class EMailAddress(RevMixin, AclMixin, models.Model): def clean(self, *args, **kwargs): self.local_part = self.local_part.lower() - if "@" in self.local_part: - raise ValidationError(_("The local part must not contain @.")) + if "@" in self.local_part or "+" in self.local_part: + raise ValidationError(_("The local part must not contain @ or +.")) + result, reason = smtp_check(self.local_part) + if result: + raise ValidationError(reason) super(EMailAddress, self).clean(*args, **kwargs) diff --git a/users/views.py b/users/views.py index a94a7927..060610f8 100644 --- a/users/views.py +++ b/users/views.py @@ -57,8 +57,10 @@ from preferences.models import OptionalUser, GeneralOption, AssoOption from re2o.views import form from re2o.utils import ( all_has_access, - SortTable, - re2o_paginator +) +from re2o.base import ( + re2o_paginator, + SortTable ) from re2o.acl import ( can_create,