diff --git a/arkindex/project/mixins.py b/arkindex/project/mixins.py index 94a41281c1dd218024ce481c87339cad6103524c..84c3562bc2cedcf9ae8c4958b6877fd5bc184724 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 Q, functions +from django.db.models import IntegerField, Q, Value, functions from django.shortcuts import get_object_or_404 from django.views.decorators.cache import cache_page from rest_framework.exceptions import APIException, ValidationError @@ -28,43 +28,48 @@ class ACLMixin(object): def user(self): return self._user or self.request.user + 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 + Return a model queryset matching a given access level for this user. """ # Handle specific cases (i.e. admin or anonymous user) if self.user.is_admin or self.user.is_internal: - return model.objects.all() - elif user.is_anonymous: - if level > Role.Guest.value or not public: + return model.objects.all().annotate(max_level=Value(Role.Admin.value)) + elif self.user.is_anonymous: + if not public: return model.objects.none() else: - return model.objects.filter(public=True) + return self.get_public_instances(model, Role.Guest.value) + # Filter users rights and annotate the resulting level for those rights queryset = model.objects \ .filter( - # Filter instances with direct and groups rights for this user (They may be duplicated) + # 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( - # In case of direct right, group level will be skipped (Null value) 'memberships__level', + # In case of direct right, the group level will be skipped (Null value) 'memberships__group__memberships__level' ) - ) \ - .filter( - # Ensure one of the right has an adequate level - max_level__gte=level ) + # Ensure one of the right has an adequate level + queryset = queryset.filter(max_level__gte=level) - if (public): - # Allow access to public instances if the public parameter is set - queryset = queryset | model.objects.filter(public=True) + # Use a join to add public instances as this is the more elegant solution + if public and level <= Role.Guest.value: + queryset = queryset.union(self.get_public_instances(model, Role.Guest.value)) - return queryset.distinct() + # Return distinct corpus with the max right level among matching rights + return queryset.order_by('id', '-max_level').distinct('id') def has_access(self, instance, level): if self.user.is_admin or self.user.is_internal: @@ -86,11 +91,13 @@ class ACLMixin(object): class RepositoryACLMixin(ACLMixin): + @property def readable_repositories(self): - return self.rights_filter(Repository, Role.Guest.value, public=False) + return self.rights_filter(Repository, Role.Guest.value) + @property def executable_repositories(self): - return self.rights_filter(Repository, Role.Contributor.value, public=False) + return self.rights_filter(Repository, Role.Contributor.value) def has_read_access(self, repo): return self.has_access(repo, Role.Guest.value) @@ -103,12 +110,16 @@ class RepositoryACLMixin(ACLMixin): class NewCorpusACLMixin(ACLMixin): + + @property def readable_corpora(self): return self.rights_filter(Corpus, Role.Guest.value, public=True) + @property def writable_corpora(self): return self.rights_filter(Corpus, Role.Contributor.value) + @property def administrable_corpora(self): return self.rights_filter(Corpus, Role.Admin.value)