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?
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.
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.
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']
...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With