From 586321fd8a326f719b09040448af8d0e64e6e1da Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Mon, 7 May 2018 17:20:55 +0200 Subject: [PATCH 1/7] =?UTF-8?q?Un=20seul=20d=C3=A9corateur=20pour=20les=20?= =?UTF-8?q?gouverner=20(presque)=20tous.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- re2o/acl.py | 215 ++++++++++++++++++---------------------------------- 1 file changed, 74 insertions(+), 141 deletions(-) diff --git a/re2o/acl.py b/re2o/acl.py index 6ab34350..b8be7a8d 100644 --- a/re2o/acl.py +++ b/re2o/acl.py @@ -28,135 +28,108 @@ 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): +def acl_base_decorator(method_name, *targets, **kwargs): + """Base decorator for acl. It checks if the 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. + """ + on_instance = kwargs.get('on_instance', True) + + def group_targets(): + current_target = None + current_fields = [] + for t in targets: + if isinstance(t, type) and issubclass(t, Model): + if current_target: + yield (current_target, current_fields) + current_target = t + current_fields = [] + else: + current_fields.append(t) + 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""" + instances = [] + + def process_target(target, fields): + 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) + 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, *chain(instances, args), **kwargs) + return wrapper + return decorator + + +def can_create(*models): """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 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 + return acl_base_decorator('can_create', *models, on_instance=False) -def can_edit(model, *field_list): +def can_edit(*targets): """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: - 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_edit', *targets) -def can_change(model, *field_list): +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 """ - 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. """ - 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,60 +160,20 @@ 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. """ - 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. """ - 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): From 84a901e3fc82ef4ccaf54491f0b5c50025a75654 Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Mon, 7 May 2018 18:57:08 +0200 Subject: [PATCH 2/7] =?UTF-8?q?Documentation=20des=20d=C3=A9corateurs=20d'?= =?UTF-8?q?ACL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- re2o/acl.py | 124 +++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 98 insertions(+), 26 deletions(-) diff --git a/re2o/acl.py b/re2o/acl.py index b8be7a8d..bce941b5 100644 --- a/re2o/acl.py +++ b/re2o/acl.py @@ -37,24 +37,89 @@ from django.urls import reverse def acl_base_decorator(method_name, *targets, **kwargs): - """Base decorator for acl. It checks if the 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. + """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. + **kwargs: There is only one keyword argument, `on_instance`, which + default value is `True`. 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. """ on_instance = kwargs.get('on_instance', True) def group_targets(): + """This generator parses the targets of the decorator, yielding + 2-tuples of (model, [fields]). + """ current_target = None current_fields = [] - for t in targets: - if isinstance(t, type) and issubclass(t, Model): + for target in targets: + if isinstance(target, type) and issubclass(target, Model): if current_target: yield (current_target, current_fields) - current_target = t + current_target = target current_fields = [] else: - current_fields.append(t) + current_fields.append(target) yield (current_target, current_fields) def decorator(view): @@ -65,6 +130,11 @@ def acl_base_decorator(method_name, *targets, **kwargs): 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) @@ -97,37 +167,37 @@ def acl_base_decorator(method_name, *targets, **kwargs): def can_create(*models): - """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. + """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 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. + """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. """ return acl_base_decorator('can_change', *targets) 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. """ return acl_base_decorator('can_delete', *targets) @@ -162,16 +232,18 @@ def can_delete_set(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. """ return acl_base_decorator('can_view', *targets) 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. """ return acl_base_decorator('can_view_all', *targets, on_instance=False) From f7d7a41586c068236bad9f6a6bcf4c7fd8dbf367 Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Mon, 7 May 2018 19:33:06 +0200 Subject: [PATCH 3/7] Autorise plusieurs apps dans can_view_app --- re2o/acl.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/re2o/acl.py b/re2o/acl.py index bce941b5..0c20828f 100644 --- a/re2o/acl.py +++ b/re2o/acl.py @@ -248,10 +248,11 @@ def can_view_all(*targets): 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() + for app_name in apps_name: + assert app_name in sys.modules.keys() def decorator(view): """The decorator to use on a specific view @@ -259,15 +260,16 @@ def can_view_app(app_name): 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)} - )) + for app_name in apps_name: + app = sys.modules[app_name] + can, msg = app.can_view(request.user) + if not can: + messages.error(request, msg) + return redirect(reverse( + 'users:profil', + kwargs={'userid': str(request.user.id)} + )) + return view(request, *args, **kwargs) return wrapper return decorator From a61d8d6ebd2dbae8741cdcdbe5eec4a556cf3b0c Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Mon, 7 May 2018 19:43:53 +0200 Subject: [PATCH 4/7] =?UTF-8?q?Factorisation=20des=20d=C3=A9corateurs=20AC?= =?UTF-8?q?L=20dans=20les=20vues.=20Fix=20#121?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- logs/views.py | 9 ++------- machines/views.py | 7 +------ preferences/views.py | 12 ++++-------- topologie/views.py | 9 +++------ 4 files changed, 10 insertions(+), 27 deletions(-) diff --git a/logs/views.py b/logs/views.py index 0acd4bd9..1fdda9fb 100644 --- a/logs/views.py +++ b/logs/views.py @@ -195,9 +195,7 @@ def revert_action(request, revision_id): @login_required -@can_view_all(IpList) -@can_view_all(Interface) -@can_view_all(User) +@can_view_all(IpList, Interface, User) def stats_general(request): """Statistiques générales affinées sur les ip, activées, utilisées par range, et les statistiques générales sur les users : users actifs, @@ -313,10 +311,7 @@ def stats_general(request): @login_required -@can_view_app('users') -@can_view_app('cotisations') -@can_view_app('machines') -@can_view_app('topologie') +@can_view_app('users', 'cotisations', 'machines', 'topologie') def stats_models(request): """Statistiques générales, affiche les comptages par models: nombre d'users, d'écoles, de droits, de bannissements, 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/views.py b/preferences/views.py index b2a6ba4c..f04a4084 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,8 @@ 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/topologie/views.py b/topologie/views.py index 283f4ffe..0c9eb0ab 100644 --- a/topologie/views.py +++ b/topologie/views.py @@ -200,9 +200,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 @@ -241,8 +239,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') @@ -957,7 +954,7 @@ edge[arrowhead=odot,arrowtail=dot]'''] {} -{} +{} {} From b1ac9fffbb557fd8109cbcdf0857df3c8efece88 Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Mon, 7 May 2018 20:03:12 +0200 Subject: [PATCH 5/7] PEP 8 pour preferences/views.py --- preferences/views.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/preferences/views.py b/preferences/views.py index f04a4084..b8ca39d2 100644 --- a/preferences/views.py +++ b/preferences/views.py @@ -145,7 +145,10 @@ def add_service(request): 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) + request.POST or None, + request.FILES or None, + instance=service_instance + ) if service.is_valid(): with transaction.atomic(), reversion.create_revision(): service.save() From 664fb7ae005a8efc33a58094214932da9ad5053c Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Mon, 7 May 2018 20:24:04 +0200 Subject: [PATCH 6/7] Utilisation de inutile. --- re2o/acl.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/re2o/acl.py b/re2o/acl.py index 0c20828f..7c0d5d73 100644 --- a/re2o/acl.py +++ b/re2o/acl.py @@ -36,7 +36,7 @@ from django.shortcuts import redirect from django.urls import reverse -def acl_base_decorator(method_name, *targets, **kwargs): +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 @@ -82,12 +82,11 @@ on_instance=False) on_instance=False) ``` But don't do that, it's silly. - **kwargs: There is only one keyword argument, `on_instance`, which - default value is `True`. 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. + 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 @@ -104,7 +103,6 @@ ModelC) ``` where `*args` and `**kwargs` are the original view arguments. """ - on_instance = kwargs.get('on_instance', True) def group_targets(): """This generator parses the targets of the decorator, yielding From 5c254fa026ec561056b84ce9ea4e7480897b744c Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Mon, 7 May 2018 22:01:32 +0200 Subject: [PATCH 7/7] factorise les can_view_app --- re2o/acl.py | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/re2o/acl.py b/re2o/acl.py index 7c0d5d73..d96a281a 100644 --- a/re2o/acl.py +++ b/re2o/acl.py @@ -111,7 +111,7 @@ ModelC) current_target = None current_fields = [] for target in targets: - if isinstance(target, type) and issubclass(target, Model): + if not isinstance(target, str): if current_target: yield (current_target, current_fields) current_target = target @@ -146,6 +146,7 @@ ModelC) for field in fields: can_change_fct = getattr(target, 'can_change_' + field) yield can_change_fct(request.user, *args, **kwargs) + error_messages = [ x[1] for x in chain.from_iterable( process_target(x[0], x[1]) for x in group_targets() @@ -251,25 +252,11 @@ def can_view_app(*apps_name): """ for app_name in apps_name: 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 - """ - for app_name in apps_name: - app = sys.modules[app_name] - can, msg = app.can_view(request.user) - if not can: - messages.error(request, msg) - 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', + *chain(sys.modules[app_name] for app_name in apps_name), + on_instance=False + ) def can_edit_history(view):