Hello and welcome to the second part of this tutorial series, I am really excited to continue with this tutorial series. In part one, we were able to setup our django project and we are going to continue from there. Here is the link to part one of this tutorial series.
Creating Django Application
We are going to create a user_account app which we will use to sign up a new user. From my project root directory(where manage.py is located) I am going to run the following command:
python manage.py startapp user_account
The above command will create a basic structure of user account app which looks like this:
The next step is to activate our user_account application. Inside the mysite folder, open settings.py file, in order for django to keep track of our application, we are going to add user_account to the INSTALLED_APPS settings. Your settings.py file should have the following code:
""" Django settings for mysite project. Generated by 'django-admin startproject' using Django 2.1.2. For more information on this file, see https://docs.djangoproject.com/en/2.1/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/2.1/ref/settings/ """ import os # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = '(g8(5_^x)+y7aul=g@=nyn@m-_fky&hvvddyjt%!$_co0a1=h^' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ 'user_account.apps.UserAccountConfig', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'mysite.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] WSGI_APPLICATION = 'mysite.wsgi.application' # Database # https://docs.djangoproject.com/en/2.1/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } } # Password validation # https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] # Internationalization # https://docs.djangoproject.com/en/2.1/topics/i18n/ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/2.1/howto/static-files/ STATIC_URL = '/static/' EMAIL_USE_TLS = True EMAIL_HOST = 'smtp.gmail.com' EMAIL_HOST_USER = 'example@gmail.com' EMAIL_HOST_PASSWORD = 'password' EMAIL_PORT = 587
on line 34: I have added our user_account to the installed apps.
Now django knows our application is active.
The next step is to apply migrations, django comes with a migration system to track your models and propagate them to the database. I am going to open my terminal and run the following command:
python manage.py migrate
You should see the following output:
We have just created migrations for the installed apps.
Since we are going to send emails to our new users, I am going to add email host to the settings.py file. Open the settings.py file, at the bottom of settings.py file, add the following code:
EMAIL_USE_TLS = True EMAIL_HOST = 'smtp.gmail.com' EMAIL_HOST_USER = 'example@gmail.com' EMAIL_HOST_PASSWORD = 'password' EMAIL_PORT = 587
In my case, I am using gmail but you can use other smtp servers available. If you are just testing, you can use Mailtrap fake smtp testing server.
Using Django Forms
We are going to use UserCreationForm that comes with django, this form is tied to User model that django comes with by default. In our user_account folder we are going to create a file called forms.py, make sure that forms.py file has the following code:
from django import forms from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.models import User class UserSignUpForm(UserCreationForm): email = forms.EmailField(max_length=100, help_text='Required') class Meta: model = User fields = ('username', 'email', 'password1', 'password2')
Let’s understand the code in forms.py file:
-
Line 1 – We import forms from django.
-
Line 2 – We import UserCreationForm from django auth.
-
Line 3 – We import user model from django auth models.
-
Line 6 – We create a UserSignUpForm class where we pass an instance of UserCreationForm.
-
Line 7 – We create form field called email with a helper text that is required.
-
Line 9 – We create a Meta class for our form.
-
Line 10 – We define which model that our form is going to use.
-
Line 11 – We define the fields that the user is going to see.
Unique Token Generator Class
When a user is created during sign up process, we need to create a unique token so that we can verify the new user when confirming that they control the email used during signup process. Inside our user_account folder, we are going to create a new file called token_generator.py file, make sure that token_generator.py file has the following code:
from django.contrib.auth.tokens import PasswordResetTokenGenerator from django.utils import six class TokenGenerator(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 = TokenGenerator()
Let’s understand the code:
-
Line 1 – we import django password token generator. This class generate a token without persisting it to the database, yet it’s still able to determine whether the token is valid or not.
-
Line 2 – we import six from django utils. Six provides simple utilities for wrapping over differences between Python 2 and Python 3.
-
Line 5 – we create TokenGenerator class an pass an Instance of PasswordTokenGenerator.
-
Line 6 – we create a method _make_hash_value which overrides PasswordTokenGenerator method.
-
Line 7 – we return user id, timestamp and user is active using the six imported from django utils.
-
Line 12 – we create variable and we equate it to our token generator class.
Sign up Logic
Inside our user_account folder, open views.py file and make sure it has the following code:
from django.http import HttpResponse from django.shortcuts import render, redirect from django.contrib.auth import login, authenticate from .forms import UserSignUpForm 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 .token_generator import account_activation_token from django.contrib.auth.models import User from django.core.mail import EmailMessage def usersignup(request): if request.method == 'POST': form = UserSignUpForm(request.POST) if form.is_valid(): user = form.save(commit=False) user.is_active = False user.save() current_site = get_current_site(request) email_subject = 'Activate Your Account' message = render_to_string('activate_account.html', { 'user': user, 'domain': current_site.domain, 'uid': urlsafe_base64_encode(force_bytes(user.pk)).decode(), 'token': account_activation_token.make_token(user), }) to_email = form.cleaned_data.get('email') email = EmailMessage(email_subject, message, to=[to_email]) email.send() return HttpResponse('We have sent you an email, please confirm your email address to complete registration') else: form = UserSignUpForm() return render(request, 'signup.html', {'form': form})
Let’s understand the code:
-
From line 1 to 11 – we import the classes that we are going to use create our logic. This include our signup form and token generator.
-
Line 14 – we create our signup method.
-
Line 19 – we make the user in active so that they cannot login to our application.
-
Line 21 – we get the current site from the request.
-
Line 22 – we create the subject of our email.
-
Line 23 – we create a message of our email. One thing to note is that we are using a activate_account.html that we will create.
-
Line 24 to 27 – we pass the content to our template, this include token, user, domain, an uid.
-
Line 29 – we get the user email from our form.
-
Line 30 – we create email instance and pass subject, message and who we are sending email to.
-
Line 31 – we send the email.
-
Line 32 – we return a http response asking the user to complete registration by checking their email.
-
Line 35 – if the request was not a post, we return the form user to fill.
The next step is to create templates. Inside our user_account folder, create a folder called templates. Inside the templates folder, create activate_account.html and make sure it has the following code:
{% autoescape off %} Hi {{ user.username }}, Please click on the link to confirm your registration, http://{{ domain }}{% url 'activate' uidb64=uid token=token %} {% endautoescape %}
In this file we just passing the user name and url the user will click to activate their account. Inside the templates folder create another file called signup.html and make sure it has the following code:
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>User sign up</title> </head> <body> <h2>Sign up</h2> <form method="post"> {% csrf_token %} {% for field in form %} <p> {{ field.label_tag }}<br> {{ field }} {% if field.help_text %} <small style="display: none">{{ 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> </body> </html>
In this file, we have just created a form which we will use to create users. The next step is to create urls. Inside the user_account folder, create urls.py file and make sure it has the following code:
from django.conf.urls import url from . import views urlpatterns = [ url(r'^$', views.usersignup, name='register_user'), ]
In this file we define the url pattern to load our signup page. In the main urls.py file located inside mysite, make sure it has the following code:
from django.contrib import admin from django.urls import path, include urlpatterns = [ path('', include('user_account.urls')), path('admin/', admin.site.urls), ]
In line 5, we include urls from user_account app. That was a lot of work, now lets test our code so far. On you terminal, run the following command to run development server:
python manage.py runserver
Navigate to http://127.0.0.1:8000/ and you should see the following output:
Great work, well the form is not pretty but you can always use frontend framework like bootstrap to improve the look and feel of the sign up form. In our case we are interested with the logic for the moment. Before we can test our form, we need to create one more method to activate our account. Open views.py file and make sure it has the following code:
from django.http import HttpResponse from django.shortcuts import render, redirect from django.contrib.auth import login, authenticate from .forms import UserSignUpForm 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 .token_generator import account_activation_token from django.contrib.auth.models import User from django.core.mail import EmailMessage def usersignup(request): if request.method == 'POST': form = UserSignUpForm(request.POST) if form.is_valid(): user = form.save(commit=False) user.is_active = False user.save() current_site = get_current_site(request) email_subject = 'Activate Your Account' message = render_to_string('activate_account.html', { 'user': user, 'domain': current_site.domain, 'uid': urlsafe_base64_encode(force_bytes(user.pk)).decode(), 'token': account_activation_token.make_token(user), }) to_email = form.cleaned_data.get('email') email = EmailMessage(email_subject, message, to=[to_email]) email.send() return HttpResponse('We have sent you an email, please confirm your email address to complete registration') else: form = UserSignUpForm() return render(request, 'signup.html', {'form': form}) def activate_account(request, uidb64, token): try: uid = force_bytes(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 HttpResponse('Your account has been activate successfully') else: return HttpResponse('Activation link is invalid!')
From line 38 to 50, we create a method to activate user account, in this method we pass uidb64 and token which we will get from the url. Line 41, we get the use from the database using the uid define in line 40. In line 42, if there is an error or user does not exist in our database we return none in line 43. In line 44, if the user exist and the token is valid, we set user to active in line 45, and save the record in line 46. In line 47, we login the user and in line 48, we return success message. In line 50, if the token is invalid, we notify the user that the link is not valid.
Now, in user_account folder, open urls.py file and make sure it has the following code:
from django.conf.urls import url from . import views urlpatterns = [ url(r'^$', views.usersignup, name='register_user'), url(r'^activate/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', views.activate_account, name='activate'), ]
In line 7, we define the url pattern for activating user account. This url has token and uidb64 as the parameters. Make sure your development server is running and head back to the browser. Try to sign up a new user. After signing up a new user, you get the following output on your browser:
Now let’s check our email that we used during sign up and see if we have an email. After checking mail, I see the following output:
The email tell the user to click the link and confirm registration, after click the link. Here is the output:
Awesome, we are able to sign up a new user, send confirmation email and activate their account successfully.
Goal Achieved In This Lesson
-
Ability to sign up new user.
-
Ability to generate token.
-
Ability to send activation email.
-
Ability to activate and login user.
With that we conclude this lesson, to get the code. Visit Email verification upon sign up in Django 2.1 and Python 3.6
Facebook Comments