Introduction
In the previous tutorial, you learned the basics of authentication in Django: signup, login, logout, and protecting pages. That is enough for simple applications, but real projects often need a more advanced registration system.
For example, you may want users to:
- register with email
- add profile information
- receive validation messages
- create an account and profile together
- be redirected properly after signup
- have a cleaner and more professional registration experience
In this tutorial, you will build a more advanced user registration system in Django using:
- a custom signup form
- extra fields
- profile creation
- validation
- better templates and user flow
By the end, you will have a stronger registration system ready for real-world projects.
What You Will Learn
By the end of this tutorial, you will understand:
- how to customize
UserCreationForm - how to add extra fields like email and full name
- how to validate registration data
- how to save user and profile data together
- how to log the user in automatically after signup
- how to create cleaner signup templates
- best practices for advanced registration systems
Prerequisites
Before starting, you should already know:
- Django authentication basics
- forms and ModelForms
- models and relationships
- templates and views
1. Why Build an Advanced Registration System?
The default UserCreationForm is useful, but it is limited. It usually includes only:
- username
- password
- password confirmation
In real projects, you often need more, such as:
- first name
- last name
- phone number
- profile image
- city or bio
A more advanced registration system gives a better user experience and helps collect the data your application really needs.
2. The Default User Model
Django already provides a built-in User model:
from django.contrib.auth.models import UserIt already includes fields such as:
usernamefirst_namelast_nameemailpassword
So in many cases, you do not need a custom user model yet. You can simply extend the default user with a profile model.
3. Create a Profile Model
A common approach is to create a separate profile linked to the user.
models.py
from django.db import models
from django.contrib.auth.models import User
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
phone = models.CharField(max_length=20, blank=True)
city = models.CharField(max_length=100, blank=True)
bio = models.TextField(blank=True)
def __str__(self):
return self.user.usernameExplanation
- each profile belongs to one user
- each user has one profile
- extra fields like
phone,city, andbioare stored separately
Run migrations after creating the model:
python manage.py makemigrations
python manage.py migrate4. Create a Custom Signup Form
Now let us create a better signup form with extra fields.
forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
class AdvancedSignUpForm(UserCreationForm):
email = forms.EmailField(required=True)
first_name = forms.CharField(max_length=100, required=True)
last_name = forms.CharField(max_length=100, required=True)
phone = forms.CharField(max_length=20, required=False)
city = forms.CharField(max_length=100, required=False)
bio = forms.CharField(widget=forms.Textarea, required=False)
class Meta:
model = User
fields = [
'username',
'first_name',
'last_name',
'email',
'password1',
'password2',
'phone',
'city',
'bio',
]Important Note
Fields like phone, city, and bio are not part of the User model. They belong to the Profile model. We include them in the form, but we will save them manually.
5. Add Validation for Email Uniqueness
By default, Django’s User model does not force unique emails unless you check manually.
Add a validation method
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
class AdvancedSignUpForm(UserCreationForm):
email = forms.EmailField(required=True)
first_name = forms.CharField(max_length=100, required=True)
last_name = forms.CharField(max_length=100, required=True)
phone = forms.CharField(max_length=20, required=False)
city = forms.CharField(max_length=100, required=False)
bio = forms.CharField(widget=forms.Textarea, required=False)
class Meta:
model = User
fields = [
'username',
'first_name',
'last_name',
'email',
'password1',
'password2',
'phone',
'city',
'bio',
]
def clean_email(self):
email = self.cleaned_data['email']
if User.objects.filter(email=email).exists():
raise forms.ValidationError('This email is already in use.')
return emailThis prevents two users from registering with the same email.
6. Save User and Profile Together
Now we need a signup view that:
- creates the user
- creates the profile
- saves all extra fields
views.py
from django.shortcuts import render, redirect
from django.contrib.auth import login
from .forms import AdvancedSignUpForm
from .models import Profile
def signup_view(request):
if request.method == 'POST':
form = AdvancedSignUpForm(request.POST)
if form.is_valid():
user = form.save(commit=False)
user.email = form.cleaned_data['email']
user.first_name = form.cleaned_data['first_name']
user.last_name = form.cleaned_data['last_name']
user.save()
Profile.objects.create(
user=user,
phone=form.cleaned_data['phone'],
city=form.cleaned_data['city'],
bio=form.cleaned_data['bio'],
)
login(request, user)
return redirect('dashboard')
else:
form = AdvancedSignUpForm()
return render(request, 'accounts/signup.html', {'form': form})7. Why Use commit=False?
This line is important:
user = form.save(commit=False)It means:
- create the user object in memory
- do not save it to the database yet
- let us modify it first
- then save it manually
This is useful when you want to add more data before saving.
8. Automatically Log In the User After Registration
After signup, many websites log the user in automatically.
We do that here:
from django.contrib.auth import login
login(request, user)This improves user experience because they do not need to log in again immediately after registering.
9. Create the Signup Template
templates/accounts/signup.html
<h1>Create Your Account</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Register</button>
</form>
<p>
Already have an account?
<a href="{% url 'login' %}">Login here</a>
</p>This version works, but it is very basic. Let us improve it.
10. Better Signup Template with Field Control
A more professional template gives you better control over layout and styling.
<h1>Create Your Account</h1>
<form method="post">
{% csrf_token %}
<p>
<label for="{{ form.username.id_for_label }}">Username</label>
{{ form.username }}
{{ form.username.errors }}
</p>
<p>
<label for="{{ form.first_name.id_for_label }}">First Name</label>
{{ form.first_name }}
{{ form.first_name.errors }}
</p>
<p>
<label for="{{ form.last_name.id_for_label }}">Last Name</label>
{{ form.last_name }}
{{ form.last_name.errors }}
</p>
<p>
<label for="{{ form.email.id_for_label }}">Email</label>
{{ form.email }}
{{ form.email.errors }}
</p>
<p>
<label for="{{ form.phone.id_for_label }}">Phone</label>
{{ form.phone }}
{{ form.phone.errors }}
</p>
<p>
<label for="{{ form.city.id_for_label }}">City</label>
{{ form.city }}
{{ form.city.errors }}
</p>
<p>
<label for="{{ form.bio.id_for_label }}">Bio</label>
{{ form.bio }}
{{ form.bio.errors }}
</p>
<p>
<label for="{{ form.password1.id_for_label }}">Password</label>
{{ form.password1 }}
{{ form.password1.errors }}
</p>
<p>
<label for="{{ form.password2.id_for_label }}">Confirm Password</label>
{{ form.password2 }}
{{ form.password2.errors }}
</p>
<button type="submit">Register</button>
</form>This makes it easier to style each field later with Bootstrap or custom CSS.
11. Add the URL
urls.py
from django.urls import path
from .views import signup_view
urlpatterns = [
path('signup/', signup_view, name='signup'),
]12. Create a Dashboard Page
After registration, we redirected the user to dashboard, so let us create it.
views.py
from django.contrib.auth.decorators import login_required
from django.shortcuts import render
@login_required
def dashboard(request):
return render(request, 'accounts/dashboard.html')templates/accounts/dashboard.html
<h1>Welcome {{ user.first_name|default:user.username }}</h1>
<p>You are now logged in.</p>
{% if user.profile %}
<p>City: {{ user.profile.city }}</p>
<p>Phone: {{ user.profile.phone }}</p>
{% endif %}13. Accessing the Profile from the User
Because the relationship is one-to-one, you can access the profile like this:
user.profileSo in templates:
{{ user.profile.city }}
{{ user.profile.phone }}This is very convenient.
14. Improve the Form with Widgets
You can make the form cleaner using widgets.
forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
class AdvancedSignUpForm(UserCreationForm):
email = forms.EmailField(
required=True,
widget=forms.EmailInput(attrs={'placeholder': 'Enter your email'})
)
first_name = forms.CharField(
max_length=100,
required=True,
widget=forms.TextInput(attrs={'placeholder': 'First name'})
)
last_name = forms.CharField(
max_length=100,
required=True,
widget=forms.TextInput(attrs={'placeholder': 'Last name'})
)
phone = forms.CharField(
max_length=20,
required=False,
widget=forms.TextInput(attrs={'placeholder': 'Phone number'})
)
city = forms.CharField(
max_length=100,
required=False,
widget=forms.TextInput(attrs={'placeholder': 'City'})
)
bio = forms.CharField(
required=False,
widget=forms.Textarea(attrs={'placeholder': 'Short bio', 'rows': 4})
)
class Meta:
model = User
fields = [
'username',
'first_name',
'last_name',
'email',
'password1',
'password2',
'phone',
'city',
'bio',
]This improves the appearance and usability of the form.
15. Add Custom Validation for Username Rules
You can also validate usernames more strictly.
Example
def clean_username(self):
username = self.cleaned_data['username']
if len(username) < 4:
raise forms.ValidationError('Username must be at least 4 characters long.')
return usernameAdd this inside your form class.
16. Add Password Help Text or Simplify It
By default, UserCreationForm shows Django’s built-in password help rules.
If you want to keep them, that is fine.
If you want to customize them, you can do:
self.fields['password1'].help_text = 'Choose a strong password.'
self.fields['password2'].help_text = 'Enter the same password again.'inside the form’s __init__() method.
Example:
class AdvancedSignUpForm(UserCreationForm):
...
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['password1'].help_text = 'Choose a strong password.'
self.fields['password2'].help_text = 'Enter the same password again.'17. Full Working Example
models.py
from django.db import models
from django.contrib.auth.models import User
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
phone = models.CharField(max_length=20, blank=True)
city = models.CharField(max_length=100, blank=True)
bio = models.TextField(blank=True)
def __str__(self):
return self.user.username
forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
class AdvancedSignUpForm(UserCreationForm):
email = forms.EmailField(required=True)
first_name = forms.CharField(max_length=100, required=True)
last_name = forms.CharField(max_length=100, required=True)
phone = forms.CharField(max_length=20, required=False)
city = forms.CharField(max_length=100, required=False)
bio = forms.CharField(widget=forms.Textarea, required=False)
class Meta:
model = User
fields = [
'username',
'first_name',
'last_name',
'email',
'password1',
'password2',
'phone',
'city',
'bio',
]
def clean_email(self):
email = self.cleaned_data['email']
if User.objects.filter(email=email).exists():
raise forms.ValidationError('This email is already in use.')
return emailviews.py
from django.shortcuts import render, redirect
from django.contrib.auth import login
from django.contrib.auth.decorators import login_required
from .forms import AdvancedSignUpForm
from .models import Profile
def signup_view(request):
if request.method == 'POST':
form = AdvancedSignUpForm(request.POST)
if form.is_valid():
user = form.save(commit=False)
user.email = form.cleaned_data['email']
user.first_name = form.cleaned_data['first_name']
user.last_name = form.cleaned_data['last_name']
user.save()
Profile.objects.create(
user=user,
phone=form.cleaned_data['phone'],
city=form.cleaned_data['city'],
bio=form.cleaned_data['bio'],
)
login(request, user)
return redirect('dashboard')
else:
form = AdvancedSignUpForm()
return render(request, 'accounts/signup.html', {'form': form})
@login_required
def dashboard(request):
return render(request, 'accounts/dashboard.html')urls.py
from django.urls import path
from .views import signup_view, dashboard
urlpatterns = [
path('signup/', signup_view, name='signup'),
path('dashboard/', dashboard, name='dashboard'),
]18. Common Beginner Mistakes
Adding profile fields directly to UserCreationForm without saving them
Extra fields like phone and city do not automatically save to the User model.
Forgetting to create the profile
If you collect profile data, you must save it into a Profile model.
Not validating unique email
Two accounts may end up using the same email.
Forgetting commit=False
You often need it when customizing user saving.
Redirecting to a protected page without logging the user in
If you redirect to the dashboard but do not log the user in, access may fail.
19. Best Practices
- keep using Django’s built-in
Usermodel unless you truly need a custom one - store extra data in a profile model
- validate email uniqueness
- show clear form errors
- log the user in automatically after successful signup
- keep templates user-friendly and clean
- make the registration experience simple
20. Optional Improvement: Create Profile Automatically with Signals
Instead of creating the profile inside the signup view, you can use signals.
Example
from django.db.models.signals import post_save
from django.contrib.auth.models import User
from django.dispatch import receiver
from .models import Profile
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)This is useful, but for beginners, creating the profile directly in the signup view is easier to understand.
21. Optional Improvement: Add Profile Picture
Later, you can extend the profile with:
avatar = models.ImageField(upload_to='avatars/', blank=True, null=True)Then update the form and template to support image upload.
This is a very common real-world improvement.
22. Summary
In this tutorial, you learned how to build a more advanced user registration system in Django by:
- customizing
UserCreationForm - adding extra fields like email and full name
- creating a
Profilemodel - validating email uniqueness
- saving user and profile data together
- logging the user in automatically
- improving the template and user flow
This approach gives you a much more realistic registration system for real Django projects.
23. Mini Quiz
1. Which form is commonly extended for advanced signup?
A. ModelForm
B. LoginForm
C. UserCreationForm
D. AuthForm
2. Where should extra data like phone or city usually be stored?
A. In the URL
B. In a profile model
C. In settings.py
D. In middleware
3. What does commit=False do?
A. Deletes the form
B. Saves the form twice
C. Creates the object without saving to the database yet
D. Logs the user out
4. Which function can log the user in after signup?
A. authenticate()
B. login()
C. logout()
D. redirect()
5. Why validate email uniqueness?
A. To make the form longer
B. To improve template design
C. To prevent multiple accounts from using the same email
D. To disable login
24. What Comes Next?
The next ideal tutorial is:
Tutorial: Login Required Pages and Permissions
Subject: restricting access to views, user roles, permissions, and protected content.
💬 Comments
No comments yet. Be the first to comment!
Login to comment.