diff --git a/apps/api/viewsets.py b/apps/api/viewsets.py index 6c5a207a..c700339f 100644 --- a/apps/api/viewsets.py +++ b/apps/api/viewsets.py @@ -5,15 +5,19 @@ from django.contrib.contenttypes.models import ContentType from member.backends import PermissionBackend from rest_framework import viewsets +from note_kfet.middlewares import get_current_authenticated_user + class ReadProtectedModelViewSet(viewsets.ModelViewSet): """ Protect a ModelViewSet by filtering the objects that the user cannot see. """ - def get_queryset(self): - model = ContentType.objects.get_for_model(self.serializer_class.Meta.model) - return super().get_queryset().filter(PermissionBackend.filter_queryset(self.request.user, model, "view")) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + model = ContentType.objects.get_for_model(self.serializer_class.Meta.model).model_class() + user = get_current_authenticated_user() + self.queryset = model.objects.filter(PermissionBackend.filter_queryset(user, model, "view")) class ReadOnlyProtectedModelViewSet(viewsets.ReadOnlyModelViewSet): @@ -21,6 +25,8 @@ class ReadOnlyProtectedModelViewSet(viewsets.ReadOnlyModelViewSet): Protect a ReadOnlyModelViewSet by filtering the objects that the user cannot see. """ - def get_queryset(self): - model = ContentType.objects.get_for_model(self.serializer_class.Meta.model) - return super().get_queryset().filter(PermissionBackend.filter_queryset(self.request.user, model, "view")) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + model = ContentType.objects.get_for_model(self.serializer_class.Meta.model).model_class() + user = get_current_authenticated_user() + self.queryset = model.objects.filter(PermissionBackend.filter_queryset(user, model, "view")) diff --git a/apps/member/backends.py b/apps/member/backends.py index 099ef8a8..90dc70f8 100644 --- a/apps/member/backends.py +++ b/apps/member/backends.py @@ -19,30 +19,28 @@ class PermissionBackend(ModelBackend): @staticmethod def permissions(user, model, type): - for membership in Membership.objects.filter(user=user).all(): - if not membership.valid() or membership.roles is None: - continue - - for permission in Permission.objects.filter( - rolepermissions__role=membership.roles, - model__app_label=model.app_label, # For polymorphic models, we don't filter on model type - type=type - ).all(): - permission = permission.about( - user=user, - club=membership.club, - User=User, - Club=Club, - Membership=Membership, - Note=Note, - NoteUser=NoteUser, - NoteClub=NoteClub, - NoteSpecial=NoteSpecial, - F=F, - Q=Q - ) - if permission.mask.rank <= get_current_session().get("permission_mask", 0): - yield permission + for permission in Permission.objects.annotate(club=F("rolepermissions__role__membership__club")) \ + .filter( + rolepermissions__role__membership__user=user, + model__app_label=model.app_label, # For polymorphic models, we don't filter on model type + type=type, + ).all(): + club = Club.objects.get(pk=permission.club) + permission = permission.about( + user=user, + club=club, + User=User, + Club=Club, + Membership=Membership, + Note=Note, + NoteUser=NoteUser, + NoteClub=NoteClub, + NoteSpecial=NoteSpecial, + F=F, + Q=Q + ) + if permission.mask.rank <= get_current_session().get("permission_mask", 0): + yield permission @staticmethod def filter_queryset(user, model, t, field=None): @@ -55,6 +53,9 @@ class PermissionBackend(ModelBackend): :return: A query that corresponds to the filter to give to a queryset """ + from time import time + ti = time() + if user.is_superuser and get_current_session().get("permission_mask", 0) >= 42: # Superusers have all rights return Q() @@ -64,11 +65,13 @@ class PermissionBackend(ModelBackend): # Never satisfied query = Q(pk=-1) - for perm in PermissionBackend.permissions(user, model, t): + perms = PermissionBackend.permissions(user, model, t) + for perm in perms: if perm.field and field != perm.field: continue if perm.type != t or perm.model != model: continue + perm.update_query() query = query | perm.query return query diff --git a/apps/note/api/views.py b/apps/note/api/views.py index e09557f4..f4bf5668 100644 --- a/apps/note/api/views.py +++ b/apps/note/api/views.py @@ -6,7 +6,6 @@ from django_filters.rest_framework import DjangoFilterBackend from rest_framework.filters import OrderingFilter, SearchFilter from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet -from member.backends import PermissionBackend from .serializers import NotePolymorphicSerializer, AliasSerializer, TemplateCategorySerializer, \ TransactionTemplateSerializer, TransactionPolymorphicSerializer from ..models.notes import Note, Alias @@ -30,7 +29,7 @@ class NotePolymorphicViewSet(ReadOnlyProtectedModelViewSet): Parse query and apply filters. :return: The filtered set of requested notes """ - queryset = super().get_queryset().filter(PermissionBackend.filter_queryset(self.request.user, Note, "view")) + queryset = super().get_queryset() alias = self.request.query_params.get("alias", ".*") queryset = queryset.filter( @@ -57,7 +56,7 @@ class AliasViewSet(ReadProtectedModelViewSet): :return: The filtered set of requested aliases """ - queryset = super().get_queryset().filter(PermissionBackend.filter_queryset(self.request.user, Alias, "view")) + queryset = super().get_queryset() alias = self.request.query_params.get("alias", ".*") queryset = queryset.filter( diff --git a/apps/permission/models.py b/apps/permission/models.py index f7752517..fca4b36b 100644 --- a/apps/permission/models.py +++ b/apps/permission/models.py @@ -14,12 +14,14 @@ from django.utils.translation import gettext_lazy as _ class InstancedPermission: - def __init__(self, model, query, type, field, mask): + def __init__(self, model, query, type, field, mask, **kwargs): self.model = model - self.query = query + self.raw_query = query + self.query = None self.type = type self.field = field self.mask = mask + self.kwargs = kwargs def applies(self, obj, permission_type, field_name=None): """ @@ -33,6 +35,8 @@ class InstancedPermission: if self.type == 'add': if permission_type == self.type: + self.update_query() + # Don't increase indexes obj.pk = 0 # Force insertion, no data verification, no trigger @@ -45,10 +49,16 @@ class InstancedPermission: if permission_type == self.type: if self.field and field_name != self.field: return False + self.update_query() return obj in self.model.model_class().objects.filter(self.query).all() else: return False + def update_query(self): + if not self.query: + # noinspection PyProtectedMember + self.query = Permission._about(self.raw_query, **self.kwargs) + def __repr__(self): if self.field: return _("Can {type} {model}.{field} in {query}").format(type=self.type, model=self.model, field=self.field, query=self.query) @@ -178,7 +188,8 @@ class Permission(models.Model): field = getattr(field, value[i]) return field - def _about(self, query, **kwargs): + @staticmethod + def _about(query, **kwargs): if len(query) == 0: # The query is either [] or {} and # applies to all objects of the model @@ -186,11 +197,11 @@ class Permission(models.Model): return Q(pk=F("pk")) if isinstance(query, list): if query[0] == 'AND': - return functools.reduce(operator.and_, [self._about(query, **kwargs) for query in query[1:]]) + return functools.reduce(operator.and_, [Permission._about(query, **kwargs) for query in query[1:]]) elif query[0] == 'OR': - return functools.reduce(operator.or_, [self._about(query, **kwargs) for query in query[1:]]) + return functools.reduce(operator.or_, [Permission._about(query, **kwargs) for query in query[1:]]) elif query[0] == 'NOT': - return ~self._about(query[1], **kwargs) + return ~Permission._about(query[1], **kwargs) elif isinstance(query, dict): q_kwargs = {} for key in query: @@ -206,7 +217,7 @@ class Permission(models.Model): return Q(**q_kwargs) else: # TODO: find a better way to crash here - raise Exception("query {} is wrong".format(self.query)) + raise Exception("query {} is wrong".format(query)) def about(self, **kwargs): """ @@ -214,8 +225,8 @@ class Permission(models.Model): replaced by their values and the query interpreted """ query = json.loads(self.query) - query = self._about(query, **kwargs) - return InstancedPermission(self.model, query, self.type, self.field, self.mask) + # query = self._about(query, **kwargs) + return InstancedPermission(self.model, query, self.type, self.field, self.mask, **kwargs) def __str__(self): if self.field: diff --git a/apps/permission/templatetags/perms.py b/apps/permission/templatetags/perms.py index 859f1ef2..79a9640f 100644 --- a/apps/permission/templatetags/perms.py +++ b/apps/permission/templatetags/perms.py @@ -13,27 +13,35 @@ from member.backends import PermissionBackend @stringfilter def not_empty_model_list(model_name): user = get_current_authenticated_user() + session = get_current_session() if user is None: return False - elif user.is_superuser and get_current_session().get("permission_mask", 0) >= 42: + elif user.is_superuser and session.get("permission_mask", 0) >= 42: return True + if session.get("not_empty_model_list_" + model_name, None): + return session.get("not_empty_model_list_" + model_name, None) == 1 spl = model_name.split(".") ct = ContentType.objects.get(app_label=spl[0], model=spl[1]) qs = ct.model_class().objects.filter(PermissionBackend.filter_queryset(user, ct, "view")) - return qs.exists() + session["not_empty_model_list_" + model_name] = 1 if qs.exists() else 2 + return session.get("not_empty_model_list_" + model_name) == 1 @stringfilter def not_empty_model_change_list(model_name): user = get_current_authenticated_user() + session = get_current_session() if user is None: return False - elif user.is_superuser and get_current_session().get("permission_mask", 0) >= 42: + elif user.is_superuser and session.get("permission_mask", 0) >= 42: return True + if session.get("not_empty_model_change_list_" + model_name, None): + return session.get("not_empty_model_change_list_" + model_name, None) == 1 spl = model_name.split(".") ct = ContentType.objects.get(app_label=spl[0], model=spl[1]) qs = ct.model_class().objects.filter(PermissionBackend.filter_queryset(user, ct, "change")) - return qs.exists() + session["not_empty_model_change_list_" + model_name] = 1 if qs.exists() else 2 + return session.get("not_empty_model_change_list_" + model_name) == 1 register = template.Library() diff --git a/static/js/base.js b/static/js/base.js index 1cc242e8..4f94893e 100644 --- a/static/js/base.js +++ b/static/js/base.js @@ -199,6 +199,7 @@ function autoCompleteNote(field_id, alias_matched_id, note_list_id, notes, notes // When the user click on an alias, the associated note is added to the emitters alias_obj.click(function () { field.val(""); + old_pattern = ""; // If the note is already an emitter, we increase the quantity var disp = null; notes_display.forEach(function (d) {