prefetch_related for Authenticated user

Sorry for this necroposting, but this theme is so important and really simple answer exists, just create a custom manager for your user model and override the get method with select_related like this:

from django.contrib.auth.models import AbstractUser, UserManager


class CustomUserManager(UserManager):
    def get(self, *args, **kwargs):
        return super().select_related('<put fields that you want>').get(*args, **kwargs)


class CustomUser(AbstractUser):
    ...

    objects = CustomUserManager()

Now, whenever Django will retrieve user instance for request.user, it will be using this manager. Also all your CustomUser.objects.get() queries will select specified related fields too.


A more granual way could be using a custom authentication backend. By using this approach one will be able to use UserModel.objects.get without the drawback of making unnecessary joins(.select_related()) or DB lookups(.prefetch_related()) while using this manager in other segments of code.

# auth_backends.py
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend


UserModel = get_user_model()


class RelatedModelBackend(ModelBackend):
    def authenticate(self, request, username=None, password=None, **kwargs):
        if username is None:
            username = kwargs.get(UserModel.USERNAME_FIELD)
        if username is None or password is None:
            return
        try:
            user = UserModel._default_manager.select_related(
                ...  # Do your magic here
            ).prefetch_related(
                ...  # Do your magic here
            )
            get(
                **{UserModel.USERNAME_FIELD: username}
            )
        except UserModel.DoesNotExist:
            # Run the default password hasher once to reduce the timing
            # difference between an existing and a nonexistent user (#20760).
            UserModel().set_password(password)
        else:
            if user.check_password(password) and self.user_can_authenticate(user):
                return user

    def get_user(self, user_id):
        try:
            user = UserModel._default_manager.select_related(
                ...  # Do your magic here
            ).prefetch_related(
                ...  # Do your magic here
            ).get(pk=user_id)
        except UserModel.DoesNotExist:
            return None
        return user if self.user_can_authenticate(user) else None

Now we need to add new backend into settings file, read more about custom auth backends here.

# settings.py
...
AUTHENTICATION_BACKENDS = ['myproject.auth_backends.RelatedModelBackend']
...

Tags:

Python

Django