Django 1.8 Tutorial – 5.2 Adding a User Profile

In the previous section, I noted that I needed to learn how to make my own authenticator. I also needed to learn to add fields to the User. There are two ways to do it, and the Django docs don’t clearly state which is better, for what situations.

I think the old way of adding fields, called User Profiles, is the way to go. It’s more code, but I think the code ends up being a little more explicit and easier to read. It also allows you to make multiple different profiles for different things.

The other way, of subclassing AbstractUserModel to create a custom user model, and then specifying that as the user model, is more properly OOP, but is less portable and less reusable.

My example below has two dependencies: the regular auth system and the django-registration-redux library.

A user profile module is simple. There are only four files: models.py, views.py, urls.py, and admin.py.

New fields are added to models.py:


from django.db import models
from django.conf import settings
from django.db.models.signals import post_save
from django.contrib.auth.models import User
from registration.signals import user_registered


class UserProfile(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL, primary_key=True)
    homepage = models.URLField()


def assure_user_profile_exists(pk):
    """
    Creates a user profile if a User exists, but the
    profile does not exist.  Use this in views or other
    places where you don't have the user object but have the pk.
    """
    user = User.objects.get(pk=pk)
    try:
        # fails if it doesn't exist
        userprofile = user.userprofile
    except UserProfile.DoesNotExist, e:
        userprofile = UserProfile(user=user)
        userprofile.save()
    return


def create_user_profile(**kwargs):
    UserProfile.objects.get_or_create(user=kwargs['user'])

user_registered.connect(create_user_profile)

This code adds a homepage field. Remember those?

The rest of the code is used to create UserProfiles when User objects are created, or to create them for existing users.

Read up on Signals, to understand what this means. It causes the function create_user_profile to execute whenever the user_registered signal is raised.

user_registered.connect(create_user_profile)

The latter situation is handled in views.py:


from django.views.generic import DetailView
from django.views.generic import CreateView, UpdateView
from django.contrib.auth.models import User
from .models import UserProfile, assure_user_profile_exists

class UserProfileDetail(DetailView):
    model = UserProfile

class UserProfileUpdate(UpdateView):
    model = UserProfile
    fields = ('homepage',)

    def get(self, request, *args, **kwargs):
        assure_user_profile_exists(kwargs['pk'])
        return (super(UserProfileUpdate, self).
                get(self, request, *args, **kwargs))

Just like before, we use the generic model views, but there’s one twist here. When we attempt to update the profile, we do a “get”, and that calls assure_user_profile_exists, to force the creation of this UserProfile.

Then it just returns the regular results.

There is one thing that’s missing, which is the deletion view. That’s because deletions should happen on the User. We should also link up a deletion function with the signals that are raised when users are deleted – but I haven’t written that yet.

There are only two urls in urls.py:


from django.conf.urls import url
import views

urlpatterns = [
    url(r'^(?P[0-9]+)/$',
        views.UserProfileDetail.as_view(),
        name='user_profile_detail'),
    url(r'^(?P[0-9]+)/update/$',
        views.UserProfileUpdate.as_view(),
        name='user_profile_edit'),
]

These work with two templates. These are both in user_profile/templates/user_profile/:

userprofile_detail.html

{% extends "base.html" %}

{% block content %}
<h2>{{ object.user.username }} Profile</h2>
<p>Homepage: {{ object.homepage }}</p>
{% endblock %}

userprofile_form.html

{% extends "base.html" %}

{% block content %}
    <h2>Edit {{ object.user.username }} Profile</h2>
    <form method="post">
        {% csrf_token %}
        {{ form.as_p }}
        <input type="submit" />
    </form>
{% endblock %}

There’s also an admin.py to add the fields to the admin:


from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
from user_profile.models import UserProfile


class UserProfileInline(admin.StackedInline):
    model = UserProfile
    can_delete = False


class UserAdmin(UserAdmin):
    inlines = (UserProfileInline,)


admin.site.unregister(User)
admin.site.register(User, UserAdmin)

This is a lot of code to monkey-patch the admin so it uses our version of UserAdmin to edit the users.

So, no code for this example. It’ll be in the next part, when I try to integrate some of these things into the minimal app, which is less and less minimal by the day.