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,