django-guardian
django-guardian copied to clipboard
Improve performance of shortcuts
Really like django guardian.
I noticed that the shortcuts use PK lists, which tend to be relatively slow for big groups. Can this be improved using a subquery?
Is this project still alive?
Example for improvement in guardian shortcuts for direct model:
if not any_perm and len(codenames): objects = queryset.filter(pk__in=Subquery(groups_obj_perms_queryset.values(fields[0]))) return objects
@ericmuijs Yeah, there are some performance pitfalls specially in the shortcuts, You can send a PR imporving that (Supprots are very wellcome for the project #603)
Ended up writing a direct model only shortcut for another project. This may be useful for some people and maybe a PR can come out of it. Though I wasn't able to update the core library. Should result in a single query
from functools import reduce
from operator import and_, or_
from typing import List, TypeVar, Union, cast
from django.contrib.auth.models import AbstractBaseUser, AnonymousUser
from django.db.models import Exists, Model, OuterRef, Q, QuerySet
from guardian.utils import get_group_obj_perms_model, get_user_obj_perms_model
T = TypeVar("T", bound=Model)
def get_objects_for_user(
user: Union[AbstractBaseUser, AnonymousUser],
perms: List[str],
klass: QuerySet[T],
any_perm: bool = False,
) -> QuerySet[T]:
"""
Fetches a queryset of objects for which the user has specified permissions.
Acts as a replacement for Django Guardian's `get_objects_for_user`, aiming
for flexible and efficient permission checks using Django's ORM.
Args:
user: User for whom to retrieve objects.
perms: Permission strings to check.
klass: Initial queryset of model objects.
any_perm: If True, returns objects for any permissions. Else, all.
Returns:
A queryset of objects with the specified permissions for the user.
Note:
- Dynamically builds queries for user/group permissions.
- Requires `klass` as a correct model type queryset and `perms` to be
model-appropriate permission codenames.
- Custom `UserObjectPermission` and `GroupObjectPermission` models
associate permissions with model instances, enabling granular access
control.
"""
if not user.is_authenticated or not perms:
return klass.none()
user_permissions_field = get_user_obj_perms_model(
klass.model
).permission.field.related_query_name()
group_permissions_field = get_group_obj_perms_model(
klass.model
).permission.field.related_query_name()
qs = klass
permission_filters = []
for perm in perms:
perm_codename = perm.split(".")[-1]
user_perm_query = Q(
**{
f"{user_permissions_field}__permission__codename": perm_codename,
f"{user_permissions_field}__user": user,
}
)
group_perm_query = Q(
**{
f"{group_permissions_field}__permission__codename": perm_codename,
f"{group_permissions_field}__group__user": user,
}
)
permission_filters.append(
Exists(klass.filter(user_perm_query | group_perm_query, pk=OuterRef("pk")))
)
if any_perm:
combined_condition = reduce(or_, permission_filters)
else:
combined_condition = reduce(and_, permission_filters)
return cast(
QuerySet[T],
qs.annotate(has_permission=combined_condition).filter(has_permission=True),
)