Create Advanced User Sign Up View in Django | Step-by-Step

Create Advanced User Sign Up View in Django | Step-by-Step

Hey Devs! In this tutorial we will cover advanced user registration view with Django.

First let's build basic sign up view then we are going to add some extra fields and confirmation mail function to make it advanced.

I am assuming that you already created your Django project.

Basic Registration

Django authentication framework provides a form named UserCreationForm (which inherits from ModelForm class) to handle the creation of new users. It has three fields namely username, password1 and password2 (for password confirmation).

In your views.py:

from django.shortcuts import render
from django.contrib.auth import login, authenticate
from django.contrib.auth.forms import UserCreationForm
from django.shortcuts import render, redirect

def home_view(request):
    return render(request, 'home.html')

def signup_view(request):
    form = UserCreationForm(request.POST)
    if form.is_valid():
        form.save()
        username = form.cleaned_data.get('username')
        password = form.cleaned_data.get('password1')
        user = authenticate(username=username, password=password)
        login(request, user)
        return redirect('home')
    return render(request, 'signup.html', {'form': form})

cleaned_data is holding the validated form data and authenticate() method takes credentials as keyword arguments, username and password for the default case, checks them against each authentication backend, and returns a User object if the credentials are valid for a backend.

Once user is verified, login() method takes an HttpRequest object and a User object and saves the user’s ID in the session, using Django’s session framework. Finally, redirect() method is basically redirecting the logged in user to home URL.

urls.py
from django.contrib import admin
from django.urls import path
from accounts.views import home_view, signup_view

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', home_view, name="home"),
    path('signup/', signup_view, name="signup")
]

Last step for basic registration, in your signup.html template:

<h2>Sign up Form</h2>
<form method="post">
  {% csrf_token %}
  {% for field in form %}
    <p>
      {{ field.label_tag }}<br>
      {{ field }}
      {% if field.help_text %}
        <small style="color: grey">{{ field.help_text }}</small>
      {% endif %}
      {% for error in field.errors %}
        <p style="color: red">{{ error }}</p>
      {% endfor %}
    </p>
  {% endfor %}
  <button type="submit">Sign up</button>
</form>

We are displaying each field in our form with help texts to avoid errors. If any errors occurs while registration it is nice to display user what causes these errors.

sign up with Django

Registration with extra fields

What if we want register user with email or username? So, create forms.py and extend the UserCreationForm.

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


class SignUpForm(UserCreationForm):
    username = forms.CharField(max_length=30)
    email = forms.EmailField(max_length=200)

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

Then , just change the UserCreationForm to SignUpForm in views.py:

from django.shortcuts import render
from django.contrib.auth import login, authenticate
from .forms import SignUpForm
from django.shortcuts import render, redirect

def home_view(request):
    return render(request, 'home.html')

def signup_view(request):
    form = SignUpForm(request.POST)
    if form.is_valid():
        form.save()
        username = form.cleaned_data.get('username')
        password = form.cleaned_data.get('password1')
        user = authenticate(username=username, password=password)
        login(request, user)
        return redirect('home')
    else:
        form = SignUpForm()
    return render(request, 'signup.html', {'form': form})

That's it! Now, user can register by his/her username and email as well.

Registration with Profile Model

In my view, this is the best and recommended way to implement registration system in your Django application. We are going to use Django Signals to create a user profile right after user registered. So, take a look models.py

from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    first_name = models.CharField(max_length=100, blank=True)
    last_name = models.CharField(max_length=100, blank=True)
    email = models.EmailField(max_length=150)
    bio = models.TextField()

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

@receiver(post_save, sender=User)
def update_profile_signal(sender, instance, created, **kwargs):
    if created:
        Profile.objects.create(user=instance)
    instance.profile.save()

With the @receiver decorator, we can link a signal with a function. So, every time that a User model instance ends to run its save() method (or when user register ends), the update_profile_signal will start to work right after user saved.

  • sender - The model class.
  • instance - The actual instance being saved.
  • created - A boolean; True if a new record was created.

Well, we want from user to enter his/her information into extra fields above to complete registration. Let's update forms.py :

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

class SignUpForm(UserCreationForm):
    first_name = forms.CharField(max_length=100, help_text='Last Name')
    last_name = forms.CharField(max_length=100, help_text='Last Name')
    email = forms.EmailField(max_length=150, help_text='Email')


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

and in views.py:

from django.shortcuts import render
from django.contrib.auth import login, authenticate
from .forms import SignUpForm
from django.shortcuts import render, redirect

def home_view(request):
    return render(request, 'home.html')

def signup_view(request):
    form = SignUpForm(request.POST)
    if form.is_valid():
        user = form.save()
        user.refresh_from_db()
        user.profile.first_name = form.cleaned_data.get('first_name')
        user.profile.last_name = form.cleaned_data.get('last_name')
        user.profile.email = form.cleaned_data.get('email')
        user.save()
        username = form.cleaned_data.get('username')
        password = form.cleaned_data.get('password1')
        user = authenticate(username=username, password=password)
        login(request, user)
        return redirect('home')
    else:
        form = SignUpForm()
    return render(request, 'signup.html', {'form': form})

We are using refresh_from_db() method to handle synchronism issue, basically reloading the database after the signal, so by this method our profile instance will load. Once profile instance loaded, set the cleaned data to the fields and save the user model.

Registration with Confirmation Mail

At this stage, we are going to configure email backend to send confirmation links. Let's test it on console for this tutorial.

Add the following line to your settings.py:

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

Then, update models.py by adding confirmation check field.

from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    first_name = models.CharField(max_length=100, blank=True)
    last_name = models.CharField(max_length=100, blank=True)
    email = models.EmailField(max_length=150)
    signup_confirmation = models.BooleanField(default=False)

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

@receiver(post_save, sender=User)
def update_profile_signal(sender, instance, created, **kwargs):
    if created:
        Profile.objects.create(user=instance)
    instance.profile.save()

Now, let's create a new module named tokens.py

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.profile.signup_confirmation)
        )

account_activation_token = AccountActivationTokenGenerator()

PasswordResetTokenGenerator is generating a token without persisting it in the database so, we extended it to create a unique token generator to confirm registration or email address. This make use of your project’s SECRET_KEY, so it is a secure and reliable method.

Once user clicked the link, it will no longer be valid.The default value for the PASSWORD_RESET_TIMEOUT_DAYS is 7 days but you can change its value in the settings.py .

Here is the forms.py:

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

class SignUpForm(UserCreationForm):
    first_name = forms.CharField(max_length=100, help_text='Last Name')
    last_name = forms.CharField(max_length=100, help_text='Last Name')
    email = forms.EmailField(max_length=150, help_text='Email')


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

and in views.py we will no longer authenticate the user, instead we will send activation link.

views.py
def signup_view(request):
    if request.method  == 'POST':
        form = SignUpForm(request.POST)
        if form.is_valid():
            user = form.save()
            user.refresh_from_db()
            user.profile.first_name = form.cleaned_data.get('first_name')
            user.profile.last_name = form.cleaned_data.get('last_name')
            user.profile.email = form.cleaned_data.get('email')
            # user can't login until link confirmed
            user.is_active = False
            user.save()
            current_site = get_current_site(request)
            subject = 'Please Activate Your Account'
            # load a template like get_template() 
            # and calls its render() method immediately.
            message = render_to_string('activation_request.html', {
                'user': user,
                'domain': current_site.domain,
                'uid': urlsafe_base64_encode(force_bytes(user.pk)),
                # method will generate a hash value with user related data
                'token': account_activation_token.make_token(user),
            })
            user.email_user(subject, message)
            return redirect('activation_sent')
    else:
        form = SignUpForm()
    return render(request, 'signup.html', {'form': form})

So basically we are generating activation url with user related data and sending it. Note that, we are setting user.is_active = False that means user can't login until he/she confirmed the registration.

Now, create activation_request.html template which will ask from user to confirm the link (This will show up in your console).

{% autoescape off %}
Hi {{ user.username }},

Please click the following link to confirm your registration:

http://{{ domain }}{% url 'activate' uidb64=uid token=token %}
{% endautoescape %}

Check your console:

confirmation-link

Create one more template named activation_sent.html. This will inform user that to check his/her mail to confirm registration

<h3>Activation link sent! Please check your console or mail.</h3>

When user clicked the link, we have to check if the user exists and the token is valid.

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
    # checking if the user exists, if the token is valid.
    if user is not None and account_activation_token.check_token(user, token):
        # if valid set active true 
        user.is_active = True
        # set signup_confirmation true
        user.profile.signup_confirmation = True
        user.save()
        login(request, user)
        return redirect('home')
    else:
        return render(request, 'activation_invalid.html')

Once registration confirmed, user becomes active and be able to login.

Full code of views.py:

from django.contrib.auth import login, authenticate
from django.shortcuts import render, redirect, get_object_or_404, HttpResponseRedirect
from django.contrib.sites.shortcuts import get_current_site
from django.utils.encoding import force_text
from django.contrib.auth.models import User
from django.db import IntegrityError
from django.utils.http import urlsafe_base64_decode
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode
from .tokens import account_activation_token
from django.template.loader import render_to_string

from .forms import SignUpForm
from .tokens import account_activation_token

def home_view(request):
    return render(request, 'home.html')

def activation_sent_view(request):
    return render(request, 'activation_sent.html')


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
    # checking if the user exists, if the token is valid.
    if user is not None and account_activation_token.check_token(user, token):
        # if valid set active true 
        user.is_active = True
        # set signup_confirmation true
        user.profile.signup_confirmation = True
        user.save()
        login(request, user)
        return redirect('home')
    else:
        return render(request, 'activation_invalid.html')

def signup_view(request):
    if request.method  == 'POST':
        form = SignUpForm(request.POST)
        if form.is_valid():
            user = form.save()
            user.refresh_from_db()
            user.profile.first_name = form.cleaned_data.get('first_name')
            user.profile.last_name = form.cleaned_data.get('last_name')
            user.profile.email = form.cleaned_data.get('email')
            # user can't login until link confirmed
            user.is_active = False
            user.save()
            current_site = get_current_site(request)
            subject = 'Please Activate Your Account'
            # load a template like get_template() 
            # and calls its render() method immediately.
            message = render_to_string('activation_request.html', {
                'user': user,
                'domain': current_site.domain,
                'uid': urlsafe_base64_encode(force_bytes(user.pk)),
                # method will generate a hash value with user related data
                'token': account_activation_token.make_token(user),
            })
            user.email_user(subject, message)
            return redirect('activation_sent')
    else:
        form = SignUpForm()
    return render(request, 'signup.html', {'form': form})

Finally, let's configure our urls.py:

from django.contrib import admin
from django.urls import path
from django.conf.urls import url
from accounts.views import home_view, signup_view, activation_sent_view, activate

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', home_view, name="home"),
    path('signup/', signup_view, name="signup"),
    path('sent/', activation_sent_view, name="activation_sent"),
    path('activate/<slug:uidb64>/<slug:token>/', activate, name='activate'),
]

I pushed all these projects in my GitHub you can clone which project you want by selecting right branch.

github

Great! In this tutorial we used console to check our link but you can configure a production quality email service to send actual confirmation mail. My next tutorial will cover this topic so make sure you are following me on social media and stay connected for more articles like this.

Support the lab

Buy me a coffeeBuy me a coffee

Instagram

Twitter