[Fixed]-Prefetch_related for Authenticated user

0πŸ‘

βœ…

You could always refetch the user from the database using select_related and prefetch_related. If select_related and prefetch_related save a lot of queries, then it will be worth the extra query to fetch the user.

def profile(request):
    user = User.objects.select_related(
        ...
    ).prefetch_related(
        ...
    ).get(pk=request.user.pk)

Note that depending on the view, prefetch_related might not be very useful in this case. It causes one extra query per model, so it’s most useful when you are fetching the related objects for an entire queryset, not a single object.

πŸ‘€Alasdair

27πŸ‘

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.

2πŸ‘

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']
...
πŸ‘€aakok

Leave a comment