view content/Coding/030-retiring-get-profile-and-auth-profile-module.rst @ 16:4280c90d5030

New Django related post about migrating away from get_profile().
author Brian Neal <bgneal@gmail.com>
date Sat, 24 May 2014 15:33:04 -0500
parents
children fa54eda9b809
line wrap: on
line source
Retiring get_profile and AUTH_PROFILE_MODULE
############################################

:date: 2014-05-24 14:08
:tags: django
:slug: retiring-get-profile-and-auth-profile-module
:author: Brian Neal
:summary: Here is how I prepared for the upcoming deprecation in Django 1.7 of
          the get_profile() and AUTH_PROFILE_MODULE functionality.

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.

.. sourcecode:: python

   # 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:

.. sourcecode:: python

   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:

.. sourcecode:: python

   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:

.. sourcecode:: python

   profile = request.user.get_profile()

After:

.. sourcecode:: python

   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.


.. _deprecations: https://docs.djangoproject.com/en/1.6/releases/1.5/#auth-profile-module
.. _AUTH_PROFILE_MODULE: https://docs.djangoproject.com/en/1.6/ref/settings/#auth-profile-module
.. _get_profile(): https://docs.djangoproject.com/en/1.6/ref/contrib/auth/#django.contrib.auth.models.User.get_profile
.. _configurable user model: https://docs.djangoproject.com/en/1.6/releases/1.5/#configurable-user-model
.. _ForeignKey: https://docs.djangoproject.com/en/1.6/ref/models/fields/#foreignkey
.. _OneToOneField: https://docs.djangoproject.com/en/1.6/ref/models/fields/#onetoonefield
.. _select_related(): https://docs.djangoproject.com/en/1.6/ref/models/querysets/#select-related
.. _prefetch_related(): https://docs.djangoproject.com/en/1.6/ref/models/querysets/#prefetch-related
.. _Django Debug Toolbar: https://github.com/django-debug-toolbar/django-debug-toolbar