Date Tags django

Django 1.7 is coming soon! This looks like an exciting release with cool features like migrations and an app loading framework. However it also comes with many deprecations that we need to prepare for. In particular, now is the time to finally move away from the AUTH_PROFILE_MODULE setting and the get_profile() method on the User model. I had been putting this off until the last minute, but after finally sitting down and doing it, I am happy to report it isn't difficult.

My initial fears about removing this feature were unfounded. Originally I thought perhaps database schema changes were needed. And without migrations support I would be doomed. I was also confusing this deprecation with the introduction of the new configurable user model from Django 1.5.

It turns out that no database changes are needed to migrate away from get_profile(). You simply replace your profile model's ForeignKey with a OneToOneField. This does not change the database schema at all. You can observe this yourself by running the manage.py sql command before and after making the change. You then need to fix up your code by removing all get_profile() calls.

Django 1.5 introduced the ability to define your own user model, but you do not need to jump to this to move away from get_profile(). That is a separate decision to make. You may in fact wish to abandon a separate user profile model and create your own combined user model. But this can be done after following the steps I outline below.

In my case, I decided it makes more sense to maintain a separate user profile model. It did not feel right to add non-authentication related fields into the user model. It is true that you will incur the overhead of database joins with a separate profile model. However, in practice, at least for me, this was not particularly burdensome. Most of the time this meant adding an additional select_related() call on an ORM query. Your mileage may vary of course. But if you are happy with the additional profile model solution you need only follow these steps and be done.

Here is an outline of the steps I followed to remove the get_profile() functionality from my code base.

Remove the AUTH_PROFILE_MODULE setting

This step is easy. Find your AUTH_PROFILE_MODULE setting and delete it.

# In your settings.py:
AUTH_PROFILE_MODULE = 'myapp.profile'     # delete this line

Change your profile model

In this step we change the ForeignKey to Django's User model to a OneToOneField. Again, this does not affect the database schema.

Before:

from django.db import models
from django.contrib.auth.models import User

class Profile(models.Model):
   """model to represent additional information about users"""
   user = models.ForeignKey(User, unique=True)  # change this line
   # ... other custom stuff here

After:

from django.db import models
from django.contrib.auth.models import User

class Profile(models.Model):
   """model to represent additional information about users"""
   user = models.OneToOneField(User)
   # ... other custom stuff here

Update the code

You now need to go through your code and find all the get_profile() calls and replace them. For example:

Before:

profile = request.user.get_profile()

After:

profile = request.user.profile

After making all these changes you are basically done! Congratulations!

Cleanup and sanity checking

There are a few other things I recommend doing at this point. First I would search your code for all occurrences of your Profile model. There were several places in my code where the extra database calls caused by get_profile() were hurting my performance. I had replaced get_profile() with custom code that queried the Profile manager directly to reduce database calls. I reviewed these changes and replaced them with select_related() calls. This simplified things. I love deleting lines of code. While I was doing this, I noticed a few places that I could use prefetch_related() (on other related fields) since my code pre-dated this method.

Next you should run any unit tests and verify they still pass. Hopefully you have unit tests, right? It was a big relief to have my tests passing after making these numerous changes.

Finally I would suggest using a profiler like the Django Debug Toolbar to do some spot checking on the number of SQL queries your pages generate after making these changes. In most cases I was able to reduce the number of calls or at least keep them the same. Using the magic of version control, I ran a version of my site both with and without my changes side-by-side. Using the profiler I convinced myself I was in fact making things better and not worse.

Conclusion

I am not familiar with early Django history, but I'm guessing that get_profile() must have pre-dated the OneToOneField. After making the switch, my code seems more straightforward with less "magic". In any event, I hope this post will make you less fearful about this change. It was not the major change I had thought it was going to be.


Comments

comments powered by Disqus