From 189d571fa15691bf10ff6055c771f9301374ce28 Mon Sep 17 00:00:00 2001 From: Valentin Rigal <rigal@teklia.com> Date: Tue, 15 Dec 2020 16:12:54 +0100 Subject: [PATCH] Implement the force approach --- arkindex/project/mixins.py | 67 ++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 24 deletions(-) diff --git a/arkindex/project/mixins.py b/arkindex/project/mixins.py index 12f0c472cc..f94a620d8c 100644 --- a/arkindex/project/mixins.py +++ b/arkindex/project/mixins.py @@ -1,6 +1,6 @@ from django.conf import settings from django.core.exceptions import PermissionDenied -from django.db.models import IntegerField, Q, Value, functions +from django.db.models import Case, IntegerField, Q, Value, When, functions from django.db.models.query_utils import DeferredAttribute from django.shortcuts import get_object_or_404 from django.views.decorators.cache import cache_page @@ -44,7 +44,7 @@ class ACLMixin(object): .filter(public=True) \ .annotate(max_level=Value(default_level, IntegerField())) - def rights_filter(self, model, level, public=False): + def rights_filter(self, model, level): """ Return a model queryset matching a given access level for this user. """ @@ -64,33 +64,52 @@ class ACLMixin(object): .annotate(max_level=Value(Role.Admin.value, IntegerField())) \ .order_by(*self.mixin_order_by_fields, 'id') - # Filter users rights and annotate the resulting level for those rights - queryset = model.objects \ - .filter( - # Filter instances with rights concerning this user. This may create duplicates - Q(memberships__user=self.user) - | Q(memberships__group__memberships__user=self.user) - ) \ - .annotate( - # Keep only the lowest level for each right via group - max_level=functions.Least( - 'memberships__level', - # In case of direct right, the group level will be skipped (Null value) - 'memberships__group__memberships__level' - ) + qs_filters = ( + # Filter corpora with a right (direct or via a group) + Q(memberships__user=self.user) + # Note: in case of direct right, the group level will be skipped (Null value) + | Q(memberships__group__memberships__user=self.user) + ) + if include_public: + # Also match public corpora for which this user has no right + qs_filters |= Q( + Q(public=True) + & ~Q(memberships__user=self.user) + & ~Q(memberships__group__memberships__user=self.user) ) - # Order by decreasing max_level to make sure we keep the max among all rights - queryset = queryset.filter(max_level__gte=level) \ - .order_by(*self.mixin_order_by_fields, 'id', '-max_level') \ - .distinct(*self.mixin_order_by_fields, 'id') - - # Use a join to add public instances as this is the more elegant solution + # Annotate instances for each right. This may return duplicated instances + max_level_annotation = functions.Least( + 'memberships__level', + # In case of direct right, group level will be skipped (Null value) + 'memberships__group__memberships__level' + ) if include_public: - queryset = queryset.union(self.get_public_instances(model, Role.Guest.value)) + # Also annotate public instances with no member right + max_level_annotation = Case( + When( + Q( + Q(public=True) + & ~Q(memberships__user=self.user) + & ~Q(memberships__group__memberships__user=self.user) + ), + # Set the rôle to guest on public instances + then=Value(Role.Guest.value) + ), + default=max_level_annotation, + output_field=IntegerField() + ) # Return distinct corpus with the max right level among matching rights - return queryset.order_by(*self.mixin_order_by_fields, 'id') + return model.objects \ + .filter(qs_filters) \ + .annotate(max_level=max_level_annotation) \ + .filter(max_level__gte=level) \ + .order_by( + # Order by decreasing max_level to make sure we keep the max among all rights + *self.mixin_order_by_fields, 'id', '-max_level' + ) \ + .distinct(*self.mixin_order_by_fields, 'id') def has_access(self, instance, level): self._check_level(level) -- GitLab