Django Extending User model

Django only provides bare minimum fields in the User model to get you started, but it also gives you full power to extend the User model to suit your application needs.

Recall that by default User model contains the following fields:

  1. username
  2. first_name
  3. last_name
  4. email
  5. password
  6. last_login
  7. is_active
  8. is_staff
  9. is_superuser
  10. date_joined

The first step in extending a User model is to create a new model with all the additional fields you want to store. To associate our new model with the User model define a OneToOneField containing reference to the User model in our new model.

Open blog's models.py and modify Author model as follows:

from django.db import models
from django.contrib.auth.models import User

# Create your models here.

class Author(models.Model):
        
    ## required to associate Author model with User model (Important)
    user = models.OneToOneField(User, null=True, blank=True)

    ## additional fields
    phone = models.IntegerField(blank=True, default=1)    
    activation_key = models.CharField(max_length=255, default=1)
    email_validated = models.BooleanField(default=False)

    def __str__(self):
        return self.name

    ...


Here we have defined three extra fields, namely phone, activation_key and email_validated. Each of theses fields does the following:

Field Description
activation_key It stores a long unique key sent during email verfication. User must click on the link containing activation_key to activate the account.
email_validated The email_validated field stores Boolean data i.e 0 or 1. If account is not validated it contains 0. Otherwise 1.

In the terminal or command prompt, create new migraton file by executing the following command:

(env) C:\Users\Q\TGDB\django_project\django_project>python manage.py makemigrations
Migrations for 'blog':
  blog\migrations\0001_initial.py:
    - Create model Author

Finally, commit the changes using python manage.py migrate command.    

(env) C:\Users\K\TGDB\django_project\django_project>python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, blog, contenttypes, sessions
Running migrations:
  Rendering model states... DONE
  Applying blog.0001_initial... OK

(env) C:\Users\K\TGDB\django_project\django_project>

While we are at it, let's update __str__() and get_absolute_url() methods. At this point both of these method uses name field of the Author model. As there is no such field right now, if you try to http://127.0.0.1:8000/ or any other page which uses Author model in anyway you would get an error like this:

[img]

To fix this error update __str__() and get_absolute_url() methods to use username attribute of the User model instead of name.

...
    def __str__(self):
        return self.user.username

    def get_absolute_url(self):
        return reverse('post_by_author', args=[self.user.username])
...

Lets revise our cadmin's register() view to send email verification to the newly created account.

from blog.models import Post, Category, Tag, Author
from django.contrib.auth.models import User
from django_project.helpers import generate_activation_key
from django.core.mail import send_mail
from django.conf import settings
...

def register(request):
    if request.method == 'POST':
        f = CustomUserCreationForm(request.POST)
        if f.is_valid():

            # send email verification now

            activation_key = helpers.generate_activation_key(username=request.POST['username'])

            subject = "TheGreatDjangoBlog Account Verification"

            message = '''\n
Please visit the following link to verify your account {}://{}/cadmin/activate-account/?key={0}
                        '''.format(request.scheme, request.get_host(), activation_key)            

            error = False

            if not send_mail(subject, message, setting.SERVER_EMAIL, [request.POST['email']]):
                messages.add_message(request, messages.INFO, 'Unable to send email verification. please Try again')
                error = True
            else:
                messages.add_message(request, messages.INFO, 'Account created! Click on the link sent to your email to activate '
                                                             'the account')

            if not error:
                u = User.objects.create_user(
                        request.POST['username'],
                        request.POST['email'],
                        request.POST['password1'],
                        is_active = 0
                )

                author = Author()
                author.activation_key = activation_key
                author.user = u
                author.save()

            return redirect('register')

    else:
        f = CustomUserCreationForm()

    return render(request, 'cadmin/register.html', {'form': f})

Here is rundown of the changes we have made in register() view:

1) In line 62, we are creating an activation_key based upon the username using generate_activation_key() helper function. The generate_activation_key() function is defined in helpers.py file as follows:

import hashlib
from django.utils.crypto import get_random_string
...

def generate_activation_key(username):
    chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)'
    secret_key = get_random_string(20, chars)
    return hashlib.sha256((secret_key + username).encode('utf-8')).hexdigest()

2) In lines 72-74, we are sending email verification using send_email() function. The syntax of send_mail() function is:

send_mail(subject, message, sender, recipient_list)

On success it returns 1, otherwise 0.

If error is encountered while sending email verification we set error_email_verification to True.

3) If no error is encountered while sending email then we proceed to create new User. Notice how we are saving the additional User data into the database.

In lines 80-85, we are using create_user() method to save built-in fields of User model. At this point additional user data like phone, activation_key and email_validated is not saved. To save this data, in line 87 we are calling create() method on Author model.

Next, add another view called activate_account()in cadmin's views.py file, whose job is to set is_active field to True when visitor click on the email verification link in the email.

from django.shortcuts import redirect, get_object_or_404, reverse, Http404
...

def activate_account(request):
    key = request.GET['key']
    if not key:
        raise Http404()

    r = get_object_or_404(Author, activation_key=key, email_validated=False)
    r.user.is_active = True
    r.user.save()
    r.email_validated = True
    r.save()

    return render(request, 'cadmin/activated.html')

Create a new file named activated.html and add the following code to it:

{% extends "cadmin/base.html" %}

{% block title %}
    Create Account - {{ block.super }}
{% endblock %}

{% block content %}

    <div class="logout">
        <p>Account activated! Please <a href="{% url 'login' %}">login</a></p>
</div>

{% endblock %}

Fianlly, add the url pattern named activate just below the register url pattern in cadmin's urls.py file as follows:

urlpatterns = [
    ...
    url(r'^register/$', views.register, name='register'),
    url(r'^activate-account/$', views.activate_account, name='activate'),
   ...

]

Visit http://127.0.0.1:8000/cadmin/register/ and create a new user.

If registration succeeds, you will be greeted with a success message and email containing a verification link will be sent to the email id provided at the time of registration.

[form_success_message]

[screen shot of email contining verification link]

To verify your account click on the verification link and you will be greeted with "Account successfully verified. Please login." message.

You account is now active and can be used to login. Visit http://127.0.0.1:8000/cadmin/login/ and login with your newly created account.