Registration in Django with Email Confirmation

published on: | by cindy In categories: Tutorial Series , Django

We all hate the extra step of confirming email when we register on a site don't we? Well, at least I do. Why must I click on some tokens/codes to confirm it's my email? We are about to find out so stay with me. From experience, this is a very important step if you intend to send emails to your clients for whatever reason, don't skip this part. We will create users and send them confirmation tokens to make sure they are who they say they are.

But why is it important to send confirmation tokens to a user?

  • If you don't verify the existence of an email address, your bounce rate is likely to be high
  • Some malicious individuals will use other people email address intentionally maybe to get some sensitive information
  • You may end up bothering people who didn't sign up for your services simply because you didn't verify the ownership of the email address
  • The list is endless you kinda get the point right?

So Django comes with powerful inbuilt authentication system whose center lies the User object.The inbuilt authentication system is graced with authentication and password management views. No need to break a sweat. Our scope is creating users and Django already has userCreationform for creating users. But for this tutorial, we go all the way, create a similar form to register users, create a profile for the user and send a confirmation link to the user's email.Users become active only after confirmation

Creating accounts app

I assume you are familiar with creating a project in Django.If not check previous tutorial on django project to get you started.I will dive right away into creating an app called accounts to manage the users using the command

python manage.py startapp accounts
Activate accounts app by adding it to installed-apps
INSTALLED_APPS = [
'accounts',
]

Understanding Django User Object

The user object comes with username, password, email, first_name and last_name fields The username is required and unique, it is used to authenticate the user. The email field on the other hand is optional and not unique.

This may pose a problem if you want to authenticate using email address as common with most apps today. In a future tutorial, we will authenticate users with either the username or email. Therefore, we are about to set the email field to required and unique.

We will write a function to check if the email the user inputs exists, throw an error if a matching email is found or complete registration otherwise.

in accounts app create a file and call it forms.py

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


class RegistrationForm(forms.ModelForm):
    email = forms.EmailField(max_length=200, help_text='Required')
    password = forms.CharField(label='Password, widget=forms.PasswordInput)
    password2 = forms.CharField(label='Repeat password, widget=forms.PasswordInput)

    class Meta:
        model = User
        fields = ('username', 'email', 'first_name',)

    def clean_password2(self):
        cd = self.cleaned_data
        if cd['password'] != cd['password2']:
            raise forms.ValidationError('Passwords do not match.')
        return cd['password2']

    def clean_email(self):
        email = self.cleaned_data['email']
        if User.objects.filter(email=email).exists():
            raise forms.ValidationError(
                'Please use another Email, that is already taken')
        return email

In a nutshell, before creating the registration form, remember to import the User object. Next, I have defined the email field with a help text indicating that it is required and two other fields for password input. The meta class describes the fields inherited from the User model and the two functions to validate password and email

Registration Views

We will define the registration logic in views.py.Remember to import the registration form we just created in forms.py We are going to create the accounts_register function to handle the registration process.

from django.shortcuts import render
from .forms import RegistrationForm

def accounts_register(request):
    if request.method == 'POST':
        registerForm = RegistrationForm(request.POST)
        if registerForm.is_valid():
            user = registerForm.save(commit=False)
            user.email = registerForm.cleaned_data['email']
            user.set_password(registerForm.cleaned_data['password'])
            # Save the User object
            user.save()
    else:
        registerForm = RegistrationForm()
    return render(request, 'accounts/register.html', {'form': registerForm})

URLs and templates

We should create the urls and templates. create a new file urls.py in accounts app and include the following

from django.urls import path
from . import views
urlpatterns = [
    path('register/, views.accounts_register, name='register'),
]

in you main urls.py include django authentication views and include accounts views as well.

from django.contrib import admin
from django.urls import path, include
urlpatterns = [
    path('admin/', admin.site.urls),
    # django auth views
    path('accounts/', include('django.contrib.auth.urls')),  
    path('accounts/', include('accounts.urls')),
]

Registration Templates

I will create two folders.Registration folder for Django inbuilt views. This is the default place where Django will look for the templates of its inbuilt views and accounts folder for the register view and the tokens we will send the users.

In accounts app create a folder called templates, then create two subfolders: the registration and accounts folders. To have a glimpse of Django views navigate to http://127.0.0.1:8000/accounts you will see all the views it comes with and your only duty is to create their templates in the registration folder which is not our focus today.

Back to the matter at hand, inside accounts folder create register.html

<!Doctype html>
<html>
<head>
    <title>Create an account</title>
</head>
<body>
    <form action="." method="post">{% csrf_token %}
        <p class="hint-text">Create your account. It''s free and only takes a minute.</p>
        {{ form.as_p }}
        <button type="submit" class="btn btn-block">Register Now</button>
    </form>
</body>
</html>

Navigate to http://127.0.0.1:8000/accounts/register/ and you are likely to get template not found error as shown in the image below:

Add Domain

Let's fix that by telling django where to find our templates.Add the following in settings.py

TEMPLATES = [
    {
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
    },
]

If we go back to our browser we should now see this not so nice registration form.

Add Domain

You can now create users

Confirm User Registration

In order to send emails, we must configure our SMTP settings. In settings.py, add the following code replacing the string with actual information.

EMAIL_HOST=host
EMAIL_HOST_USER=''user"
EMAIL_HOST_PASSWORD=''password''
EMAIL_PORT=587
EMAIL_USE_TLS=True

Token Generation

We have to create the tokens we will send. Create a new file and call it tokens.py inside the accounts app.

We will inherit from Django PasswordResetTokenGenerator to generate the tokens

from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.utils import six

class AccountActivationTokenGenerator(PasswordResetTokenGenerator):
    def _make_hash_value(self, user, timestamp):
        return (
            six.text_type(user.pk) + six.text_type(timestamp) +
            six.text_type(user.is_active)
        )

account_activation_token = AccountActivationTokenGenerator()

Sending Tokens

We have a number of imports to do. Don't forget to import the account_activation_token from tokens.py Paste the following code, it appears long but it is logical.

To build on the user registration we have created, we want to send validation tokens. The user becomes active only after confirming the tokens. Add the following line before user.save()

user.is_active = False

We want to send plain text email alongside HTML. So, define themessage variable for the plain text and html_message for the html content. Then we use email_user() to send the email. The method is available with the User object

Enough said, the accounts_register function should look like this

from django.shortcuts import render, redirect
from django.http import HttpResponse
from django.contrib.sites.shortcuts import get_current_site
from django.utils.encoding import force_bytes, force_text
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
from django.template.loader import render_to_string
from django.conf import settings
from django.contrib.auth import login
from django.contrib.auth.models import User
from .forms import RegistrationForm
from .tokens import account_activation_token

def accounts_register(request):
    if request.method == 'POST':
        registerForm = RegistrationForm(request.POST)
        if registerForm.is_valid():
            user = registerForm.save(commit=False)
            user.email = registerForm.cleaned_data['email']
            user.set_password(registerForm.cleaned_data['password'])
            user.is_active = False
            # Save the User object
            user.save()
            # get current site
            current_site = get_current_site(request)
            subject = 'Activate your Account'
            # create Message
            message = render_to_string('accounts/account_activation_email.txt', {
                'user': user,
                'domain': current_site.domain,
                'uid': urlsafe_base64_encode(force_bytes(user.pk)).decode("utf-8"),
                'token': account_activation_token.make_token(user),
            })
            html_message = render_to_string(''accounts/account_activation_email.html', {
                'user': user,
                'domain': current_site.domain,
                'uid': urlsafe_base64_encode(force_bytes(user.pk)).decode("utf-8"),
                'token': account_activation_token.make_token(user),
            })
            # send activation link to the user
            user.email_user(subject=subject, message=message,
                            html_message=html_message)
            return HttpResponse('registered succesfully and activation sent')
    else:
        registerForm = RegistrationForm()
    return render(request, 'accounts/register.html', {'form': registerForm})

Token Generation

def activate(request, uidb64, token):
     try:
        uid = force_text(urlsafe_base64_decode(uidb64))
        user = User.objects.get(pk=uid)
    except(TypeError, ValueError, OverflowError, User.DoesNotExist):
        user = None
    if user is not None and account_activation_token.check_token(user, token):
        user.is_active = True
        user.save()
        login(request, user)
        return redirect('login')
    else:
        return HttpResponse('invalid token')

URLs and templates

in accounts.urls.py add the following view

 path('activate/<slug:uidb64>/<slug:token>)/', views.activate, name='activate'),

In accounts templates.

account_activation_invalid.html

{% autoescape off %}
Hi {{ user.username }},
Your account has successfully created. Please click below link to activate your account
http://{{ domain }}{% url 'activate uidb64=uid token=token %}
{% endautoescape %}

You can send tokens in user email after registration.The code is available on github