annotate content/Coding/030-retiring-get-profile-and-auth-profile-module.rst @ 21:21a29f2520ca

Add related_name to UserProfile model example.
author Brian Neal <bgneal@gmail.com>
date Sat, 27 Aug 2016 13:18:54 -0500
parents fa54eda9b809
children
rev   line source
bgneal@16 1 Retiring get_profile and AUTH_PROFILE_MODULE
bgneal@16 2 ############################################
bgneal@16 3
bgneal@16 4 :date: 2014-05-24 14:08
bgneal@16 5 :tags: django
bgneal@16 6 :slug: retiring-get-profile-and-auth-profile-module
bgneal@16 7 :author: Brian Neal
bgneal@16 8 :summary: Here is how I prepared for the upcoming deprecation in Django 1.7 of
bgneal@16 9 the get_profile() and AUTH_PROFILE_MODULE functionality.
bgneal@16 10
bgneal@16 11 Django 1.7 is coming soon! This looks like an exciting release with cool
bgneal@16 12 features like migrations and an app loading framework. However it also comes
bgneal@16 13 with many deprecations_ that we need to prepare for. In particular, now is the
bgneal@16 14 time to finally move away from the AUTH_PROFILE_MODULE_ setting and the
bgneal@16 15 `get_profile()`_ method on the ``User`` model. I had been putting this off
bgneal@16 16 until the last minute, but after finally sitting down and doing it, I am happy
bgneal@16 17 to report it isn't difficult.
bgneal@16 18
bgneal@16 19 My initial fears about removing this feature were unfounded. Originally
bgneal@16 20 I thought perhaps database schema changes were needed. And without migrations
bgneal@16 21 support I would be doomed. I was also confusing this deprecation with the
bgneal@16 22 introduction of the new `configurable user model`_ from Django 1.5.
bgneal@16 23
bgneal@16 24 It turns out that no database changes are needed to migrate away from
bgneal@16 25 ``get_profile()``. You simply replace your profile model's ForeignKey_ with
bgneal@16 26 a OneToOneField_. This does not change the database schema at all. You can
bgneal@16 27 observe this yourself by running the ``manage.py sql`` command before and after
bgneal@16 28 making the change. You then need to fix up your code by removing all
bgneal@16 29 ``get_profile()`` calls.
bgneal@16 30
bgneal@16 31 Django 1.5 introduced the ability to define your own user model, but you do not
bgneal@16 32 need to jump to this to move away from ``get_profile()``. That is a separate
bgneal@16 33 decision to make. You may in fact wish to abandon a separate user profile model
bgneal@16 34 and create your own combined user model. But this can be done after following
bgneal@16 35 the steps I outline below.
bgneal@16 36
bgneal@16 37 In my case, I decided it makes more sense to maintain a separate user profile
bgneal@16 38 model. It did not feel right to add non-authentication related fields into the
bgneal@16 39 user model. It is true that you will incur the overhead of database joins with
bgneal@16 40 a separate profile model. However, in practice, at least for me, this was not
bgneal@16 41 particularly burdensome. Most of the time this meant adding an additional
bgneal@16 42 `select_related()`_ call on an ORM query. Your mileage may vary of course. But
bgneal@16 43 if you are happy with the additional profile model solution you need only
bgneal@16 44 follow these steps and be done.
bgneal@16 45
bgneal@16 46 Here is an outline of the steps I followed to remove the ``get_profile()``
bgneal@16 47 functionality from my code base.
bgneal@16 48
bgneal@16 49 Remove the AUTH_PROFILE_MODULE setting
bgneal@16 50 --------------------------------------
bgneal@16 51
bgneal@16 52 This step is easy. Find your ``AUTH_PROFILE_MODULE`` setting and delete it.
bgneal@16 53
bgneal@16 54 .. sourcecode:: python
bgneal@16 55
bgneal@16 56 # In your settings.py:
bgneal@16 57 AUTH_PROFILE_MODULE = 'myapp.profile' # delete this line
bgneal@16 58
bgneal@16 59
bgneal@16 60 Change your profile model
bgneal@16 61 -------------------------
bgneal@16 62
bgneal@16 63 In this step we change the ``ForeignKey`` to Django's ``User`` model to
bgneal@16 64 a ``OneToOneField``. Again, this does not affect the database schema.
bgneal@16 65
bgneal@16 66 Before:
bgneal@16 67
bgneal@16 68 .. sourcecode:: python
bgneal@16 69
bgneal@16 70 from django.db import models
bgneal@16 71 from django.contrib.auth.models import User
bgneal@16 72
bgneal@16 73 class Profile(models.Model):
bgneal@16 74 """model to represent additional information about users"""
bgneal@16 75 user = models.ForeignKey(User, unique=True) # change this line
bgneal@16 76 # ... other custom stuff here
bgneal@16 77
bgneal@16 78 After:
bgneal@16 79
bgneal@16 80 .. sourcecode:: python
bgneal@16 81
bgneal@16 82 from django.db import models
bgneal@16 83 from django.contrib.auth.models import User
bgneal@16 84
bgneal@16 85 class Profile(models.Model):
bgneal@16 86 """model to represent additional information about users"""
bgneal@21 87 user = models.OneToOneField(User, related_name='profile')
bgneal@16 88 # ... other custom stuff here
bgneal@16 89
bgneal@16 90 Update the code
bgneal@16 91 ---------------
bgneal@16 92
bgneal@16 93 You now need to go through your code and find all the ``get_profile()`` calls
bgneal@16 94 and replace them. For example:
bgneal@16 95
bgneal@16 96 Before:
bgneal@16 97
bgneal@16 98 .. sourcecode:: python
bgneal@16 99
bgneal@16 100 profile = request.user.get_profile()
bgneal@16 101
bgneal@16 102 After:
bgneal@16 103
bgneal@16 104 .. sourcecode:: python
bgneal@16 105
bgneal@16 106 profile = request.user.profile
bgneal@16 107
bgneal@16 108 After making all these changes you are basically done! Congratulations!
bgneal@16 109
bgneal@16 110 Cleanup and sanity checking
bgneal@16 111 ---------------------------
bgneal@16 112
bgneal@16 113 There are a few other things I recommend doing at this point. First I would
bgneal@16 114 search your code for all occurrences of your ``Profile`` model. There were
bgneal@16 115 several places in my code where the extra database calls caused by
bgneal@17 116 ``get_profile()`` were hurting my performance. I had replaced ``get_profile()``
bgneal@16 117 with custom code that queried the ``Profile`` manager directly to reduce
bgneal@16 118 database calls. I reviewed these changes and replaced them with
bgneal@16 119 ``select_related()`` calls. This simplified things. I love deleting lines of
bgneal@16 120 code. While I was doing this, I noticed a few places that I could use
bgneal@16 121 `prefetch_related()`_ (on other related fields) since my code pre-dated this
bgneal@16 122 method.
bgneal@16 123
bgneal@16 124 Next you should run any unit tests and verify they still pass. Hopefully you
bgneal@16 125 have unit tests, right? It was a big relief to have my tests passing after
bgneal@16 126 making these numerous changes.
bgneal@16 127
bgneal@16 128 Finally I would suggest using a profiler like the `Django Debug Toolbar`_ to do
bgneal@16 129 some spot checking on the number of SQL queries your pages generate after making
bgneal@16 130 these changes. In most cases I was able to reduce the number of calls or at
bgneal@16 131 least keep them the same. Using the magic of version control, I ran a version
bgneal@16 132 of my site both with and without my changes side-by-side. Using the profiler
bgneal@16 133 I convinced myself I was in fact making things better and not worse.
bgneal@16 134
bgneal@16 135 Conclusion
bgneal@16 136 ----------
bgneal@16 137
bgneal@16 138 I am not familiar with early Django history, but I'm guessing that
bgneal@16 139 ``get_profile()`` must have pre-dated the OneToOneField_. After making the
bgneal@16 140 switch, my code seems more straightforward with less "magic". In any event,
bgneal@16 141 I hope this post will make you less fearful about this change. It was not the
bgneal@16 142 major change I had thought it was going to be.
bgneal@16 143
bgneal@16 144
bgneal@16 145 .. _deprecations: https://docs.djangoproject.com/en/1.6/releases/1.5/#auth-profile-module
bgneal@16 146 .. _AUTH_PROFILE_MODULE: https://docs.djangoproject.com/en/1.6/ref/settings/#auth-profile-module
bgneal@16 147 .. _get_profile(): https://docs.djangoproject.com/en/1.6/ref/contrib/auth/#django.contrib.auth.models.User.get_profile
bgneal@16 148 .. _configurable user model: https://docs.djangoproject.com/en/1.6/releases/1.5/#configurable-user-model
bgneal@16 149 .. _ForeignKey: https://docs.djangoproject.com/en/1.6/ref/models/fields/#foreignkey
bgneal@16 150 .. _OneToOneField: https://docs.djangoproject.com/en/1.6/ref/models/fields/#onetoonefield
bgneal@16 151 .. _select_related(): https://docs.djangoproject.com/en/1.6/ref/models/querysets/#select-related
bgneal@16 152 .. _prefetch_related(): https://docs.djangoproject.com/en/1.6/ref/models/querysets/#prefetch-related
bgneal@16 153 .. _Django Debug Toolbar: https://github.com/django-debug-toolbar/django-debug-toolbar