diff --git a/cotisations/views_autocomplete.py b/cotisations/views_autocomplete.py index 70afe855..1e634813 100644 --- a/cotisations/views_autocomplete.py +++ b/cotisations/views_autocomplete.py @@ -37,7 +37,7 @@ from .models import ( Banque ) -from re2o.mixins import AutocompleteViewMixin +from re2o.views import AutocompleteViewMixin from re2o.acl import ( can_view_all, diff --git a/re2o/acl.py b/re2o/acl.py index f74427ea..692df905 100644 --- a/re2o/acl.py +++ b/re2o/acl.py @@ -369,6 +369,15 @@ def can_view_all(*targets): return acl_base_decorator("can_view_all", *targets, on_instance=False) +def can_list(*targets): + """Decorator to check if an user can list a class of model. + It runs `acl_base_decorator` with the flag `on_instance=False` and the + method 'can_list'. See `acl_base_decorator` documentation for further + details. + """ + return acl_base_decorator("can_list", *targets, on_instance=False) + + def can_view_app(*apps_name): """Decorator to check if an user can view the applications.""" for app_name in apps_name: diff --git a/re2o/mixins.py b/re2o/mixins.py index a57a0628..df5d7310 100644 --- a/re2o/mixins.py +++ b/re2o/mixins.py @@ -29,6 +29,7 @@ from django.utils.translation import ugettext as _ from dal import autocomplete + class RevMixin(object): """A mixin to subclass the save and delete function of a model to enforce the versioning of the object before those actions @@ -80,6 +81,8 @@ class AclMixin(object): :can_view: Applied on an instance, return if the user can view the instance :can_view_all: Applied on a class, return if the user can view all + instances + :can_list: Applied on a class, return if the user can list all instances""" @classmethod @@ -209,6 +212,28 @@ class AclMixin(object): (permission,), ) + @classmethod + def can_list(cls, user_request, *_args, **_kwargs): + """Check if a user can list all instances of an object + + Parameters: + user_request: User calling for this action + + Returns: + Boolean: True if user_request has the right access to do it, else + false with reason for reject authorization + """ + permission = cls.get_modulename() + ".view_" + cls.get_classname() + can = user_request.has_perm(permission) + return ( + can, + _("You don't have the right to list every %s object.") % cls.get_classname() + if not can + else None, + (permission,), + cls.objects.all() if can else None, + ) + def can_view(self, user_request, *_args, **_kwargs): """Check if a user can view an instance of an object @@ -272,13 +297,3 @@ class AutocompleteMultipleModelMixin(autocomplete.ModelSelect2Multiple): attrs["data-minimum-results-for-search"] = attrs.get("data-minimum-results-for-search", 10) return attrs - -class AutocompleteViewMixin(autocomplete.Select2QuerySetView): - obj_type = None # This MUST be overridden by child class - query_filter = "name__icontains" # Override this if necessary - - def get_queryset(self): - query_set = self.obj_type.objects.all() - if self.q: - query_set = query_set.filter(**{ self.query_filter: self.q}) - return query_set diff --git a/re2o/templatetags/acl.py b/re2o/templatetags/acl.py index 132239be..4c88b207 100644 --- a/re2o/templatetags/acl.py +++ b/re2o/templatetags/acl.py @@ -141,6 +141,8 @@ def get_callback(tag_name, obj=None): return acl_fct(obj.can_view_all, False) if tag_name == "cannot_view_all": return acl_fct(obj.can_view_all, True) + if tag_name == "can_list": + return acl_fct(obj.can_list, False) if tag_name == "can_view_app": return acl_fct( lambda x: ( @@ -296,6 +298,7 @@ def acl_change_filter(parser, token): @register.tag("cannot_delete_all") @register.tag("can_view_all") @register.tag("cannot_view_all") +@register.tag("can_list") def acl_model_filter(parser, token): """Generic definition of an acl templatetag for acl based on model""" diff --git a/re2o/views.py b/re2o/views.py index 51f36b1a..359c3e81 100644 --- a/re2o/views.py +++ b/re2o/views.py @@ -33,6 +33,10 @@ from django.template.context_processors import csrf from django.conf import settings from django.utils.translation import ugettext as _ from django.views.decorators.cache import cache_page +from django.contrib.auth.decorators import login_required +from django.contrib.auth.mixins import LoginRequiredMixin +from django.utils.decorators import method_decorator +from dal import autocomplete from preferences.models import ( Service, @@ -43,6 +47,7 @@ from preferences.models import ( Mandate, ) +from .acl import can_list from .contributors import CONTRIBUTORS from importlib import import_module from re2o.settings_local import OPTIONNAL_APPS_RE2O @@ -169,3 +174,21 @@ def handler500(request): def handler404(request): """The handler view for a 404 error""" return render(request, "errors/404.html", status=404) + + +class AutocompleteViewMixin(LoginRequiredMixin, autocomplete.Select2QuerySetView): + obj_type = None # This MUST be overridden by child class + query_set = None + query_filter = "name__icontains" # Override this if necessary + + def get_queryset(self): + + can, reason, _permission, query_set = self.obj_type.can_list(self.request.user) + + self.query_set = query_set + if hasattr(self, "filter_results"): + self.filter_results() + else: + if self.q: + self.query_set = self.query_set.filter(**{ self.query_filter: self.q}) + return self.query_set diff --git a/topologie/views_autocomplete.py b/topologie/views_autocomplete.py index bef31fdb..801b1572 100644 --- a/topologie/views_autocomplete.py +++ b/topologie/views_autocomplete.py @@ -44,7 +44,7 @@ from .models import ( SwitchBay, ) -from re2o.mixins import AutocompleteViewMixin +from re2o.views import AutocompleteViewMixin from re2o.acl import ( can_view_all, diff --git a/users/views_autocomplete.py b/users/views_autocomplete.py index 94f5a93f..b565a4ce 100644 --- a/users/views_autocomplete.py +++ b/users/views_autocomplete.py @@ -45,7 +45,7 @@ from .models import ( EMailAddress, ) -from re2o.mixins import AutocompleteViewMixin +from re2o.views import AutocompleteViewMixin from re2o.acl import ( can_view_all,