Budgets are moral documents.
June 27th, 2009
 
-->

django-profiles: The Missing Manual

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. Unfortunately, I was not able to find these answers from the Django docs on my own – a friend supplied the answers.

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).
——————-

I have one remaining question: 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. Anyone know how to get it in there?

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.

23 Responses to “django-profiles: The Missing Manual”

  1. adrianliem says:

    thanks, this entry really helps me understanding the django profiles :)

  2. adrianliem says:

    could you please also give me some clue about the contents of the profile_detail.html & profile_list.html ? thanks :)

  3. shacker says:

    Hey adrianliem – I’ve updated the post with info on retrieving object data for profile_detail and profile_list in your templates.

  4. adrianliem says:

    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!

  5. shacker says:

    Posted. One response so far, but sounds too complicated. Waiting for something more… elegant.

  6. Colin says:

    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.

  7. shacker says:

    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.

  8. Colin says:

    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.

  9. Colin says:

    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!

  10. shacker says:

    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.

  11. Jonas Rullo says:

    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?

  12. shacker says:
    I changed {{ p }} to {{ p.user }} to get my user name to appear on the 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.

  13. Reinhardt says:

    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!

  14. Colin says:

    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.

  15. Jonas Rullo says:

    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?

  16. shacker says:

    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)

  17. Reinhardt says:

    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?

  18. Jeff says:

    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

  19. shacker says:

    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.

  20. Jeff says:

    Shacker,

    The is this correct?

    In urls.py, import your custom form and pass it through to the django-profiles view, right after the reference to the django-profiles urlconf (you want to do this after, not before, so the last matching URL for /profiles/edit/ is the one you define, not the one django-profiles defines:

    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 :)

  21. shacker says:

    Good catch Jeff. My sample was written correctly but the explanatory text was inside out. I’ve just fixed that. Thanks.

  22. ste says:

    Hi, with this package is possible to manage different profiles for different kind of users? How ca I do?

  23. shacker says:

    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.

Leave a reply

→ Want an avatar/icon to show up alongside your comment? Get a free Gravatar. ←