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:
In this tutorial, you will build a more advanced user registration system in Django using:
By the end, you will have a stronger registration system ready for real-world projects.
By the end of this tutorial, you will understand:
UserCreationFormBefore starting, you should already know:
The default UserCreationForm is useful, but it is limited. It usually includes only:
In real projects, you often need more, such as:
A more advanced registration system gives a better user experience and helps collect the data your application really needs.
Django already provides a built-in User model:
from django.contrib.auth.models import UserIt already includes fields such as:
usernamefirst_namelast_nameemailpasswordSo in many cases, you do not need a custom user model yet. You can simply extend the default user with a profile model.
A common approach is to create a separate profile linked to the user.
models.pyfrom 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.usernamephone, city, and bio are stored separatelyRun migrations after creating the model:
python manage.py makemigrations
python manage.py migrateNow let us create a better signup form with extra fields.
forms.pyfrom 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',
]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.
By default, Django’s User model does not force unique emails unless you check manually.
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.
Now we need a signup view that:
views.pyfrom 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})commit=False?This line is important:
user = form.save(commit=False)It means:
This is useful when you want to add more data before saving.
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.
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.
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.
urls.pyfrom django.urls import path
from .views import signup_view
urlpatterns = [
path('signup/', signup_view, name='signup'),
]After registration, we redirected the user to dashboard, so let us create it.
views.pyfrom 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 %}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.
You can make the form cleaner using widgets.
forms.pyfrom 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.
You can also validate usernames more strictly.
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.
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.'models.pyfrom 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.pyfrom 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.pyfrom 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.pyfrom django.urls import path
from .views import signup_view, dashboard
urlpatterns = [
path('signup/', signup_view, name='signup'),
path('dashboard/', dashboard, name='dashboard'),
]UserCreationForm without saving themExtra fields like phone and city do not automatically save to the User model.
If you collect profile data, you must save it into a Profile model.
Two accounts may end up using the same email.
commit=FalseYou often need it when customizing user saving.
If you redirect to the dashboard but do not log the user in, access may fail.
User model unless you truly need a custom oneInstead of creating the profile inside the signup view, you can use signals.
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.
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.
In this tutorial, you learned how to build a more advanced user registration system in Django by:
UserCreationFormProfile modelThis approach gives you a much more realistic registration system for real Django projects.
A. ModelForm
B. LoginForm
C. UserCreationForm
D. AuthForm
A. In the URL
B. In a profile model
C. In settings.py
D. In middleware
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
A. authenticate()
B. login()
C. logout()
D. redirect()
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
The next ideal tutorial is:
Tutorial: Login Required Pages and Permissions
Subject: restricting access to views, user roles, permissions, and protected content.