Create Advanced User Sign Up View in Django | Step-by-Step
Tags: python, django, beginners, career
Published: 2019-09-22 20:10:50

This post cross-published with [OnePublish](https://www.onepubli.sh)

<p>Hello DEV Network!</p>
<p>In this tutorial we will cover advanced user registration view with Django.</p>
<p>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.</p>

<p>I am assuming that you already created your Django project.</p>

<h4>Basic Registration</h4>
<p>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).</p>
<p>In your <b>views.py</b>: </p>
```python
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})
```
<p><i>cleaned_data</i> is holding the validated form data and <i>authenticate()</i> 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.</p>
<p>Once user is verified, <i>login()</i> method takes an HttpRequest object and a User object and saves the user’s ID in the session, using Django’s session framework. Finally, <i>redirect()</i> method is basically redirecting the logged in user to home URL.</p>
<b>urls.py</b>
```python
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")
]

```

<p>Last step for basic registration, in your <b>signup.html</b> template:</p>
```html
<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>
```
<p>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.</p>

<img src="https://thepracticaldev.s3.amazonaws.com/i/a8raq15wolv3phzxdqok.png" alt="sign up with Django" />

<h4>Registration with extra fields</h4>
<p>What if we want register user with email or username? So, create <b>forms.py</b> and extend the <i>UserCreationForm</i>.</p>

```python
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', )
```
<p>Then , just change the <i>UserCreationForm</i> to <i>SignUpForm</i> in <b>views.py</b>: </p>

```python
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})
```
<p>That's it! Now, user can register by his/her username and email as well.</p>

<h4>Registration with Profile Model</h4>
<p>In my view, this is the best and recommended way to implement registration system in your Django application. We are going to use <b>Django Signals</b> to create a user profile right after user registered. So, take a look <b>models.py</b></p>

```python
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()
```
<p>With the <i>@receiver</i> decorator, we can link a signal with a function. So, every time that a User model instance ends to run its <i>save()</i> method (or when user register ends), the <i>update_profile_signal</i> will start to work right after user saved.</p>

<ul>
<li><b>sender</b> - The model class.</li>
<li><b>instance</b> - The actual instance being saved.</li>
<li><b>created</b> - A boolean; True if a new record was created.</li>
</ul>

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

```python
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',)
```
<p>and in <b>views.py</b>: </p>

```python
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})
```
<p>We are using <i>refresh_from_db()</i> 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.</p>

<h4>Registration with Confirmation Mail</h4>
<p>At this stage, we are going to configure email backend to send confirmation links. Let's test it on console for this tutorial.</p>
<p>Add the following line to your <b>settings.py</b>: </p>
```python
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
```
<p>Then, update <b>models.py</b> by adding confirmation check field.</p>
```python
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()
```
<p>Now, let's create a new module named <b>tokens.py</b></p>

```python
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()
```
<p><i>PasswordResetTokenGenerator</i> 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.</p>

<p>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 .</p>

<p>Here is the <b>forms.py</b>: </p>
```python
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', )
```
<p> and in <b>views.py</b> we will no longer authenticate the user, instead we will send activation link.</p>
<b>views.py</b>
```python
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})
```
<p>So basically we are generating activation url with user related data and sending it. Note that, we are setting <I>user.is_active = False</i> that means user can't login until he/she confirmed the registration. </p>
<p>Now, create <b>activation_request.html</b> template which will ask from user to confirm the link (This will show up in your console).</p>

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

Please click the following link to confirm your registration:

http://{{ domain }}{% url 'activate' uidb64=uid token=token %}
{% endautoescape %}
```
<p>Check your console:</p>

<img src="https://thepracticaldev.s3.amazonaws.com/i/f0zlo8tw0un5v2b9nx6d.png" alt="confirmation-link" />
<p>Create one more template named <b>activation_sent.html</b>. This will inform user that to check his/her mail to confirm registration</p>
```html
<h3>Activation link sent! Please check your console or mail.</h3>
```

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

```python
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')
```
<p>Once registration confirmed, user becomes active and be able to login.</p>
<p>Full code of <b>views.py</b>: </p>

```python
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})
```
<p>Finally, let's configure our <b>urls.py</b>: </p>

```python
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'),
]
```
<p>I pushed all these projects in my GitHub you can clone which project you want by selecting right branch.</p>
<img src="https://thepracticaldev.s3.amazonaws.com/i/rm9y8zon2tympq8veqws.png" alt="github" />
{% github raszidzie/django-advanced-signup-tutorial %}
<p>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 check <a href="https://reversepython.net">Reverse Python</a> for more articles like this.</p>

<a href="https://www.instagram.com/coderasha/?hl=en">Instagram</a>
<a href="https://twitter.com/coderasha?lang=en">Twitter</a>