The User model in Django is intentionally basic, defining only the username, first and last name, password and email address. It’s intended more for authentication than for handling user profiles. To create an extended user model you’ll need to define a custom class with a ForeignKey to User, then tell your project which model defines the Profile class. In your settings, use something like:
1 | AUTH_PROFILE_MODULE = 'accounts.UserProfile' |
To make it easier to let users create and edit their own Profile data, James Bennett (aka ubernostrum), who is the author of Practical Django Projects and the excellent b-list blog, created the reusable app django-profiles. It’s a companion to django-registration, which provides pluggable functionality to let users register and validate their own accounts on Django-based sites.
Both apps are excellent, and come with very careful documentation. But here’s the rub: Bennett’s documentation style comes from the mind of an engineer, rather than an average user who just needs to get things done quickly. For people who write Django code every day and are intimately familiar with the official Django docs, they’re probably sufficient. For those of us who don’t have the luxury of being full-time programmers, who don’t live and breathe Django, they’re frustrating. Sample templates are not included, and no clues are given as to what should go in the templates you create. Likewise, the ability to customize the default behavior of the apps is only hinted at, not spelled out. Users coming from a CMS world where you install and configure a plugin and get instant functionality for your site quickly become frustrated.
From IRC logs, it appears that Bennett believes banging your head against a wall is a great way to learn. To an extent, that’s true. I know there’s no better way to learn a new tool than having to solve real-world problems with it. But at the same time, learning doesn’t only take place in the Django docs – it happens on mailing lists, in code samples on djangosnippets.org (which, by the way, is another of Bennett’s projects), and, yes, in documentation for add-on apps like django-profiles.
Let’s take an example: A developer wants to let users edit their own profiles. They get their Profile model registered, install django-profiles, and create a template at profiles/edit_profile.html. What goes in that template? Not much is needed, but the django-profiles docs don’t give you a clue (nor do they give you a clue where to find the answer in the Django docs). You’ll need something like this:
1 2 3 4 5 6 7 8 9 10 11 12 | {% extends "base.html" %} {% block title %}Edit Profile{% endblock %} {% block content %} <h1>Edit contact info for {{ user }} </h1> <form method="POST" action=""> {{ form }} <input type="submit" name="submit" value="Update" id="submit"> </form> {% endblock content %} |
Now access /profiles/edit/ and you’ll see all fields on your profile model represented with appropriate field types. So far so good. Now you probably want to customize two things, right off the bat. You may have fields on the profile that only administrators should be able to edit, and you want to hide those fields. And you may want to modify the success_url, to control where the user is sent after a successful form submission. The docs for django-profiles say that the provided edit_profile view takes optional form_class and success_url arguments. But how can you pass in arguments? You’re simply linking to the predefined URL /profiles/edit/ – there is no code of your own from which you can pass in arguments.
This is where things became inscrutable to me. I was at an impasse, with no clue or hint as to what to do next. If the django-profiles docs had included a link to the section of the Django docs that contained the answer (or some kind of directional indicator) I could have done the research and gotten things moving. Fortunately, a friend and fellow Django developer had been down this road before and had the solution. Here’s how the pieces connect:
First, you need to create a custom ModelForm based on your Profile model. If you don’t already have a forms.py in your app, create one, then add something like:
1 2 3 4 5 6 7 8 | from django.db import models from django.forms import ModelForm from ourcrestmont.itaco.models import * class ProfileForm(ModelForm): class Meta: model = Foo exclude = ('field1','field2','field3',) |
The idea is to pass your custom ModelForm to django-profiles with the name form_class, thereby overriding the default object of the same name. django-profiles will then operate against your custom ProfileForm rather than from a default representation of your Profile model. Once I understood this, the light went on and things started to snap into place.
Still, how can you pass this custom form_class to django-profiles, when there’s no view code in your own app to handle this? That’s where trick #2 comes in: The seldom-used ability to pass dictionaries of custom values in from your urlconf. So wiring things up now becomes a pretty straightforward task. In urls.py, import your custom form and pass it through to the django-profiles view, right before the reference to the django-profiles urlconf. Because Django will use the first matching URL it finds, you want to do this before the django-profiles-provided URL is found so you can override it.
1 2 3 | from projname.appname.forms import ProfileForm ('^profiles/edit', 'profiles.views.edit_profile', {'form_class': ProfileForm,}), (r'^profiles/', include('profiles.urls')), |
You can pass in your custom success_url value in the same way:
1 2 3 | from projname.appname.forms import ProfileForm ('^profiles/edit', 'profiles.views.edit_profile', {'form_class': ProfileForm,'success_url':'/my/custom/url',}), (r'^profiles/', include('profiles.urls')), |
Now access /profiles/edit/ again and you’ll find that the view is using your custom form definition, rather a default one derived from the profile model. Pretty easy once you see how the pieces fit together.
If you need even more control than that, there’s another alternative to passing a dict in through the urlconf – write your own view with the name edit_profile, overriding aspects of the provided view of the same name:
1 2 3 4 5 | from profiles import views as profile_views from myprofiles.forms import ProfileForm def edit_profile(request): return profile_views.edit_profile(request, form_class=ProfileForm) |
(I haven’t tried this method).
profile_detail and profile_list
django-profiles enables other templates as well. As documented, the “details” template lets you retrieve all data associated with a single profile by sending an object named “profile” to the template profile/profile_detail.html, e.g.:
1 2 3 4 | <p><strong>Address 2:</strong><br>{{ profile.address2 }}</p> <p><strong>City:</strong><br> {{ profile.city }} </p> |
It’s not quite so clear how to get a list of all profiles in the system. The docs say:
profiles/profile_list.html will display a list of user profiles, using the list_detail.object_list generic view
You’ll access the list view at /profiles/, with template code along the lines of:
1 2 3 | {% for p in object_list %} <a href="{% url profiles_profile_detail p.user %}">{{ p }}</a>> {% endfor %} |
(in other words you can ignore the “list_detail.” portion of the object name referenced in the docs).
Let Users Edit Their Own Email Addresses
A common task when editing profile data would be to change one’s email address. But since the email address is included in the User model and not in the Profile model, it doesn’t show up in the {{form}} object.
The solution is to add an email field to the Form object (not the Profile model – that would not be DRY), then put a custom save method on the form that retrieves the corresponding User and updates its email field. Here’s a complete ProfileForm that does all of the above. With a little tweaking, this will also let users edit their first and last names.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | class ProfileForm(ModelForm): def __init__(self, *args, **kwargs): super(ProfileForm, self).__init__(*args, **kwargs) try: self.fields['email'].initial = self.instance.user.email # self.fields['first_name'].initial = self.instance.user.first_name # self.fields['last_name'].initial = self.instance.user.last_name except User.DoesNotExist: pass email = forms.EmailField(label="Primary email",help_text='') class Meta: model = Profile exclude = ('user',) def save(self, *args, **kwargs): """ Update the primary email address on the related User object as well. """ u = self.instance.user u.email = self.cleaned_data['email'] u.save() profile = super(ProfileForm, self).save(*args,**kwargs) return profile |
With a few well-placed links and code samples, the django-profiles docs could be a great learning opportunity for this kind of Lego-like site construction. Until then, I’ll update this post with any other tips users provide on django-profiles implementation.
Despite my gripes about the docs, many thanks to Bennett for all of his excellent free code, writing, and other contributions to the Django community. And a ton of thanks to mandric for the golden ticket on how to wire up the pieces.
thanks, this entry really helps me understanding the django profiles :)
could you please also give me some clue about the contents of the profile_detail.html & profile_list.html ? thanks :)
Hey adrianliem – I’ve updated the post with info on retrieving object data for profile_detail and profile_list in your templates.
hey thanks for the update :)
about the email problem you questioned, if i may suggest you, you can try to ask the question in stackoverflow.com — usually i ask questions about django in there, and the people there are pretty helpful :)
have a great day!
Posted. One response so far, but sounds too complicated. Waiting for something more… elegant.
Great post, thanks. If you figure out the email business I would be really appreciative. Also, what do you do if you want more user info on the profile detail page?
For example, if I have users adding notes to a site, and I want their profile page to list all the notes they’ve added. Is that what the “context” business is all about?
Thanks again.
Colin – I havent’ solved the email editing problem yet, but I did post the question at StackOverflow and some hints on how to approach the problem are there.
As for extra notes, those really shouldn’t be part of the profile – you should create a separate Notes model with a ForeignKey to the user’s profile, then use the __set notation to retrieve them. That way a user can have as many notes as you like attached to their profile without having to do hack-y things like putting note1, note2, note3 on the profile model.
Wow. Fast response time!
Thanks for the tips. I knew about the One To Many business and the “_set” thing, my problem is that I’m up to late. Here on the East Coast it’s past 1, and I apparently forgot that all I needed to do to reference notes was do a user.note_set.all to get the notes for a user…sorry to clog the instruments!
Also, I took a gander at your post on StackOverflow and I think I understand the idea of having separate pages for such things.
Anyway, I’m done for the night. Happy hacking, and thanks again.
One last thing. Interesting to see on your twitter profile that you work at the Berkley J-school. I’m a reporter for a small weekly out here in Maine and am using django to help morph my job description into a reporter/web developer for the company… Amazing stuff going on in the internet/journalism world these days, good luck!
Love to hear examples of Django being used in journalism – especially by non-hardcore programmers. Let us know when you’ve got something up for the world to see – would love to take a look.
Thanks so much, I used your post to get most of my info to show up. However on the list view, your example only gave me a link “object list”. I changed {{ p }} to {{ p.user }} to get my user name to appear on the list page. The link does click through to the detail page, but the url in the link is http://domain/full/server/path/to/project/profiles/p.user
Shouldn’t the url path just be domain/detail/p.user
or something similar? The resulting detail page has a link with the correct profile, so it clicks through to the edit page. I’m concerned about the url path generated on the profiles list page. It seems like that path should not even work.
How would I get the normal path generated in the profiles list page?
That sounds like you might not have an appropriate unicode method set on your profile model. {{p}} should render exactly as specified in that method.
Links to profile pages are defined in the urls.py belonging to django-profiles, so if you want to change them you can edit them there.
How to set AUTH_PROFILE_MODULE? Should I create a models.py in the root directory of the project, create a model class (eg. UserProfile) and then set the value of AUTH_PROFILE_MODULE to myproject.UserProfile, or to myproject.models.UserProfile?
Or should I create an app for only have the models.py in it (startapp UserProfileModel)??? But then I have two applications installed for one purpose.
What should I do best? That’s very complicated!
Reinhardt: If I may.
Django docs emphasize the importance of only using the appname.ModelName for the AUTH_PROFILE_MODULE.
As for whether to create a separate app, don’t. You’ll notice that django-profile does not include a model.py file. Create one and then add a model for your UserProfile with whatever you’d like to add to the User model (url, address, phone number, etc…).
That becomes your AUTH_PROFILE_MODULE setting:
AUTH_PROFILE_MODULE=profiles.UserProfile
Works like a charm.
OK, so I added:
def __unicode__(self):
return self.user.first_name
to my UserProfile model and the first name automatically appears with {{ p }}.
Is that just a semantic, or am I saving myself something?
Jonas – The point of the unicode method is to provide a default string by which that model is represented anywhere where you don’t specify it otherwise. So if p is your profile and you just call p, you’re now going to get just the first name on this page and anywhere else in your system where you refer to a profile (including in the admin). If you wanted the full name to appear instead you might use:
return "%s %s" % (self.user.first_name, self.user.last_name)Colin – I installed django-profiles and it is not in my project directory, but in c:\python\lib\site-packages\profiles
Should I put the models.py in the directory mentioned above, or somewhere in my projects dir?
Can you explain the reason why this works
(‘^profiles/edit’, ‘profiles.views.edit_profile’, {‘form_class’: ProfileForm,}),
(r’^profiles/’, include(‘profiles.urls’)),
Why does listing the profiles/edit first actually get process last?
I tried it the other way and it defaults to the profiles app. Why does listing it first have it processed last?
It works, I’m just confused.
Jeff
Jeff – profiles/edit is defined in profiles.urls, so it’s essentially being defined twice – once in the Profiles app itself and once by you. When Django is processing URLs, it always grabs the FIRST match it finds in the list of all possible URLs. So since you want to override the view provided by the Profiles app you need to make sure Django intercepts your URL definition BEFORE the one provided by the Profiles app.
Shacker,
The is this correct?
BTW – liked your article about Six Flags. I just took my family to Sea World and experienced some of you pain, although I guess the east coast is a little more affordable – we only paid $10 to park :)
Good catch Jeff. My sample was written correctly but the explanatory text was inside out. I’ve just fixed that. Thanks.
Hi, with this package is possible to manage different profiles for different kind of users? How ca I do?
Ste – Yes, but that’s a very different problem from the one this blog post is talking about. Basically you’ll want to create multiple profile models, all with non-overlapping sets of attributes. What you’ll lose is the ability to define an canonical profile model in your Django project. But you’ll need to ask more specific questions if you have more – try the django-users mailing list.