If you want to associate extra information with a user account in Django you need to create a separate "User Profile" model. To get the profile for a given User object one simply calls get_profile. This is a nice easy way of handling things and helps keep the User model in Django simple. However it does have a downside - fetching the user and user profile requires two database queries. That's not so bad when we're selecting just one user and profile, but when we are displaying a list of users we'd end up doing one query for the users and another n-queries for the profiles - the classic n+1 query problem!
To reduce the number of queries to just one we can use select_related. Prior to Django 1.2 select_related did not support the reverse direction of a OneToOneField, so we couldn't actually use it with the user and user profile models. Luckily that's no longer a problem.
If we are clever we can setup the user profile so it stills works with get_profile and does not create an extra query.
get_profile looks like this:
def get_profile(self): if not hasattr(self, '_profile_cache'): # ... # query to get profile and store it in _profile_cache on instance # so we don't need to look it up again later # ... return self._profile_cache
So if select_related stores the profile object in _profile_cache then get_profile will not need to do any more querying.
To do this we'd define the user profile model like this:
class UserProfile(models.Model): user = models.OneToOneField(User, related_name='_profile_cache') # ... # other model fields # ...
The key thing is that we have set related_name on the OneToOneField to be '_profile_cache'. The related_name defines the attribute on the *other* model (User in this case) and is what we need to refer to when we use select_related.
Querying for all user instances and user profiles at the same time would look like:
The only downside is that this does change the behaviour of get_profile slightly. Previously if no profile existed for a given user a DoesNotExist exception is raised. With this approach get_profile instead returns None. So you're code will need to handle this. On the upside repeated calls to get_profile, when no profile exists, won't re-query the database.
The other minor problem is that we are relying on the name of a private attribute on the User model (that little pesky underscore indicates it's not officially for public consumption). Theoretically the attribute name could change in a future version of Django. To mitigate against the name changing I'd personally just store the name in a variable or on the profile model as a class attribute and reference that whenever you need it, so at least there's only one place to make any change. Apart from that this only requires a minor modification to your user profile model and the use of select_related, so it's not a massively invasive optimisation.