Home > Enterprise >  Django get_queryset has different signature for Admin and Generic views, how to prevent code duplica
Django get_queryset has different signature for Admin and Generic views, how to prevent code duplica

Time:11-27

I would like to write simple code to filter records in view based on request information (eg. organization the user belongs to).

I started to implemented it as Mixin for Admin views.

class OrganizationPermissionMixin:

    def get_queryset(self, request):
        query = super().get_queryset(request)
        if request.user.is_superuser:
            return query
        return query.filter(
            organization__in=request.user.organization_set.all()
        )

This works fine but when I tried to apply this Mixin on Generic views, I have a signature error as there is no request parameter passed to the get_queryset method:

TypeError: OrganizationPermissionMixin.get_queryset() missing 1 required positional argument: 'request'

If I adapt the Mixin to:

class OrganizationPermissionMixin:

    def get_queryset(self):
        query = super().get_queryset()
        if self.request.user.is_superuser:
            return query
        return query.filter(
            organization__in=self.request.user.organization_set.all()
        )

It works for generic views such as ListView but now it indeed breaks for ModelAdmin view:

OrganizationPermissionMixin.get_queryset() takes 1 positional argument but 2 were given

This inconsistency in signature is somehow very frustrating because it requires to duplicate code simply because request passing mechanism is different between Generic and Admin views.

My question is: how can I make this Mixin works both for Generic and Admin views. Is there something ready for that in Django? Is it normal it behaves like this or is it an unfortunate design choice?

CodePudding user response:

The request is not passed as a queryset in a class-based view. It is however in a ModelAdmin, you can harmonize the two with:

class OrganizationPermissionMixin:
    def get_queryset(self, *args, **kwargs):
        request = args[0] if args else kwargs.get('request') or self.request
        query = super().get_queryset(*args, **kwargs)
        if request.user.is_superuser:
            return query
        return query.filter(organization__user=request.user)

It will thus first try to obtain the request from the kwargs. If that returns None, it will "fallback" on self.request, which is the case for a class-based view.

The modeling of the ModelAdmin is quite bad. For example it reuses the same object, and therefore has no request encoded in the object. It also groups all sorts of views together. There have been proposals to redesign the modeladmin, but unfortunately, these are currently not implemented due to backwards compatibility.

CodePudding user response:

Maybe sth like this:

...
def get_queryset(self, *args, **kwargs):
    request = kwargs.get("request", None)
    if request:
        query = super().get_queryset(request)
    else:
        query = super().get_queryset()
    ...
  • Related