bgneal@16: Retiring get_profile and AUTH_PROFILE_MODULE bgneal@16: ############################################ bgneal@16: bgneal@16: :date: 2014-05-24 14:08 bgneal@16: :tags: django bgneal@16: :slug: retiring-get-profile-and-auth-profile-module bgneal@16: :author: Brian Neal bgneal@16: :summary: Here is how I prepared for the upcoming deprecation in Django 1.7 of bgneal@16: the get_profile() and AUTH_PROFILE_MODULE functionality. bgneal@16: bgneal@16: Django 1.7 is coming soon! This looks like an exciting release with cool bgneal@16: features like migrations and an app loading framework. However it also comes bgneal@16: with many deprecations_ that we need to prepare for. In particular, now is the bgneal@16: time to finally move away from the AUTH_PROFILE_MODULE_ setting and the bgneal@16: `get_profile()`_ method on the ``User`` model. I had been putting this off bgneal@16: until the last minute, but after finally sitting down and doing it, I am happy bgneal@16: to report it isn't difficult. bgneal@16: bgneal@16: My initial fears about removing this feature were unfounded. Originally bgneal@16: I thought perhaps database schema changes were needed. And without migrations bgneal@16: support I would be doomed. I was also confusing this deprecation with the bgneal@16: introduction of the new `configurable user model`_ from Django 1.5. bgneal@16: bgneal@16: It turns out that no database changes are needed to migrate away from bgneal@16: ``get_profile()``. You simply replace your profile model's ForeignKey_ with bgneal@16: a OneToOneField_. This does not change the database schema at all. You can bgneal@16: observe this yourself by running the ``manage.py sql`` command before and after bgneal@16: making the change. You then need to fix up your code by removing all bgneal@16: ``get_profile()`` calls. bgneal@16: bgneal@16: Django 1.5 introduced the ability to define your own user model, but you do not bgneal@16: need to jump to this to move away from ``get_profile()``. That is a separate bgneal@16: decision to make. You may in fact wish to abandon a separate user profile model bgneal@16: and create your own combined user model. But this can be done after following bgneal@16: the steps I outline below. bgneal@16: bgneal@16: In my case, I decided it makes more sense to maintain a separate user profile bgneal@16: model. It did not feel right to add non-authentication related fields into the bgneal@16: user model. It is true that you will incur the overhead of database joins with bgneal@16: a separate profile model. However, in practice, at least for me, this was not bgneal@16: particularly burdensome. Most of the time this meant adding an additional bgneal@16: `select_related()`_ call on an ORM query. Your mileage may vary of course. But bgneal@16: if you are happy with the additional profile model solution you need only bgneal@16: follow these steps and be done. bgneal@16: bgneal@16: Here is an outline of the steps I followed to remove the ``get_profile()`` bgneal@16: functionality from my code base. bgneal@16: bgneal@16: Remove the AUTH_PROFILE_MODULE setting bgneal@16: -------------------------------------- bgneal@16: bgneal@16: This step is easy. Find your ``AUTH_PROFILE_MODULE`` setting and delete it. bgneal@16: bgneal@16: .. sourcecode:: python bgneal@16: bgneal@16: # In your settings.py: bgneal@16: AUTH_PROFILE_MODULE = 'myapp.profile' # delete this line bgneal@16: bgneal@16: bgneal@16: Change your profile model bgneal@16: ------------------------- bgneal@16: bgneal@16: In this step we change the ``ForeignKey`` to Django's ``User`` model to bgneal@16: a ``OneToOneField``. Again, this does not affect the database schema. bgneal@16: bgneal@16: Before: bgneal@16: bgneal@16: .. sourcecode:: python bgneal@16: bgneal@16: from django.db import models bgneal@16: from django.contrib.auth.models import User bgneal@16: bgneal@16: class Profile(models.Model): bgneal@16: """model to represent additional information about users""" bgneal@16: user = models.ForeignKey(User, unique=True) # change this line bgneal@16: # ... other custom stuff here bgneal@16: bgneal@16: After: bgneal@16: bgneal@16: .. sourcecode:: python bgneal@16: bgneal@16: from django.db import models bgneal@16: from django.contrib.auth.models import User bgneal@16: bgneal@16: class Profile(models.Model): bgneal@16: """model to represent additional information about users""" bgneal@21: user = models.OneToOneField(User, related_name='profile') bgneal@16: # ... other custom stuff here bgneal@16: bgneal@16: Update the code bgneal@16: --------------- bgneal@16: bgneal@16: You now need to go through your code and find all the ``get_profile()`` calls bgneal@16: and replace them. For example: bgneal@16: bgneal@16: Before: bgneal@16: bgneal@16: .. sourcecode:: python bgneal@16: bgneal@16: profile = request.user.get_profile() bgneal@16: bgneal@16: After: bgneal@16: bgneal@16: .. sourcecode:: python bgneal@16: bgneal@16: profile = request.user.profile bgneal@16: bgneal@16: After making all these changes you are basically done! Congratulations! bgneal@16: bgneal@16: Cleanup and sanity checking bgneal@16: --------------------------- bgneal@16: bgneal@16: There are a few other things I recommend doing at this point. First I would bgneal@16: search your code for all occurrences of your ``Profile`` model. There were bgneal@16: several places in my code where the extra database calls caused by bgneal@17: ``get_profile()`` were hurting my performance. I had replaced ``get_profile()`` bgneal@16: with custom code that queried the ``Profile`` manager directly to reduce bgneal@16: database calls. I reviewed these changes and replaced them with bgneal@16: ``select_related()`` calls. This simplified things. I love deleting lines of bgneal@16: code. While I was doing this, I noticed a few places that I could use bgneal@16: `prefetch_related()`_ (on other related fields) since my code pre-dated this bgneal@16: method. bgneal@16: bgneal@16: Next you should run any unit tests and verify they still pass. Hopefully you bgneal@16: have unit tests, right? It was a big relief to have my tests passing after bgneal@16: making these numerous changes. bgneal@16: bgneal@16: Finally I would suggest using a profiler like the `Django Debug Toolbar`_ to do bgneal@16: some spot checking on the number of SQL queries your pages generate after making bgneal@16: these changes. In most cases I was able to reduce the number of calls or at bgneal@16: least keep them the same. Using the magic of version control, I ran a version bgneal@16: of my site both with and without my changes side-by-side. Using the profiler bgneal@16: I convinced myself I was in fact making things better and not worse. bgneal@16: bgneal@16: Conclusion bgneal@16: ---------- bgneal@16: bgneal@16: I am not familiar with early Django history, but I'm guessing that bgneal@16: ``get_profile()`` must have pre-dated the OneToOneField_. After making the bgneal@16: switch, my code seems more straightforward with less "magic". In any event, bgneal@16: I hope this post will make you less fearful about this change. It was not the bgneal@16: major change I had thought it was going to be. bgneal@16: bgneal@16: bgneal@16: .. _deprecations: https://docs.djangoproject.com/en/1.6/releases/1.5/#auth-profile-module bgneal@16: .. _AUTH_PROFILE_MODULE: https://docs.djangoproject.com/en/1.6/ref/settings/#auth-profile-module bgneal@16: .. _get_profile(): https://docs.djangoproject.com/en/1.6/ref/contrib/auth/#django.contrib.auth.models.User.get_profile bgneal@16: .. _configurable user model: https://docs.djangoproject.com/en/1.6/releases/1.5/#configurable-user-model bgneal@16: .. _ForeignKey: https://docs.djangoproject.com/en/1.6/ref/models/fields/#foreignkey bgneal@16: .. _OneToOneField: https://docs.djangoproject.com/en/1.6/ref/models/fields/#onetoonefield bgneal@16: .. _select_related(): https://docs.djangoproject.com/en/1.6/ref/models/querysets/#select-related bgneal@16: .. _prefetch_related(): https://docs.djangoproject.com/en/1.6/ref/models/querysets/#prefetch-related bgneal@16: .. _Django Debug Toolbar: https://github.com/django-debug-toolbar/django-debug-toolbar