Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

prefetch_related for Authenticated user

Tags:

python

django

I'm working on a django web application and i'm in the process of minimizing the amount of individual database hits by using the prefetch_related and select_related methods, i have a certain method in my User model that pulls a couple of different related objects from it.

def get_profile_info(self):
    *fetch data from a couple of models*

And then i use this method in my view.

def profile(request):
    profile_info = request.user.get_profile_info()
    *rest of the view*

The problem is, since request.user is not retrieved by the normal query methods, i don't get to use the prefetch_related and select_related along with pulling the user, and i can't find any way to retrieve the related data along with the model of that user.

Is there a way to, say, override the retrieving of the user model so i can run the prefetch_related and select_related methods?

like image 733
YoungVenus Avatar asked Oct 20 '17 17:10

YoungVenus


People also ask

What is the difference between Prefetch_related and Select_related?

One uses select_related when the object that you're going to be selecting is a single object, so OneToOneField or a ForeignKey. You use prefetch_related when you're going to get a “set” of things, so ManyToManyFields as you stated or reverse ForeignKeys.

How does prefetch related work?

prefetch_related does a separate lookup for each relationship, and performs the “joining” in Python. It is different from select_related , the prefetch_related made the JOIN using Python rather than in the database. Let's dig into it with an example. We have 10 stores in the database and each store has 10 books.


2 Answers

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.

like image 92
Andrii Matiiash Avatar answered Sep 29 '22 20:09

Andrii Matiiash


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']
...
like image 39
aakok Avatar answered Sep 29 '22 22:09

aakok