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