diff 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 diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/content/Coding/030-retiring-get-profile-and-auth-profile-module.rst	Sat May 24 15:33:04 2014 -0500
@@ -0,0 +1,153 @@
+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