Source code for caps.views.mixins

from functools import cached_property

from django.db.models import Q
from django.core.exceptions import PermissionDenied
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.shortcuts import get_object_or_404

from .. import permissions
from ..models import Agent, AccessQuerySet


__all__ = (
    "OwnedMixin",
    "OwnedPermissionMixin",
    "SingleOwnedMixin",
    "ByUUIDMixin",
    "AgentMixin",
    "AccessMixin",
)


class UserAgentMixin:
    """
    This mixin provides two cached properties providing user's agents.
    """

    @cached_property
    def agent(self) -> Agent:
        """Return user's agent."""
        return self.agents and self.agents[0]

    @cached_property
    def agents(self) -> list[Agent]:
        """
        Current user's agents (of his groups included).
        User's agent is the first of the list.
        """
        return Agent.objects.user(self.request.user).order_by("-user")


[docs] class OwnedMixin(UserAgentMixin): """ Base mixin providing functionalities to work with :py:class:`~caps.models.object.Owned` model. It provides: - assign self's :py:attr:`agent` and :py:attr:`agents` - queryset to available :py:class:`~caps.models.access.Access`. """ access_class = None """ Access class (defaults to model's Access). """
[docs] def get_access_queryset(self) -> AccessQuerySet | None: """Return queryset for accesses.""" query = None if model := getattr(self, "model", None): query = model.Access.objects.all() else: query = getattr(self, "queryset", None) if query is not None: query = query.model.Access.objects.all() if query is not None: return query.select_related("receiver") return None
[docs] def get_queryset(self): """Get Owned queryset based get_access_queryset.""" accesses = self.get_access_queryset() return super().get_queryset().available(self.agents, accesses)
# This class code is mostly taken from Django Rest Framework's permissions.DjangoModelPermissions # Its code falls under the same license.
[docs] class OwnedPermissionMixin(OwnedMixin): """ This mixin checks for object permission when ``get_object()`` is called. It raises a ``PermissionDenied`` or ``Http404`` if user does not have access to the object. """ permissions = [permissions.OwnedPermissions]
[docs] def get_object(self): obj = super().get_object() self.check_object_permissions(obj) return obj
[docs] def check_object_permissions(self, request, obj): if perms := self.get_permissions(): allowed = any(p.has_object_permission(request, self, obj) for p in perms) if not allowed: raise PermissionDenied(f"Permission not allowed for {self.request.method} on this object.")
[docs] def get_permissions(self): return [p() for p in self.permissions]
[docs] class SingleOwnedMixin(OwnedMixin): """Detail mixin used to retrieve Owned detail. It requires subclass to have a ``check_object_permissions`` method ( eg by a child of :py:class:`OwnedPermissionMixin` or DRF APIView). """ lookup_url_kwarg = "uuid" """ URL's kwargs argument used to retrieve access uuid. """
[docs] def get_access_queryset(self): """When ``uuid`` GET argument is provided, filter accesses on it.""" query = super().get_access_queryset() if query is not None: if uuid := self.kwargs.get(self.lookup_url_kwarg): return query.filter(uuid=uuid) return query
[docs] def get_object(self): uuid = self.kwargs[self.lookup_url_kwarg] q = Q(uuid=uuid, owner__in=self.agents) if accesses := self.get_access_queryset(): q |= Q(accesses__in=accesses) obj = get_object_or_404(self.get_queryset(), q) self.check_object_permissions(self.request, obj) return obj
# ---- Other mixins
[docs] class ByUUIDMixin: """Fetch a model by UUID.""" lookup_url_kwarg = "uuid" """ URL's kwargs argument used to retrieve access uuid. """
[docs] def get_object(self): return get_object_or_404(self.get_queryset(), uuid=self.kwargs[self.lookup_url_kwarg])
[docs] class AgentMixin(ByUUIDMixin, UserAgentMixin, PermissionRequiredMixin): model = Agent
[docs] class AccessMixin(ByUUIDMixin, UserAgentMixin): """Mixin used by Access views and viewsets."""
[docs] def get_queryset(self): # FIXME: owner shall be able to remove any access # a user can view/delete only access for which he is # either receiver or emitter. return super().get_queryset().agent(self.agents)