diff --git a/core/auth/backends.py b/core/auth/backends.py index 7d6fd76c..fced226a 100644 --- a/core/auth/backends.py +++ b/core/auth/backends.py @@ -1,15 +1,16 @@ from __future__ import annotations -from typing import TYPE_CHECKING +import typing from django.conf import settings from django.contrib.auth.backends import ModelBackend from django.contrib.auth.models import Permission +from django.db.models import Exists, OuterRef, Q, QuerySet -from core.models import Group +from core.models import Group, User -if TYPE_CHECKING: - from core.models import User +if typing.TYPE_CHECKING: + from django.db.models.base import Model class SithModelBackend(ModelBackend): @@ -40,3 +41,53 @@ class SithModelBackend(ModelBackend): return Permission.objects.filter( group__group__in=groups.values_list("pk", flat=True) ) + + @typing.override + def with_perm( + self, + perm: str | Permission, + is_active: bool | None = True, + include_superusers: bool = False, + obj: Model | None = None, + ) -> QuerySet[User]: + """Return users that have permission "perm". + + Contrary to the base django method, superusers aren't included in the + result. + This is because the OR operation to include superusers in the query result + utterly destroy the query performances on postgres + (it makes it like 1000x slower, and I'm not even kidding). + To overcome that, we could use a UNION instead, but then we wouldn't + be able to perform further filter operations on the queryset. + Thus, the `include_superusers` argument is not used at all. + + Because of that, it is useless to set `include_superusers`, + as it will be silently ignored. + The only reason it's still there is not to break the interface + of the base class. + """ + if isinstance(perm, str): + try: + app_label, codename = perm.split(".") + except ValueError as e: + raise ValueError( + "Permission name should be in the form " + "app_label.permission_codename." + ) from e + permission_q = Q(codename=codename, content_type__app_label=app_label) + elif isinstance(perm, Permission): + permission_q = Q(pk=perm.pk) + else: + raise TypeError( + "The `perm` argument must be a string or a permission instance." + ) + + user_q = Exists( + Permission.objects.filter( + Q(group__group__users=OuterRef("pk")) | Q(user=OuterRef("pk")), + permission_q, + ) + ) + if is_active is not None: + user_q &= Q(is_active=is_active) + return User.objects.filter(user_q)