This post cross-published with OnePublish
Hello DEV Network!
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.
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:
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.
thepylot / django-advanced-signup-tutorial
Create Advanced User Sign Up View in Django | Step-by-Step
django-advanced-signup-tutorial
Create Advanced User Sign Up View in Django | Step-by-Step
Getting Started
This tutorial works on Python 3+ and Django 2+.
Clone the project by selecting right branch and run following commands:
python3 manage.py makemigrations accounts
python3 manage.py migrate
python3 manage.py runserver
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 Reverse Python for more articles like this.
Top comments (36)
Hello coderasha,
I have tried most of the websites where I could look for an answer, but I didn't get any. Since I am new to web development, so not able to debug the issue properly. I have scratched every part of my head with no luck. I hope you would help me.
My website workflow is as below:
1) user sign up and redirect to login screen once registration successful.
2) user login and now he/she sees a form, where he/she needs to fill in mandatory fields
3) save and then either click on "profile" link(in the navbar) to see the profile data, or "save" button will redirect to the profile page automatically.
what's working:
What's not working:
The profile page that is getting generated for logged in user has the data showing up for default User model, i.e(first_name, last_name, and email). However, the other information like (dob, photo, country, state) etc, is not getting populated on the profile page.
What I have tested already.
I learned that "request.user.model_name" will help to populate the information. So, for that I have to create signals. I have done that as well, but no luck.
Errors:
RelatedObjectDoesNotExist at /profile/
User has no userprofile.
Request Method: GET
Request URL: 127.0.0.1:8000/profile/
Django Version: 3.0.5
Exception Type: RelatedObjectDoesNotExist
Exception Value:
User has no userprofile.
Exception Location: C:\Python38\lib\site-packages\django\db\models\fields\related_descriptors.py in get, line 420
Python Executable: C:\Python38\python.exe
Python Version: 3.8.2
Python Path:
['C:\Users\anshu\djago-project\SkoolSkill',
'C:\Python38\python38.zip',
'C:\Python38\DLLs',
'C:\Python38\lib',
'C:\Python38',
'C:\Python38\lib\site-packages']
Server time: Sun, 3 May 2020 15:48:15 +0000
So, to test further I used "request.user"(at this point signal or no signal, the data comes for those 3 fields). So, as said, only few fields populate the data from DB, but the custom fields doesn't
Below are my django files details:
views.py
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from .forms import SignUpForm, UserProfileForm
from django.views.decorators.csrf import csrf_exempt
def login_user(request):
if request.method == 'POST':
username = request.POST['username']
password = request.POST['password']
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
messages.success(request, f'login success')
return redirect('bolo')
else:
messages.error(request, f'error while login, please try again')
return redirect('login')
else:
return render(request, 'authenticate\login.html', {})
def userprofileview(request): # Authenticated user filling the form to complete the registration
if request.method == 'POST':
form = UserProfileForm(request.POST, request.FILES)
if form.is_valid():
form.save()
messages.success(request, f'Profile has been updated successfully')
return redirect('/profile')
else:
messages.error(request, AssertionError)
else:
form = UserProfileForm()
return render(request, 'authenticate\bolo.html', context={'form': form})
@login_required
def profile_page(request): # Fetching data from DB to show user's complete profile page
form = UserProfileForm()
data = request.user.userprofile
context = {'form': form, 'data': data}
return render(request, 'authenticate\profile.html', context)
urls.py
urlpatterns = [
path('', home, name='home'),
path('contact/', home, name='contact'),
path("login/", login_user, name='login'),
path("logout/", logout_user, name='logout'),
path("register/", register_user, name='register'),
path("bolo/", userprofileview, name='bolo'),
path('profile/', profile_page, name='profile'),
]
models.py
from django.db import models
from django.urls import reverse
from django.contrib.auth.models import User
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, null=True)
Photo = models.FileField(upload_to='documents/%Y/%m/%d/', null=True)
uploaded_at = models.DateTimeField(auto_now_add=True, null=True)
dob = models.DateField(max_length=20, null=True)
country = models.CharField(max_length=100, null=True)
State = models.CharField(max_length=100, null=True)
District = models.CharField(max_length=100, null=True)
phone = models.CharField(max_length=10, null=True)
profile.html
signals.py
from django.db.models.signals import post_save
from django.contrib.auth.models import User
from django.dispatch import receiver
from .models import UserProfile
+
@receiver(post_save, sender=User)
def create_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
@receiver(post_save, sender=User)
def save_profile(sender, instance, **kwargs):
instance.userprofile.save()
apps.py
from django.apps import AppConfig
class AuthenticationConfig(AppConfig):
name = 'AUTHENTICATION'
I will be very grateful to you if you could help me out.
Thank you
Hey! Sorry for late answer. It seems your queryset is not correct. Can you please change
to
and finally, update your profile.html:
Hi,
Thank you for your reply. I followed your instructions, but now no data is getting populated at all. Just the field names are showing up. I have attached the screenshot.
Please suggest
Regards,
Amitesh
Try this one:
Btw I can't see any screenshots but I think it should work now
Not working, and the strangest part is that the registration, and login is getting successful. The data is going to the database for User model, but now the data has stopped going for the "UserProfile" model to the DB. Before, though the data was not showing up on the web page but the data was going the DB. Below is the ORM query output
I really appreciate your effort, and hope we rectify the issue soon. If you are comfortable, I can share my project as a compressed file with you if you would like to test in your environment.
Below is what I see on the web page:
email: email@gmail.com
fname: sam
lname: Sahay
DOB:
Country:
State:
District:
As you could see, first three fields are populating data, but rest not.
Amitesh
Please disregard the previous reply. My apologies. The data is showing up in the database for User model as well as the UserProfile model. I will keep you posted over the latest suggestion by you. There is some issues with the routes . I need to fix that before I see the profile page.
Thank you again.
Amitesh
Hi,
Ok, so there has been some progress. I followed you instructions as well as I modified the function that is responsible to let the authenticated users fill in the form. Below are the two functions that is now modified.
def userprofileview(request): # Authenticated user filling the form to complete the registration
if request.method == 'POST':
form = UserProfileForm(request.POST, request.FILES)
if form.is_valid():
pr = UserProfile()
pr.user = User.objects.get(id=request.user.id)
# pr.first_name = form.cleaned_data['first_name']
# pr.last_name = form.cleaned_data['last_name']
# pr.email = form.cleaned_data['email']
pr.dob = form.cleaned_data['dob']
pr.country = form.cleaned_data['country']
pr.State = form.cleaned_data['State']
pr.District = form.cleaned_data['District']
pr.phone = form.cleaned_data['phone']
pr.save()
messages.success(request, f'Profile has been updated successfully')
return redirect('/profile')
else:
messages.error(request, AssertionError)
else:
form = UserProfileForm()
return render(request, 'authenticate\bolo.html', context={'form': form})
@login_required
def profile_page(request): # Fetching data from DB to show user's complete profile page
data = get_object_or_404(UserProfile, user=request.user)
print('data=', data)
context = {'data': data}
return render(request, 'authenticate\profile.html', context)
1)As you could see in the "userprofileview" view, I have used the form.cleaned_data[] for all the form fields.
2) In the "profile_page", I have used your suggestion.
The change in the webpage is that, now I could see data for all the fields from the custom model UserProfile. However, now the fname, lname, and email is not populating the data.
email:
fname:
lname:
DOB:May 4, 2020
Country: India
State: Mahrashtra
District: Pune
So, I tried the form.cleaned_data() over the first_name, last_name and email. However, it didn't work.
Please suggest,
Thank you
amitesh
Hi there,
I just downloaded the code from github and i keep getting the following error :
cannot import name 'six' from 'django.utils'.
Please help me resolve the issue. Thanks
If you are using Django3,
pip install six
add
six
to thesettings.py
INSTALLED_APPS
and migrate.hey this doesn't work for me... you say add six to settings.py, do you mean like this ?
Because I always get "Name 'six' is not defined"
And then what do I have to migrate ?
That’s right. Can you try pip freeze and check, six was installed correctly?
I managed to fix it after a few tries, so it's good now :)
Worked great, adding to setting.py, huge thanks
Hey! That's removed in Django 3.0. django-cors-headers supports Django up until 2.1 right now. So, try to use six library instead of
Hello CoderAsha,
Great tutorial format by the way, quite easy to follow.
On my local machine, activation works correctly using my gmail account (EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend').
Email is sent, clicking the link activates the user and sets the signup_confirmation bool value to True and the user may now login.
However, once I push it to my live google VM, it no longer works and gives me the error:
SMTPAuthenticationError at /accounts/signup/
(534, b'5.7.14 \n5.7.14 Please log in via your web browser and then try again.\n5.7.14 Learn more at\n5.7.14 support.google.com/mail/answer/78754 a6sm50520455pgg.25 - gsmtp')
Do you have any idea about this?
Thank you!
Hi, I am glad you liked the tutorial. Google blocks sign-in attempts from apps which do not use modern security standard. You can however, turn on/off this safety feature by going to the link below:
google.com/settings/security/lesss...
let me know if it worked for you
I just turned this off and it worked. Thanks!
You already have the user when you save the form, then why do you need to call the
atuhenticate()
.Just for clarification can you please explain.
Awesome post!
Is it possible to modify the UserCreationForm to to include an imagefield so the user uploads an image an image when they are first registering and if they don't then just use the default image
yea u can
Thank you for this effort @conderasha
I have question, What if I don't use django form in signup stage? how can I insert in two models simultaneously (User & User_Profile)?? In django form I can use (.save(commit = false)). what can I do if I don't use form???
I have a Django application I have implemented this registration flow for but the flow has recently been changed.
The new flow requires that when a user registers, a verification email should be sent and after he/she verifies, a notification email should be sent to the admin, but he/she shouldn't be able to login until the admin approves it followed by a notification email.
How can I achieve this without using libraries/apps such as Django-registration-redux and Django-allauth? Please, include working snippets. I
Hello Sir,
Great tutorial, clean and simple very easy to follow.
Sir can i use it to make an API.
If not then what will be the best way to do so?
A little bit confussion ,where the condition is to check user is verified? Only boolean field enough?
Can I implement same concept to login for phone number otp verified user sir?
Thank you gor your great idea.
Hello, I am a student in Ghana.
I'm really grateful and appreciate this tutorial of yours.
This tutorial has been very useful.
When are you going to release the tutorial on how to "configure a production quality email service to send actual confirmation mail".
Really looking forward to that tutorial.
Thank you.