Creating a custom user model in Django 1.6 - Part 1

Published byGoogle on

Welcome back to our Django tutorial series.

Before Django 1.5 customizing the user model was a bit of a hack as it involved creating a user profile with a one-to-one relationship to the user. All that changed in 1.5.

In this tutorial we are goin to see how to substitue Django's default user model with our own and allow the use of a user's e-mail instead of it's username during the login process.

Since 1.7 is not officially stable yet, this tutorial is meant for Django 1.6 but should also work with 1.7 (except for the South part which will be replaced in 1.7 by Django's own schema migration mechanism)

When forced to choose a login name on a website, you sometimes have to get creative because your usual login name has already been taken. This often leads to a lot of head scratching when, 3 years later, you have to remember that login name in order to get into that particular website.
To make things easier, the most commonly used method nowadays is to allow users to log in using either their username or their e-mail address.
By default Django requires a username in order to login, but it doesn't always have to be the case.

So let's get started!

First things first lets define our new user model. To do this, we'll first create an application dedicated to our new user and its authentication:

$ python manage.py startapp myauth

This user model is basically the same as the default django user model with a slightly different behaviour. But for the sake of it we will extend AbstractBaseUser instead of extending auth.User so that this tutorial can be used as a base for extending users in a broader way.
Edit myauth/models.py so that it looks like this:

from django.db import models

import re
import uuid

from django.core import validators
from django.utils import timezone
from django.core.mail import send_mail
from django.utils.http import urlquote
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager
from django import forms


class User(AbstractBaseUser, PermissionsMixin):
  username = models.CharField(_('username'), max_length=30, unique=True,
    help_text=_('Required. 30 characters or fewer. Letters, numbers and @/./+/-/_ characters'),
    validators=[
      validators.RegexValidator(re.compile('^[\w.@+-]+$'), _('Enter a valid username.'), _('invalid'))
    ])
  first_name = models.CharField(_('first name'), max_length=30, blank=True, null=True)
  last_name = models.CharField(_('last name'), max_length=30, blank=True, null=True)
  email = models.EmailField(_('email address'), max_length=255)
  is_staff = models.BooleanField(_('staff status'), default=False,
    help_text=_('Designates whether the user can log into this admin site.'))
  is_active = models.BooleanField(_('active'), default=False,
    help_text=_('Designates whether this user should be treated as active. Unselect this instead of deleting accounts.'))
  date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
  receive_newsletter = models.BooleanField(_('receive newsletter'), default=False)

  USERNAME_FIELD = 'username'
  REQUIRED_FIELDS = ['email',]

  class Meta:
    verbose_name = _('user')
    verbose_name_plural = _('users')

  def get_full_name(self):
    full_name = '%s %s' % (self.first_name, self.last_name)
    return full_name.strip()

  def get_short_name(self):
    return self.first_name

  def email_user(self, subject, message, from_email=None):
    send_mail(subject, message, from_email, [self.email])

As you can see, this mimics Django's default user model, if you need to add fields to your model or specific validators, you can do it all here as with any other model.
Now User model is a bit specific, amongst other things it requires a Manager which has methods to create unpriviledged users as well as superusers (used from the command line manage.py createsuperuser) so let's create the manager by adding this class at the beginning of myauth/models.py:

class UserManager(BaseUserManager):

  def _create_user(self, username, email, password, is_staff, is_superuser, **extra_fields):
    now = timezone.now()
    if not username:
      raise ValueError(_('The given username must be set'))
    email = self.normalize_email(email)
    user = self.model(username=username, email=email,
             is_staff=is_staff, is_active=False,
             is_superuser=is_superuser, last_login=now,
             date_joined=now, **extra_fields)
    user.set_password(password)
    user.save(using=self._db)
    return user

  def create_user(self, username, email=None, password=None, **extra_fields):
    return self._create_user(username, email, password, False, False,
                 **extra_fields)

  def create_superuser(self, username, email, password, **extra_fields):
    user=self._create_user(username, email, password, True, True,
                 **extra_fields)
    user.is_active=True
    user.save(using=self._db)
    return user

As well as this line to the User class (in order to set the correct manager):

  objects = UserManager()

Now let's add our application to settings.py and tell Django what model it should use as default User model by setting AUTH_USER_MODEL.

INSTALLED_APPS = (
    .
    .
    .
## Add this line:
    'myauth',
)

## Also add this at the bottom of the fille:
AUTH_USER_MODEL = 'myauth.User'

And finally let's create the new tables in our database using south:

$ python manage.py schemamigration myauth --initial
Creating migrations directory at '/home/emma/web-code/mybaseproject/myauth/migrations'...
Creating __init__.py in '/home/emma/web-code/mybaseproject/myauth/migrations'...
 + Added model myauth.User
 + Added M2M table for groups on myauth.User
 + Added M2M table for user_permissions on myauth.User
Created 0001_initial.py. You can now apply this migration with: ./manage.py migrate myauth
$ python manage.py migrate myauth
Running migrations for myauth:
 - Migrating forwards to 0001_initial.
 > myauth:0001_initial
 - Loading initial data for myauth.
Installed 0 object(s) from 0 fixture(s)

It is always best to determine at the begining of a project whether you will need to use a custom user model or not, but it is not always possible as project requirements change during the life of most projects.
So if you already have existing users you'll need to also create a data migration but we'll see about that next week. We will also cover form creation for your new user model.

In the mean time, have a great week everyone and happy coding Smile

Tutorial Django Custom user model

Comments

Why you totally duplicate AbstractUser class in your class User? You can make something like
class User(AbstractUser):
receive_newsletter = models.BooleanField(_('receive newsletter'), default=False)

Same question about your UserManager and django's UserManager.

By Pavel Zagrebelin - 3 years, 6 months ago Reply 

Hi,

as mentioned above, this tutorial is meant be used as a base for extending users in a broader way. In the above example we are, so far, only adding a field to Django's AbstractUser class and if it is the only thing you want to do, you could extend Django's AbstractUser directly.
Now if you want to i.e. completely get rid of username and use only the e-mail address or replace is_staff with something more complex, you can't extend Django Django's AbstractUser and have to go through the process described here.
Part of those scenarios will be covered in part 4 of this tutorial, this is why I chose this way of doing things.

By Emma Delescolle - 3 years, 6 months ago Reply 

Excellent tutorial, just what I needed, modifying Django's user model is indeed annoying

By Waleed Asif - 3 years, 4 months ago Reply 

Thanks :-)

And yes, it can be annoying, that's why the next tutorial which is late will be talking about making your modified user model a re-useable app.

By Emmanuelle Delescolle - 3 years, 4 months ago Reply 

Post a comment