diff --git a/machines/views.py b/machines/views.py
index c3033049..75c2f483 100644
--- a/machines/views.py
+++ b/machines/views.py
@@ -1272,12 +1272,7 @@ def index_nas(request):
@login_required
-@can_view_all(SOA)
-@can_view_all(Mx)
-@can_view_all(Ns)
-@can_view_all(Txt)
-@can_view_all(Srv)
-@can_view_all(Extension)
+@can_view_all(SOA, Mx, Ns, Txt, Srv, Extension)
def index_extension(request):
""" View displaying the list of existing extensions, the list of
existing SOA records, the list of existing MX records , the list of
diff --git a/preferences/aes_field.py b/preferences/aes_field.py
index 1d3ffa54..302aa82b 100644
--- a/preferences/aes_field.py
+++ b/preferences/aes_field.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.
diff --git a/preferences/views.py b/preferences/views.py
index b2a6ba4c..b8ca39d2 100644
--- a/preferences/views.py
+++ b/preferences/views.py
@@ -58,13 +58,8 @@ from . import forms
@login_required
-@can_view_all(OptionalUser)
-@can_view_all(OptionalMachine)
-@can_view_all(OptionalTopologie)
-@can_view_all(GeneralOption)
-@can_view_all(AssoOption)
-@can_view_all(MailMessageOption)
-@can_view_all(HomeOption)
+@can_view_all(OptionalUser, OptionalMachine, OptionalTopologie, GeneralOption,
+ AssoOption, MailMessageOption, HomeOption)
def display_options(request):
"""Vue pour affichage des options (en vrac) classé selon les models
correspondants dans un tableau"""
@@ -149,7 +144,11 @@ def add_service(request):
@can_edit(Service)
def edit_service(request, service_instance, **_kwargs):
"""Edition des services affichés sur la page d'accueil"""
- service = ServiceForm(request.POST or None, request.FILES or None,instance=service_instance)
+ service = ServiceForm(
+ request.POST or None,
+ request.FILES or None,
+ instance=service_instance
+ )
if service.is_valid():
with transaction.atomic(), reversion.create_revision():
service.save()
diff --git a/re2o/acl.py b/re2o/acl.py
index 6ab34350..d96a281a 100644
--- a/re2o/acl.py
+++ b/re2o/acl.py
@@ -28,135 +28,177 @@ Here are defined some decorators that can be used in views to handle ACL.
from __future__ import unicode_literals
import sys
+from itertools import chain
+from django.db.models import Model
from django.contrib import messages
from django.shortcuts import redirect
from django.urls import reverse
-def can_create(model):
- """Decorator to check if an user can create a model.
- It assumes that a valid user exists in the request and that the model has a
- method can_create(user) which returns true if the user can create this kind
- of models.
+def acl_base_decorator(method_name, *targets, on_instance=True):
+ """Base decorator for acl. It checks if the `request.user` has the
+ permission by calling model.method_name. If the flag on_instance is True,
+ tries to get an instance of the model by calling
+ `model.get_instance(*args, **kwargs)` and runs `instance.mehod_name`
+ rather than model.method_name.
+
+ It is not intended to be used as is. It is a base for others ACL
+ decorators.
+
+ Args:
+ method_name: The name of the method which is to to be used for ACL.
+ (ex: 'can_edit') WARNING: if no method called 'method_name' exists,
+ then no error will be triggered, the decorator will act as if
+ permission was granted. This is to allow you to run ACL tests on
+ fields only. If the method exists, it has to return a 2-tuple
+ `(can, reason)` with `can` being a boolean stating whether the
+ access is granted and `reason` a message to be displayed if `can`
+ equals `False` (can be `None`)
+ *targets: The targets. Targets are specified like a sequence of models
+ and fields names. As an example
+ ```
+ acl_base_decorator('can_edit', ModelA, 'field1', 'field2', \
+ModelB, ModelC, 'field3', on_instance=False)
+ ```
+ will make the following calls (where `user` is the current user,
+ `*args` and `**kwargs` are the arguments initially passed to the
+ view):
+ - `ModelA.can_edit(user, *args, **kwargs)`
+ - `ModelA.can_change_field1(user, *args, **kwargs)`
+ - `ModelA.can_change_field2(user, *args, **kwargs)`
+ - `ModelB.can_edit(user, *args, **kwargs)`
+ - `ModelC.can_edit(user, *args, **kwargs)`
+ - `ModelC.can_change_field3(user, *args, **kwargs)`
+
+ Note that
+ ```
+ acl_base_decorator('can_edit', 'field1', ModelA, 'field2', \
+on_instance=False)
+ ```
+ would have the same effect that
+ ```
+ acl_base_decorator('can_edit', ModelA, 'field1', 'field2', \
+on_instance=False)
+ ```
+ But don't do that, it's silly.
+ on_instance: When `on_instance` equals `False`, the decorator runs the
+ ACL method on the model class rather than on an instance. If an
+ instance need to fetched, it is done calling the assumed existing
+ method `get_instance` of the model, with the arguments originally
+ passed to the view.
+
+ Returns:
+ The user is either redirected to their own page with an explanation
+ message if at least one access is not granted, or to the view. In order
+ to avoid duplicate DB calls, when the `on_instance` flag equals `True`,
+ the instances are passed to the view. Example, with this decorator:
+ ```
+ acl_base_decorator('can_edit', ModelA, 'field1', 'field2', ModelB,\
+ModelC)
+ ```
+ The view will be called like this:
+ ```
+ view(request, instance_of_A, instance_of_b, *args, **kwargs)
+ ```
+ where `*args` and `**kwargs` are the original view arguments.
"""
+
+ def group_targets():
+ """This generator parses the targets of the decorator, yielding
+ 2-tuples of (model, [fields]).
+ """
+ current_target = None
+ current_fields = []
+ for target in targets:
+ if not isinstance(target, str):
+ if current_target:
+ yield (current_target, current_fields)
+ current_target = target
+ current_fields = []
+ else:
+ current_fields.append(target)
+ yield (current_target, current_fields)
+
def decorator(view):
"""The decorator to use on a specific view
"""
def wrapper(request, *args, **kwargs):
- """The wrapper used for a specific request
- """
- can, msg = model.can_create(request.user, *args, **kwargs)
- if not can:
- messages.error(
- request, msg or "Vous ne pouvez pas accéder à ce menu")
- return redirect(reverse('index'))
- return view(request, *args, **kwargs)
- return wrapper
- return decorator
+ """The wrapper used for a specific request"""
+ instances = []
+ def process_target(target, fields):
+ """This function calls the methods on the target and checks for
+ the can_change_`field` method with the given fields. It also
+ stores the instances of models in order to avoid duplicate DB
+ calls for the view.
+ """
+ if on_instance:
+ try:
+ target = target.get_instance(*args, **kwargs)
+ instances.append(target)
+ except target.DoesNotExist:
+ yield False, u"Entrée inexistante"
+ return
+ if hasattr(target, method_name):
+ can_fct = getattr(target, method_name)
+ yield can_fct(request.user, *args, **kwargs)
+ for field in fields:
+ can_change_fct = getattr(target, 'can_change_' + field)
+ yield can_change_fct(request.user, *args, **kwargs)
-def can_edit(model, *field_list):
- """Decorator to check if an user can edit a model.
- It tries to get an instance of the model, using
- `model.get_instance(*args, **kwargs)` and assumes that the model has a
- method `can_edit(user)` which returns `true` if the user can edit this
- kind of models.
- """
- def decorator(view):
- """The decorator to use on a specific view
- """
- def wrapper(request, *args, **kwargs):
- """The wrapper used for a specific request
- """
- try:
- instance = model.get_instance(*args, **kwargs)
- except model.DoesNotExist:
- messages.error(request, u"Entrée inexistante")
- return redirect(reverse(
- 'users:profil',
- kwargs={'userid': str(request.user.id)}
- ))
- can, msg = instance.can_edit(request.user)
- if not can:
- messages.error(
- request, msg or "Vous ne pouvez pas accéder à ce menu")
- return redirect(reverse(
- 'users:profil',
- kwargs={'userid': str(request.user.id)}
- ))
- for field in field_list:
- can_change_fct = getattr(instance, 'can_change_' + field)
- can, msg = can_change_fct(request.user, *args, **kwargs)
- if not can:
+ error_messages = [
+ x[1] for x in chain.from_iterable(
+ process_target(x[0], x[1]) for x in group_targets()
+ ) if not x[0]
+ ]
+ if error_messages:
+ for msg in error_messages:
messages.error(
request, msg or "Vous ne pouvez pas accéder à ce menu")
- return redirect(reverse(
- 'users:profil',
- kwargs={'userid': str(request.user.id)}
- ))
- return view(request, instance, *args, **kwargs)
+ return redirect(reverse(
+ 'users:profil',
+ kwargs={'userid': str(request.user.id)}
+ ))
+ return view(request, *chain(instances, args), **kwargs)
return wrapper
return decorator
-def can_change(model, *field_list):
+def can_create(*models):
+ """Decorator to check if an user can create the given models. It runs
+ `acl_base_decorator` with the flag `on_instance=False` and the method
+ 'can_create'. See `acl_base_decorator` documentation for further details.
+ """
+ return acl_base_decorator('can_create', *models, on_instance=False)
+
+
+def can_edit(*targets):
+ """Decorator to check if an user can edit the models.
+ It runs `acl_base_decorator` with the flag `on_instance=True` and the
+ method 'can_edit'. See `acl_base_decorator` documentation for further
+ details.
+ """
+ return acl_base_decorator('can_edit', *targets)
+
+
+def can_change(*targets):
"""Decorator to check if an user can edit a field of a model class.
- Difference with can_edit : take a class and not an instance
+ Difference with can_edit : takes a class and not an instance
+ It runs `acl_base_decorator` with the flag `on_instance=False` and the
+ method 'can_change'. See `acl_base_decorator` documentation for further
+ details.
"""
- def decorator(view):
- """The decorator to use on a specific view
- """
- def wrapper(request, *args, **kwargs):
- """The wrapper used for a specific request
- """
- for field in field_list:
- can_change_fct = getattr(model, 'can_change_' + field)
- can, msg = can_change_fct(request.user, *args, **kwargs)
- if not can:
- messages.error(
- request, msg or "Vous ne pouvez pas accéder à ce menu")
- return redirect(reverse(
- 'users:profil',
- kwargs={'userid': str(request.user.id)}
- ))
- return view(request, *args, **kwargs)
- return wrapper
- return decorator
+ return acl_base_decorator('can_change', *targets)
-def can_delete(model):
+def can_delete(*targets):
"""Decorator to check if an user can delete a model.
- It tries to get an instance of the model, using
- `model.get_instance(*args, **kwargs)` and assumes that the model has a
- method `can_delete(user)` which returns `true` if the user can delete this
- kind of models.
+ It runs `acl_base_decorator` with the flag `on_instance=True` and the
+ method 'can_edit'. See `acl_base_decorator` documentation for further
+ details.
"""
- def decorator(view):
- """The decorator to use on a specific view
- """
- def wrapper(request, *args, **kwargs):
- """The wrapper used for a specific request
- """
- try:
- instance = model.get_instance(*args, **kwargs)
- except model.DoesNotExist:
- messages.error(request, u"Entrée inexistante")
- return redirect(reverse(
- 'users:profil',
- kwargs={'userid': str(request.user.id)}
- ))
- can, msg = instance.can_delete(request.user)
- if not can:
- messages.error(
- request, msg or "Vous ne pouvez pas accéder à ce menu")
- return redirect(reverse(
- 'users:profil',
- kwargs={'userid': str(request.user.id)}
- ))
- return view(request, instance, *args, **kwargs)
- return wrapper
- return decorator
+ return acl_base_decorator('can_delete', *targets)
def can_delete_set(model):
@@ -187,84 +229,34 @@ def can_delete_set(model):
return decorator
-def can_view(model):
+def can_view(*targets):
"""Decorator to check if an user can view a model.
- It tries to get an instance of the model, using
- `model.get_instance(*args, **kwargs)` and assumes that the model has a
- method `can_view(user)` which returns `true` if the user can view this
- kind of models.
+ It runs `acl_base_decorator` with the flag `on_instance=True` and the
+ method 'can_view'. See `acl_base_decorator` documentation for further
+ details.
"""
- def decorator(view):
- """The decorator to use on a specific view
- """
- def wrapper(request, *args, **kwargs):
- """The wrapper used for a specific request
- """
- try:
- instance = model.get_instance(*args, **kwargs)
- except model.DoesNotExist:
- messages.error(request, u"Entrée inexistante")
- 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 "Vous ne pouvez pas accéder à ce menu")
- return redirect(reverse(
- 'users:profil',
- kwargs={'userid': str(request.user.id)}
- ))
- return view(request, instance, *args, **kwargs)
- return wrapper
- return decorator
+ return acl_base_decorator('can_view', *targets)
-def can_view_all(model):
+def can_view_all(*targets):
"""Decorator to check if an user can view a class of model.
+ It runs `acl_base_decorator` with the flag `on_instance=False` and the
+ method 'can_view_all'. See `acl_base_decorator` documentation for further
+ details.
"""
- def decorator(view):
- """The decorator to use on a specific view
- """
- def wrapper(request, *args, **kwargs):
- """The wrapper used for a specific request
- """
- can, msg = model.can_view_all(request.user)
- if not can:
- messages.error(
- request, msg or "Vous ne pouvez pas accéder à ce menu")
- return redirect(reverse(
- 'users:profil',
- kwargs={'userid': str(request.user.id)}
- ))
- return view(request, *args, **kwargs)
- return wrapper
- return decorator
+ return acl_base_decorator('can_view_all', *targets, on_instance=False)
-def can_view_app(app_name):
- """Decorator to check if an user can view an application.
+def can_view_app(*apps_name):
+ """Decorator to check if an user can view the applications.
"""
- assert app_name in sys.modules.keys()
-
- def decorator(view):
- """The decorator to use on a specific view
- """
- def wrapper(request, *args, **kwargs):
- """The wrapper used for a specific request
- """
- app = sys.modules[app_name]
- can, msg = app.can_view(request.user)
- if can:
- return view(request, *args, **kwargs)
- messages.error(request, msg)
- return redirect(reverse(
- 'users:profil',
- kwargs={'userid': str(request.user.id)}
- ))
- return wrapper
- return decorator
+ for app_name in apps_name:
+ assert app_name in sys.modules.keys()
+ return acl_base_decorator(
+ 'can_view',
+ *chain(sys.modules[app_name] for app_name in apps_name),
+ on_instance=False
+ )
def can_edit_history(view):
diff --git a/re2o/locale/fr/LC_MESSAGES/django.mo b/re2o/locale/fr/LC_MESSAGES/django.mo
index eb533f3e..b05137c3 100644
Binary files a/re2o/locale/fr/LC_MESSAGES/django.mo and b/re2o/locale/fr/LC_MESSAGES/django.mo differ
diff --git a/re2o/locale/fr/LC_MESSAGES/django.po b/re2o/locale/fr/LC_MESSAGES/django.po
index 2cabc6ec..1ece2026 100644
--- a/re2o/locale/fr/LC_MESSAGES/django.po
+++ b/re2o/locale/fr/LC_MESSAGES/django.po
@@ -30,6 +30,14 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
+#: settings.py:140
+msgid "English"
+msgstr "Anglais"
+
+#: settings.py:141
+msgid "French"
+msgstr "Français"
+
#: templates/re2o/about.html:29 templates/re2o/about.html:35
msgid "About Re2o"
msgstr "A propos de Re2o"
@@ -147,6 +155,10 @@ msgstr ""
msgid "Dependencies"
msgstr "Dépendances"
+#: templates/re2o/buttons/setlang.html:34
+msgid "Translation in development"
+msgstr "Traduction en développement"
+
#: views.py:172
msgid "No Git repository configured"
msgstr "Aucun repository git configuré"
diff --git a/re2o/settings.py b/re2o/settings.py
index bac8982b..52606b6d 100644
--- a/re2o/settings.py
+++ b/re2o/settings.py
@@ -37,6 +37,7 @@ from __future__ import unicode_literals
import os
from .settings_local import *
+from django.utils.translation import ugettext_lazy as _
# The root directory for the project
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
@@ -135,6 +136,10 @@ LOCALE_PATHS = [
# For translations outside of apps
os.path.join(BASE_DIR, 'templates', 'locale').replace('\\', '/')
]
+LANGUAGES = [
+ ('en', _('English')),
+ ('fr', _('French'))
+]
# Should use time zone ?
USE_TZ = True
diff --git a/re2o/urls.py b/re2o/urls.py
index b1cccfd9..47172521 100644
--- a/re2o/urls.py
+++ b/re2o/urls.py
@@ -55,6 +55,7 @@ urlpatterns = [
url(r'^about/$', about_page, name='about'),
url('^logout/', auth_views.logout, {'next_page': '/'}),
url('^', include('django.contrib.auth.urls')),
+ url(r'^i18n/', include('django.conf.urls.i18n')),
url(r'^admin/', include(admin.site.urls)),
url(r'^users/', include('users.urls', namespace='users')),
url(r'^search/', include('search.urls', namespace='search')),
diff --git a/re2o/utils.py b/re2o/utils.py
index 5a4f9400..75304369 100644
--- a/re2o/utils.py
+++ b/re2o/utils.py
@@ -44,6 +44,49 @@ 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
@@ -318,3 +361,19 @@ def remove_user_room(room):
return
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 b0668ef9..8cdb1cc1 100644
--- a/search/forms.py
+++ b/search/forms.py
@@ -26,6 +26,7 @@ from __future__ import unicode_literals
from django import forms
from django.forms import Form
+from re2o.utils import get_input_formats_help_text
CHOICES_USER = (
('0', 'Actifs'),
@@ -91,12 +92,17 @@ class SearchFormPlus(Form):
s = forms.DateField(
required=False,
label="Date de début",
- help_text='DD/MM/YYYY',
- input_formats=['%d/%m/%Y']
)
e = forms.DateField(
required=False,
- help_text='DD/MM/YYYY',
- input_formats=['%d/%m/%Y'],
label="Date de fin"
)
+
+ def __init__(self, *args, **kwargs):
+ super(SearchFormPlus, self).__init__(*args, **kwargs)
+ self.fields['s'].help_text = get_input_formats_help_text(
+ self.fields['s'].input_formats
+ )
+ self.fields['e'].help_text = get_input_formats_help_text(
+ self.fields['e'].input_formats
+ )
diff --git a/static/css/base.css b/static/css/base.css
index 7338fab6..b6a7ae26 100644
--- a/static/css/base.css
+++ b/static/css/base.css
@@ -35,6 +35,26 @@ footer a {
border-radius: 0;
}
+/* Add right colors for buttons in dropdown in navbar-inverse (else it is light
+ * gray on white bg and white when hovered */
+.navbar-inverse .dropdown-menu .btn-link {
+ text-decoration: none;
+ color: #262626;
+ padding: 0;
+}
+.navbar-inverse .dropdown-menu .btn-link:hover {
+ background-color: #f5f5f5;
+}
+@media screen and (max-width: 767px) {
+ .navbar-inverse .dropdown-menu .btn-link {
+ color: #9d9d9d;
+ }
+ .navbar-inverse .dropdown-menu .btn-link:hover {
+ color: #fff;
+ background-color: transparent;
+ }
+}
+
/* Set height of the grid so .sidenav can be 100% (adjust as needed) */
.row.content {
height: 100%;
diff --git a/templates/base.html b/templates/base.html
index 297cac89..f198feec 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -118,7 +118,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% acl_end %}
{% if not request.user.is_authenticated %}
{% if var_sa %}
@@ -145,48 +145,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
- {% endif %}
-
-
- {% comment %}
-
-
-
-
- {% if not request.user.is_authenticated %}
- {% if var_sa %}
-
diff --git a/topologie/templates/topologie/index_p.html b/topologie/templates/topologie/index_p.html
index 0bd62039..138cc62c 100644
--- a/topologie/templates/topologie/index_p.html
+++ b/topologie/templates/topologie/index_p.html
@@ -36,6 +36,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
Ajouter des ports
{% acl_end %}
+{% include "topologie/aff_repr_switch.html" with port_list=port_list %}
{% include "topologie/aff_port.html" with port_list=port_list %}
diff --git a/topologie/views.py b/topologie/views.py
index 5ca5eb7a..992e1df1 100644
--- a/topologie/views.py
+++ b/topologie/views.py
@@ -208,9 +208,7 @@ def index_ap(request):
@login_required
-@can_view_all(Stack)
-@can_view_all(Building)
-@can_view_all(SwitchBay)
+@can_view_all(Stack, Building, SwitchBay)
def index_physical_grouping(request):
"""Affichage de la liste des stacks (affiche l'ensemble des switches)"""
stack_list = (Stack.objects
@@ -249,8 +247,7 @@ def index_physical_grouping(request):
@login_required
-@can_view_all(ModelSwitch)
-@can_view_all(ConstructorSwitch)
+@can_view_all(ModelSwitch, ConstructorSwitch)
def index_model_switch(request):
""" Affichage de l'ensemble des modèles de switches"""
model_switch_list = ModelSwitch.objects.select_related('constructor')
diff --git a/users/forms.py b/users/forms.py
index 0a17df8b..b9d2b826 100644
--- a/users/forms.py
+++ b/users/forms.py
@@ -41,7 +41,7 @@ from django.utils import timezone
from django.contrib.auth.models import Group, Permission
from preferences.models import OptionalUser
-from re2o.utils import remove_user_room
+from re2o.utils import remove_user_room, get_input_formats_help_text
from re2o.mixins import FormRevMixin
from re2o.field_permissions import FieldPermissionFormMixin
@@ -422,12 +422,19 @@ class ServiceUserForm(FormRevMixin, ModelForm):
class Meta:
model = ServiceUser
- fields = ('pseudo', 'access_group')
+ fields = ('pseudo', 'access_group','comment')
def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(ServiceUserForm, self).__init__(*args, prefix=prefix, **kwargs)
+ def save(self, commit=True):
+ """Changement du mot de passe"""
+ user = super(ServiceUserForm, self).save(commit=False)
+ if self.cleaned_data['password']:
+ user.set_password(self.cleaned_data.get("password"))
+ user.save()
+
class EditServiceUserForm(ServiceUserForm):
"""Formulaire d'edition de base d'un service user. Ne permet
@@ -447,7 +454,7 @@ class StateForm(FormRevMixin, ModelForm):
super(StateForm, self).__init__(*args, prefix=prefix, **kwargs)
-class GroupForm(FormRevMixin, ModelForm):
+class GroupForm(FieldPermissionFormMixin, FormRevMixin, ModelForm):
""" Gestion des groupes d'un user"""
groups = forms.ModelMultipleChoiceField(
Group.objects.all(),
@@ -457,11 +464,13 @@ class GroupForm(FormRevMixin, ModelForm):
class Meta:
model = User
- fields = ['groups']
+ fields = ['is_superuser', 'groups']
def __init__(self, *args, **kwargs):
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"
class SchoolForm(FormRevMixin, ModelForm):
@@ -558,6 +567,9 @@ class BanForm(FormRevMixin, ModelForm):
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'].help_text = get_input_formats_help_text(
+ self.fields['date_end'].input_formats
+ )
class Meta:
model = Ban
@@ -570,6 +582,9 @@ class WhitelistForm(FormRevMixin, ModelForm):
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'].help_text = get_input_formats_help_text(
+ self.fields['date_end'].input_formats
+ )
class Meta:
model = Whitelist
diff --git a/users/migrations/0072_auto_20180426_2021.py b/users/migrations/0072_auto_20180426_2021.py
new file mode 100644
index 00000000..f60103e0
--- /dev/null
+++ b/users/migrations/0072_auto_20180426_2021.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2018-04-26 18:21
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('users', '0071_auto_20180415_1252'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='ban',
+ name='date_end',
+ field=models.DateTimeField(),
+ ),
+ migrations.AlterField(
+ model_name='whitelist',
+ name='date_end',
+ field=models.DateTimeField(),
+ ),
+ ]
diff --git a/users/models.py b/users/models.py
index f7bfc128..1e27e0f2 100644
--- a/users/models.py
+++ b/users/models.py
@@ -812,6 +812,18 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser,
"Droit requis pour éditer les groupes de l'user"
)
+ @staticmethod
+ def can_change_is_superuser(user_request, *_args, **_kwargs):
+ """ Check if an user can change a is_superuser flag
+
+ :param user_request: The user who request
+ :returns: a message and a boolean which is True if permission is granted.
+ """
+ return (
+ user_request.is_superuser,
+ "Droit superuser requis pour éditer le flag superuser"
+ )
+
def can_view(self, user_request, *_args, **_kwargs):
"""Check if an user can view an user object.
@@ -1229,7 +1241,7 @@ class Ban(RevMixin, AclMixin, models.Model):
user = models.ForeignKey('User', on_delete=models.PROTECT)
raison = models.CharField(max_length=255)
date_start = models.DateTimeField(auto_now_add=True)
- date_end = models.DateTimeField(help_text='%d/%m/%y %H:%M:%S')
+ date_end = models.DateTimeField()
state = models.IntegerField(choices=STATES, default=STATE_HARD)
class Meta:
@@ -1314,7 +1326,7 @@ class Whitelist(RevMixin, AclMixin, models.Model):
user = models.ForeignKey('User', on_delete=models.PROTECT)
raison = models.CharField(max_length=255)
date_start = models.DateTimeField(auto_now_add=True)
- date_end = models.DateTimeField(help_text='%d/%m/%y %H:%M:%S')
+ date_end = models.DateTimeField()
class Meta:
permissions = (
diff --git a/users/templates/users/aff_listright.html b/users/templates/users/aff_listright.html
index be94b146..8906b38e 100644
--- a/users/templates/users/aff_listright.html
+++ b/users/templates/users/aff_listright.html
@@ -33,6 +33,44 @@ with this program; if not, write to the Free Software Foundation, Inc.,
+
+ {% endif %}
{% for listright in listright_list %}
@@ -48,9 +86,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
-
-
-
{{ listright.details }}
+
+
+
{{ listright.details }}
{% include 'buttons/edit.html' with href='users:edit-listright' id=listright.id %}
{% include 'buttons/history.html' with href='users:history' name='listright' id=listright.id %}
diff --git a/users/templates/users/profil.html b/users/templates/users/profil.html
index 169524f5..abba61a2 100644
--- a/users/templates/users/profil.html
+++ b/users/templates/users/profil.html
@@ -245,7 +245,7 @@ non adhérent{% endif %} et votre connexion est {% if users.has_access %}