Black Glasses

Thinking and solving, traveling and seeing.

Custom Django User Model

With the release of Django 1.5 came the addition of a configurable user model, an easier way of storing user-related data within your applications.

It seems every time I start a Django project, I go to create the custom user model, forget how to do it, and then end up following the various steps of the same 8-9 tutorials until I have what I’m looking for. So, hopefully, by writing this down, I’ll remember what I’m doing next time, and hopefully this will help someone else too.

For most of my projects, the standard Django user model/authentication is inadequate. I don’t want usernames, I want my users to register using email addresses. This presents some issues with the standard Django user implementation. You can add an email field to the standard Django user model, but the authentication backend is still going to look for the username when logging in. Even with the email field added, it’s still going to require a username when registering.

The custom implementation is actually quite simple. I usually start by creating a new accounts application in my Django project.

1
$ python manage.py startapp accounts

models.py

In order to extend the Django user model, we’ll want to create a custom User class, inheriting the AbstractBaseUser class.

models.py
1
2
3
4
5
6
7
from django.db import models
from django.contrib.auth.models import AbstractBaseUser

class User(AbstractBaseUser):
    """
    Custom user class.
    """

Now that we have our class in place, we can add our basic attributes that we’ll want in our user model.

models.py
1
2
3
4
5
6
7
8
9
10
11
from django.db import models
from django.contrib.auth.models import AbstractBaseUser

class User(AbstractBaseUser):
    """
    Custom user class.
    """
    email = models.EmailField('email address', unique=True, db_index=True)
    joined = models.DateTimeField(auto_now_add=True)
    is_active = models.BooleanField(default=True)
    is_admin = models.BooleanField(default=False)

I usually keep it simple, not a ton of fields. Any other information that I want stored, I would add to a user profile class (maybe I’ll do a post about this in the future.) You’ll notice on the email field, I’ve set the unique field to True. This makes sure that users can only register an email once, and that there will be no duplicates. This flag will also help us in our registration form validation. I’ve also added an optional database index on that field with db_index=True. The joined field is automatically populated with a DateTime of when the user registers, and is_active is set to True by default. By default, I don’t want users to be administrators when they register, so I have set the is_admin field to False.

We need to add one more attribute to this model in order for it to play nice with Django, the USERNAME_FIELD.

models.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from django.db import models
from django.contrib.auth.models import AbstractBaseUser

class User(AbstractBaseUser):
    """
    Custom user class.
    """
    email = models.EmailField('email address', unique=True, db_index=True)
    joined = models.DateTimeField(auto_now_add=True)
    is_active = models.BooleanField(default=True)
    is_admin = models.BooleanField(default=False)

    USERNAME_FIELD = 'email'

    def __unicode__(self):
        return self.email

USERNAME_FIELD is the field that tells Django which field on the User model is used as the unique identifier. For us, this is our email address field. This field must be unique (unique=True) in its definition, which our email address field is.

I’ve also added the __unicode__() method to display a human-readable representation of our User model object.

Now that our model is complete, we’ll move to the forms.

Forms

Rather than having a single forms.py file packed full of everything, I like to split my forms up into a separate module inside my accounts application. This keeps things easy. Just create a folder called forms and put an __init__.py file inside. Inside this new folder, we’ll create 2 new files, authenticate.py, for our login form, and register.py, for our registration form. We’ll start with register.py since it’s a bit longer.

forms/register.py

Our registration form is going to be simple, but it’s easily expanded for more complex operations. We’ll start by creating a new form, inheriting the forms.ModelForm helper class that Django provides.

forms/register.py
1
2
3
4
5
6
7
from django import forms
from accounts.models import User

class RegistrationForm(forms.ModelForm):
    """
    Form for registering a new account.
    """

This follows a similar pattern to what we did above in models.py, we’re just extending some functionality that already exists in Django. We’re using forms.ModelForm because our form is going to be mapped directly to our User model, this will become obvious once we add some more fields.

forms/register.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from django import forms
from accounts.models import User

class RegistrationForm(forms.ModelForm):
    """
    Form for registering a new account.
    """
    email = forms.EmailField(widget=forms.widget.TextInput,label="Email")
    password1 = forms.CharField(widget=forms.widget.PasswordInput,
                                label="Password")
    password2 = forms.CharField(widget=forms.widget.PasswordInput,
                                label="Password (again)")

    class Meta:
        model = User
        fields = ['email', 'password1', 'password2']

The fields we’ve added to the RegistrationForm are the fields which will appear as HTML when the view renders our page. We’ve added some attributes to the form fields to tell Django how we want our model form handled. A widget is how Django handles HTML inputs. The widget deals with the data from POST/GETs, as well as the rendered HTML. So, widget=forms.widget.TextInput would be a simple text field, where widet=forms.widget.PasswordInput would be a password input. There are tons of widgets as well as attributes which you can pass into the widget to customize it. This is covered in the documentation. The label= field allows us to customize the HTML label which the form can render for each field.

Within our new RegistrationForm is a class, Meta. This class in Django acts as a way of attaching additional options, or metadata, to the form. In models this could be ordering options, overriding the database table name, or other options which you may want to exercise control over. For our form, we use it to associate our User model directly to the form, as well as tell the form which fields to accept submitted data from.

As far as security goes, it’s best practice to use the fields=[] attribute to explicitly set all fields that should be edited in the form. This also aids in our validation, telling the form which fields to expect. In our case, we wouldn’t want users to be able to set the is_admin attribute on their own record when registering, this would lead to huge security implications. So, to mitigate this, we specifically set which fields can be written. There’s more detail about the Meta class, and which options can be set in the Django documentation.

Next step with our registration form is just to add some simple validation, and provide a save mechanism.

forms/register.py
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
27
28
29
30
31
32
33
34
35
from django import forms
from accounts.models import User

class RegistrationForm(forms.ModelForm):
    """
    Form for registering a new account.
    """
    email = forms.EmailField(widget=forms.widget.TextInput,label="Email")
    password1 = forms.CharField(widget=forms.widget.PasswordInput,
                                label="Password")
    password2 = forms.CharField(widget=forms.widget.PasswordInput,
                                label="Password (again)")

    class Meta:
        model = User
        fields = ['email', 'password1', 'password2']

    def clean(self):
        """
        Verifies that the values entered into the password fields match

        NOTE: Errors here will appear in ``non_field_errors()`` because it applies to more than one field.
        """
        cleaned_data = super(RegistrationForm, self).clean()
        if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:
            if self.cleaned_data['password1'] != self.cleaned_data['password2']:
                raise forms.ValidationError("Passwords don't match. Please enter both fields again.")
        return self.cleaned_data

    def save(self, commit=True):
        user = super(RegistrationForm, self).save(commit=False)
        user.set_password(self.cleaned_data['password1'])
        if commit:
            user.save()
        return user

For our clean() method, we’re simply making sure the password fields match. This is something that Django does out of the box when using the standard form, but since we’ve implemented our own, we need to handle this part. The email field is validated automatically, we’ve told the form to expect it in fields=['email',...etc.], and on the model we have unique=True, so Django knows exactly how to handle that.

We also have to override the default save() method on our form because we need to handle something that is specific to user creation. In order for our inputted password to be properly hashed and stored upon creation, we need to run the set_password(password) method on a user object, before saving it to the database. We create a user object (line 33) using the already validated form fields. The .save(commit=False) tells Django to save the new record, but don’t commit it to the database yet. We then take the returned user object (line 34), and set the password using the password submitted. This takes care of the password hashing but still doesn’t save it to the database yet. Finally, we call user.save() and save the user to the database.

Now that our registration form is in place, we can move on to the much simpler log in form.

forms/authenticate.py

Our authentication form is two fields, email and password. To do this, we’ll start a new form, inheriting the forms.Form class from Django.

forms/authenticate.py
1
2
3
4
5
6
7
8
9
10
11
from django import forms

class AuthenticationForm(forms.Form):
    """
    Login form
    """
    email = forms.EmailField(widget=forms.widgets.TextInput)
    password = forms.CharField(widget=forms.widgets.PasswordInput)

    class Meta:
        fields = ['email', 'password']

The form itself is much simpler compared to forms/register.py, mainly because we aren’t acting as a forms.ModelForm here. We are simply taking data, validating that it exists (and in the proper format for field email), and handing it along the chain to do our User model lookups to ensure it exists.

There’s only a few steps left. We still need to wire up our views, create our custom authentication backend, and point our URLs to the accounts application. We’ll finish up this accounts application in Part 2.

Post any questions you may have in the comments.

Comments