Mercurial > public > pelican-blog
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