Source code for caps.models.capability_set

from __future__ import annotations
from collections.abc import Iterable
from typing import Any

from django.core.exceptions import PermissionDenied
from django.contrib.auth.models import Permission
from django.utils.translation import gettext as __

from .capability import Capability

__all__ = ("CapabilitySet",)


[docs] class CapabilitySet: """ Base class to handle set of capabilities. It is one of the bases of :py:class:`Share`. This class should not be used per se. """ Capability: type[Capability] | Permission """Capability class to use. It must be set in order to use CapabilitySet. """ Cap: int | tuple[int, int] """ Capability information, as tuple of ``(permission_id, max_derive)`` or single permission id (then ``max_derive is None``). """ Caps: Iterable[CapabilitySet.Cap] """ Many capability information. """ capabilities: Iterable[Capability] = None """ Capabilities contained in the CapabilitySet. """
[docs] def get_capabilities(self) -> Iterable[Capability]: return self.capabilities
# FIXME: remove?
[docs] def get_capability(self, codename: str) -> Capability | None: """Get capability by name or None.""" return next((r for r in self.get_capabilities() if r.codename == codename), None)
[docs] def is_derived(self, other: CapabilitySet) -> bool: """Return True if `capabilities` iterable is a subset of self. Set is a subset of another one if and only if: - all capabilities of subset are in set and derived from set \ (cf. `Capability.is_subset`) - there is no capability inside subset that are not in set. """ capabilities = {c.permission_id: c for c in self.get_capabilities()} for item in other.get_capabilities(): capability = capabilities.get(item.permission_id) if not capability or not capability.is_derived(item): return False return True
[docs] def create_capability(self, cap: CapabilitySet.Cap, **kwargs) -> CapabilitySet.Capability: """Create a single (unsaved) capability. :param cap: capability information :param **kwargs: extra initial arguments :return the new unsaved capability instance. """ perm_id, kwargs = self.get_capability_kwargs(cap, {**kwargs, "Share": self}) return self.Capability(permission_id=perm_id, **kwargs)
[docs] def create_capabilities(self, caps: CapabilitySet.Caps, **kwargs) -> list[CapabilitySet.Capability]: """Create multiple capabilities based on descriptors. :param caps: capabilities' informations :param **kwargs: extra initial arguments :return a list of unsaved capabilities instances. """ return [self.create_capability(cap, **kwargs) for cap in caps]
[docs] def derive_caps( self, caps: CapabilitySet.Caps | None = None, raises: bool = False, defaults: dict[str, Any] = {} ) -> list[Capability]: """Derive capabilities from this set. When `caps` is provided, it will only derive allowed derivations for declared capabilities. When `caps` is not provided, it derives all allowed capabilities of the set. The created capabilities are not saved to the db. :param source: capabilities to derive :param caps: specify which capabilities to derive :param raises: if True, raise exception when there are denied derivation. :param defaults: initial arguments to pass to all generated capabilities. :return the list of derived capabilities. :yield PermissionDenied: when there are unauthorized derivation and :py:param:`raises` is True. """ if caps is None: defaults = {"max_derive": 0, **defaults} return [c.derive(**defaults) for c in self.get_capabilities() if c.can_derive(defaults["max_derive"])] by_perm = {c.permission_id: c for c in self.get_capabilities()} derived, denied = [], [] for cap in caps: perm_id, kwargs = self.get_capability_kwargs(cap, defaults) capability = by_perm.get(perm_id) try: if capability is None: raise PermissionDenied("Capability does not exist for this Share.") # we always use Capability.derive in order to ensure custom # implementations. obj = capability.derive(**kwargs) derived.append(obj) except PermissionDenied: denied.append(str(cap)) if raises and denied: raise PermissionDenied( __("Some capabilities can not be derived: {denied}").format(denied=", ".join(denied)) ) return derived
[docs] def get_capability_kwargs(self, cap: CapabilitySet.Cap, defaults: dict[str, Any]) -> tuple[int, dict[str, Any]]: """ From provided ``cap`` description, return a tuple with permission id and :py:meth:`Capability.derive` arguments. We return a tuple because permission id can not be provided to the derive method. :param cap: the capability descriptor; :param **kwargs: extra arguments to pass down to the method; """ if isinstance(cap, tuple): perm_id, max_derive = cap elif isinstance(cap, int): perm_id, max_derive = cap, None else: raise ValueError(f"Invalid value for `cap`: {cap}") return perm_id, {"max_derive": max_derive, **defaults}