Creating a simple sign up view and then moving onto more advanced sign up view with profile model and confirmation mail
I have also added a location field in custom profile model and stored the actual location (city, country code) of user using API, a little javascript and python without letting user know about it. For a tutorial on using that API checkout my repo
In this tutorial we will
- Create basic sign up view
- Create sign up form with extra fields
- Create profile model for users
- Create sign up view with confirmation email
After completing the project and adding some styling from mdbootstrap my project looks like
Home Page is simply showing a navbar with working links
Login page looks like
Sign Up page
And finally dashboard page with the user name displayed...
You can reuse the code from my repo and include it in your django project. Put a star if you liked my work.
pip install -r requirements.txt -
django-admin startproject website . -
python manage.py migrate -
python manage.py createsuperuser -
python manage.py runserver -
In project
settings.pyfile import decouplefrom decouple import config SECRET_KEY = config("PROJECT_KEY")And create
.envfile like soPROJECT_KEY=93%@nka8)+fv-*ai-st1d*h)w2j2-y^)(jfiv9bogcy0u241u7
We will start with the basic sign up features that django provides by default
Simplest way to implement a Sign Up view is using UserCreationForm. This form is for those django apps which use the default user model that only contains a username and password for user sign ups.
To implement that view we need
urls.py
... from .views import signup_view urlpatterns = [ path('admin/', admin.site.urls), path('signup/', signup_view, name='sign-up'), ] In urls.py we simply import the signup_view that we haven't yet added to views.py and create a url for that view.
Now create views.py file in the website folder and put this code inside it.
from django.shortcuts import render, redirect from django.contrib.auth import authenticate, login from django.contrib.auth.forms import UserCreationForm def signup(request): if request.method == "POST": 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') else: form = UserCreationForm() return render(request, 'signup.html', {'form': form}) This is most basic signup view that django has. All of the new user creation process is done using django. We use the default UserCreationForm form to display the signup form. We authenticate the new user using the username and password that we get from the post request from the form. We then login the user and redirect it to home view. If the request method is not POST we simply show the empty form in templates/signup.html file.
Create a templates folder in root path (where manage.py file lies). In that folder create signup.html file.
signup.html
{% extends 'base.html' %} {% block content %} <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="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> {% endblock %} This is a little different way of rendering form. There are more ways like
{{ form.as_p }} {{ form.as_table }} {{ form.as_ul }} So far we have been using the default fields that UserCreationForm provides us. But what if we wanted the email address of the new user which is the important part aside from the username and password.
For that we can inherit a new form class from UserCreationForm. Create a new file named forms.py in the website folder. All of this should go in a separate app so you can start another app. Let's just do that as our project is increasing in size.
python manage.py startapp users Include app in project settings
... INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'users', ] Move the file website/views.py to users folder and replace it. These are some of the changes which you would have to do a lot of times if you are working on a large project in any language.
Since we moved the views.py file, all our imports in urls.py will give errors. Let's also create a urls.py file in users folder.
From the website/urls.py file change this piece of code into
from .views import signup_view, dashboard_view, home_view urlpatterns = [ path('admin/', admin.site.urls), path('signup/', signup_view, name='sign-up'), path('dashboard/', dashboard_view, name='dashboard'), path('', home_view, name='home'), ] replace with below code
from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('', include('users.urls', namespace='users')), ] Now we will create a custom user registration form so create a new file forms.py in users folder.
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=10, min_length=4, required=True, widget=forms.TextInput( attrs={ "placeholder": "First Name", "class": "form-control" } ) ) last_name = forms.CharField( max_length=30, required=True, widget=forms.TextInput( attrs={ "placeholder": "Last Name", "class": "form-control" } ) ) email = forms.EmailField( max_length=254, widget=forms.EmailInput( attrs={ "placeholder": "Email", "class": "form-control" } ) ) class Meta: model = User fields = ('username', 'first_name', 'last_name', 'email', 'password1', 'password2',) You can do the same with password fields by using PasswordInput()
... password1 = forms.CharField( label='', max_length=30, min_length=8, required=True, widget=forms.PasswordInput( attrs={ "placeholder": "Password", "class": "form-control" } ) ) password2 = forms.CharField( label='', max_length=30, min_length=8, required=True, widget=forms.PasswordInput( attrs={ "placeholder": "Confirm Password", "class": "form-control" } ) ) This would give the forms a nice look as well as placeholders and the rest of the django password validation remains intact and active.
So far we have been using the User model from django.contrib.auth.models that meets almost all needs but Django docs itself recommends using a custom model for users instead of the User so in this section we will be making our own custom model for users and name it Profile
For this part we will start another app name profiles
python manage.py startapp profiles Add profiles app in settings
Inside profiles/models.py add
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 In this particular case, the profile is created using a Signal. It’s not mandatory, but usually it is a good way to implement it.
class Profile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) bio = models.CharField(max_length=50, blank=True) location = models.CharField(max_length=30, blank=True) @receiver(post_save, sender=User) def update_user_profile(sender, instance, created, **kwargs): if created: Profile.objects.create(user=instance) instance.profile.save() This is our custom model, ofcourse you can go far more further adding in birth date, and profile image and lots more stuff, but for simplicity we are just using three fields.
Now we need to create a form so forms.py file should have
from django import forms from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.models import User class SignUpForm(UserCreationForm): birth_date = forms.DateField(help_text='Required. Format: YYYY-MM-DD') class Meta: model = User fields = ('username', 'birth_date', 'password1', 'password2', ) You can add in attributes in the fields again like we did earlier.
There are a few changes that views.py file should have
from django.contrib.auth import login, authenticate from django.shortcuts import render, redirect from mysite.core.forms import SignUpForm def signup(request): if request.method == 'POST': form = SignUpForm(request.POST) if form.is_valid(): user = form.save() user.refresh_from_db() # load the profile instance created by the signal user.profile.birth_date = form.cleaned_data.get('birth_date') user.save() raw_password = form.cleaned_data.get('password1') user = authenticate(username=user.username, password=raw_password) login(request, user) return redirect('home') else: form = SignUpForm() return render(request, 'signup.html', {'form': form}) Because of the Signal handling the Profile creation, we have a synchronism issue here. It is easily solved by calling the user.refresh_from_db() method. This will cause a hard refresh from the database, which will retrieve the profile instance.
If you don’t call user.refresh_from_db(), when you try to access the user.profile, it will return None.
After refreshing it user model, set the cleaned data to the fields that matter, and save the user model. The user save will trigger the profile save as well, that’s why you don’t need to call user.profile.save(), instead you call just user.save().
You can display the user details using
<h1 class="mt-5 text-center">Welcome {{ request.user }}</h1> <p class="text-left mt-5">Bio: {{ user.profile.bio }}</p> <p class="text-left">Location: {{ user.profile.location }}</p> <p class="text-left">Joined: {{ user.profile.timestamp }}</p> For customizing the forms you can use django-widget-tweaks
Django provides built-in system for sending emails. But first of test purposes we will be using Console Backend for emails.
Add this settings in settings.py file
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' Now for checking if a user is authenticated we will create a field in the profile model to determine if the user is confirmed or not.
profiles/models.py
class Profile(models.Model): ... email_confirmed = models.BooleanField(default=False) And for creating a one time link using django we will create a new file 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.email_confirmed) ) account_activation_token = AccountActivationTokenGenerator() We use the pk from the user timestamp and the email_confirmed field to create a token. We basically extended the PasswordResetTokenGenerator to create a unique token generator to confirm email addresses. This make use of your project’s SECRET_KEY, so it is a pretty safe and reliable method.
Now we need to define views for account activation as well as account activation sent view
def account_activation_sent_view(request): return render(request, 'registration/account_activation_sent.html') def account_activate(request, uidb64, token): try: uid = urlsafe_base64_decode(uidb64).decode() print(uid) user = User.objects.get(pk=uid) except (TypeError, ValueError, OverflowError, User.DoesNotExist) as e: print(e) user = None if user is not None and account_activation_token.check_token(user, token): user.is_active = True user.profile.email_confirmed = True user.save() login(request, user) return redirect('users:dashboard') else: return render(request, 'registration/account_activation_invalid.html') account_activation_sent_view is justfor redirecting users if their account activation url is wrong. The template registration/account_activation_sent.html will be
{% extends "base.html" %} {% block title %} {{ block.super }} - Check Your Email Account {% endblock %} {% block content %} <h3 class="text-center">Check your email account for verifying your django account.</h3> {% endblock %} account_activate function simply fetches the uidb64 and token from the url and uses the .check_token function from AccountActivationTokenGenerator class which takes the user and token.
You can remove the print statements from the code, I use them for testing purposes while writing my code.
profiles/views.py
User = get_user_model() def signup(request): if request.method == 'POST': form = SignUpForm(request.POST) if form.is_valid(): user = form.save(commit=False) user.is_active = False user.save() user = form.save() current_site = get_current_site(request) subject = "Activate your Django Serives Account" message = render_to_string('registration/account_activation_email.html', { 'user': user, 'domain': current_site.domain, 'uid': urlsafe_base64_encode(force_bytes(user.pk)).decode(), 'token': account_activation_token.make_token(user) }) user.email_user(subject, message) return redirect('profiles:account-activation-sent') else: form = SignUpForm() return render(request, 'app/signup.html', { 'form': form, 'profile': True }) This is the code for sending email to the user email, notice we have removed user.refresh_from_db() since we are using form.save(commit=False) so you can use user.refresh_from_db() after saving the form
... if form.is_valid(): user = form.save(commit=False) user.is_active = False user.save() user = form.save() user.refresh_from_db() # your code here user.save() # call save again ... This is it. We can extend our model by using the smtp as email backend that will actually send email to the email provided by the user but it requires some more settings to be defined in the settings.py file. For more details visit docs