How to update Django user profile from ForeignKey to OneToOneField

In one of my projects for the user profile model was used ForeignKey field.

class UserProfile(models.Model):
    user = models.ForeignKey(User, unique=True)

This leads to a lot of unnecessary queries when you want to access to related data, like:

{% raw %}
{% for item in items %}
    {{ item.user.get_profile.company }}
{% endfor %}
{% endraw %}

But as the unique=True, this means that for each user, there is only one profile. For other cases, it is possible to check this with following query:

from django.db.models import Count
UserProfile.objects.annotate(user_count=Count('user')).filter(user_count__gt=1)

In this situation it is better to replace the ForeignKey with OneToOneField.

user = models.OneToOneField(User, related_name='userprofile', primary_key=True, db_column='user_id')
db_column='user_id' #because it already has the required values
related_name='userprofile' #because there may be requests like User.objects.filter(userprofile__company=...)

Then you need to update the database, I use the south, so:

mysqldump .... #for backup
python manage.py schemamigration app --auto
python manage.py migrate app

You can then make another migration to rename user_id to id:

user = models.OneToOneField(User, related_name='userprofile', primary_key=True, db_column='id')

Then in the templates user.get_profile must be replaced with user.userprofile and user.userprofile.id with user.id. And in the code user.get_profile() with user.userprofile.

After that, you can significantly reduce the number of queries using the necessary select_related, like

Item.objects.select_related('user__userprofile__company').all()
comments powered by Disqus