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