diff --git a/arkindex/project/mixins.py b/arkindex/project/mixins.py
index 12f0c472cc3d103612e26d7145d37295713b8df9..386844b09178c2e0c954dd522d01a6f59d128df5 100644
--- a/arkindex/project/mixins.py
+++ b/arkindex/project/mixins.py
@@ -1,7 +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.query_utils import DeferredAttribute
+from django.db.models import Q
 from django.shortcuts import get_object_or_404
 from django.views.decorators.cache import cache_page
 from rest_framework.exceptions import APIException, ValidationError
@@ -14,89 +13,29 @@ from arkindex.project.elastic import ESQuerySet
 from arkindex.project.openapi import AutoSchema, SearchAutoSchema
 from arkindex.project.pagination import CustomCursorPagination
 from arkindex.users.models import Role
+from arkindex.users.utils import check_level_param, filter_rights
 
 
 class ACLMixin(object):
     """
     Access control mixin using the generic Right table.
     """
-    _user = None
-    mixin_order_by_fields = ()
-
-    def __init__(self, user=None, order_by_fields=()):
+    def __init__(self, user=None):
         self._user = user
-        self.mixin_order_by_fields = order_by_fields
 
     @property
     def user(self):
         return self._user or self.request.user
 
-    def _check_level(self, level):
-        assert type(level) is int, 'An integer level is required to compare access rights.'
-        assert level >= 1, 'Level integer should be greater than or equal to 1.'
-        assert level <= 100, 'level integer should be lower than or equal to 100'
-
-    def _has_public_field(self, model):
-        return type(getattr(model, 'public', None)) is DeferredAttribute
-
-    def get_public_instances(self, model, default_level):
-        return model.objects \
-            .filter(public=True) \
-            .annotate(max_level=Value(default_level, IntegerField()))
-
-    def rights_filter(self, model, level, public=False):
-        """
-        Return a model queryset matching a given access level for this user.
-        """
-        self._check_level(level)
-        include_public = level <= Role.Guest.value and self._has_public_field(model)
-
-        # Handle special authentications
-        if self.user.is_anonymous:
-            # Anonymous users have Guest access on public instances only
-            if not include_public:
-                return model.objects.none()
-            return self.get_public_instances(model, Role.Guest.value) \
-                .order_by(*self.mixin_order_by_fields, 'id')
-        elif self.user.is_admin or self.user.is_internal:
-            # Superusers have an Admin access to all corpora
-            return model.objects.all() \
-                .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'
-                )
-            )
-
-        # 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
-        if include_public:
-            queryset = queryset.union(self.get_public_instances(model, Role.Guest.value))
-
-        # Return distinct corpus with the max right level among matching rights
-        return queryset.order_by(*self.mixin_order_by_fields, 'id')
-
     def has_access(self, instance, level):
-        self._check_level(level)
+        check_level_param(level)
 
+        # Handle special authentications
+        if level <= Role.Guest.value and getattr(instance, 'public', False):
+            return True
         if self.user.is_admin or self.user.is_internal:
             return True
+
         return instance.memberships.filter(
             Q(
                 # Right direcly owned by this user
@@ -116,11 +55,15 @@ class RepositoryACLMixin(ACLMixin):
 
     @property
     def readable_repositories(self):
-        return self.rights_filter(Repository, Role.Guest.value)
+        return Repository.objects.filter(
+            id__in=filter_rights(self.user, Repository, Role.Guest.value).values('id')
+        )
 
     @property
     def executable_repositories(self):
-        return self.rights_filter(Repository, Role.Contributor.value)
+        return Repository.objects.filter(
+            id__in=filter_rights(self.user, Repository, Role.Contributor.value).values('id')
+        )
 
     def has_read_access(self, repo):
         return self.has_access(repo, Role.Guest.value)
@@ -136,11 +79,15 @@ class NewCorpusACLMixin(ACLMixin):
 
     @property
     def readable_corpora(self):
-        return self.rights_filter(Corpus, Role.Guest.value)
+        return Corpus.objects.filter(
+            id__in=filter_rights(self.user, Corpus, Role.Guest.value).values('id')
+        )
 
     @property
     def writable_corpora(self):
-        return self.rights_filter(Corpus, Role.Contributor.value)
+        return Corpus.objects.filter(
+            id__in=filter_rights(self.user, Corpus, Role.Contributor.value).values('id')
+        )
 
     @property
     def administrable_corpora(self):
diff --git a/arkindex/users/utils.py b/arkindex/users/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..c40b4cb4a3b7387f6bfec523f41f9877252fb08f
--- /dev/null
+++ b/arkindex/users/utils.py
@@ -0,0 +1,66 @@
+from django.db.models import IntegerField, Q, Value, functions
+from django.db.models.query_utils import DeferredAttribute
+
+from arkindex.users.models import Role
+
+PUBLIC_LEVEL = Role.Guest.value
+
+
+def has_public_field(model):
+    return type(getattr(model, 'public', None)) is DeferredAttribute
+
+
+def get_public_instances(model):
+    return model.objects \
+        .filter(public=True) \
+        .annotate(max_level=Value(PUBLIC_LEVEL, IntegerField()))
+
+
+def check_level_param(level):
+    assert type(level) is int, 'An integer level is required to compare access rights.'
+    assert level >= 1, 'Level integer should be greater than or equal to 1.'
+    assert level <= 100, 'level integer should be lower than or equal to 100'
+
+
+def filter_rights(user, model, level):
+    """
+    Return a generic queryset of objects with access rights for this user.
+    Level filtering parameter should be an integer between 1 and 100.
+    """
+    check_level_param(level)
+
+    public = level <= Role.Guest.value and has_public_field(model)
+
+    # Handle special authentications
+    if user.is_anonymous:
+        # Anonymous users have Guest access on public instances only
+        if not public:
+            return model.objects.none()
+        return get_public_instances(model)
+    elif user.is_admin or user.is_internal:
+        # Superusers have an Admin access to all corpora
+        return model.objects.all() \
+            .annotate(max_level=Value(Role.Admin.value, IntegerField()))
+
+    # Filter users rights and annotate the resulting level for those rights
+    queryset = model.objects \
+        .filter(
+            # Filter instances with rights concerning this user
+            Q(memberships__user=user)
+            | Q(memberships__group__memberships__user=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'
+            )
+        ) \
+        .filter(max_level__gte=level)
+
+    # Use a join to add public instances as this is the more elegant solution
+    if public:
+        queryset = queryset.union(get_public_instances(model))
+
+    return queryset