Create a Sign up Page
Our site now has all of the necessary functionality for authenticating users. Next, we can add a sign up page.
All of the forms used in the class-based authentication views are also available on their own, allowing you to use them when implementing your own log in views. While Django does not provide a class-based view for sign up, it does provide a UserCreationForm
.
You can import all of the forms in the auth
package from django.contrib.auth.forms
.
Let's use the UserCreationForm
to implement a sign up view.
Step 1: Subclass the UserCreationForm
The UserCreationForm
is a ModelForm
that has three fields:
username
password1
password2
As we also require some other fields from the user, we will have to subclass this form to use it.
First, inherit from the form and add your fields.
# authentication/forms.py
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import UserCreationForm
class SignupForm(UserCreationForm):
class Meta(UserCreationForm.Meta):
model = get_user_model()
fields = ('username', 'email', 'first_name', 'last_name', 'role')
Because we are subclassing a ModelForm
, it is best practice to also inherit from the inner class Meta
when extending the form.
We are also using the utility method get_user_model
, which allows you to get the User
model without importing it directly. This is particularly important if you are building an app designed for reuse in different projects.
Step 2: Add a Sign up View
Now that we have the form, it is a simple task of adding a view, writing the template, and including it in the URL patterns.
First the view:
# authentication/views.py
from django.conf import settings
from django.contrib.auth import login
from django.shortcuts import redirect, render
from . import forms
def signup_page(request):
form = forms.SignupForm()
if request.method == 'POST':
form = forms.SignupForm(request.POST)
if form.is_valid():
user = form.save()
# auto-login user
login(request, user)
return redirect(settings.LOGIN_REDIRECT_URL)
return render(request, 'authentication/signup.html', context={'form': form})
The best way to access the configured settings is to import django.conf.settings
. This allows you to access LOGIN_REDIRECT_URL
, which you'll use for the redirect on successful sign up. You also used the login
function to log the user in automatically.
Step 3: Configure the Templates
Next, the template:
# authentication/templates/authentication/signup.html
{% extends 'base.html' %}
{% block content %}
<h2>Sign Up</h2>
{% if form.errors %}
{{ form.errors }}
{% endif %}
<form method="post">
{{ form.as_p }}
{% csrf_token %}
<button type="submit" >Submit</button>
</form>
{% endblock content %}
Let's also add a sign up link to the log in page.
# authentication/templates/authentication/login.html
{% extends 'base.html' %}
{% block content %}
<form method="post">
{{ form.as_p }}
{% csrf_token %}
<button type="submit" >Submit</button>
</form>
<p>Not a member? <a href="{% url 'signup' %}">Sign up now!</a></p>
{% endblock content %}
Step 4: Add the URL Pattern
Now update the URL patterns.
# fotoblog/urls.py
urlpatterns = [
...
path('signup/', authentication.views.signup_page, name='signup'),
]
Let's see if it worked.
Excellent! Try signing up a user yourself.
You will see all of the restrictions on a password. These are the default password validators for Django, but you can configure these to be as strong or as weak as you want. Let's see how to do that.
Enforce Custom Password Validation
You need to use validators to customize password validation. Validators are classes used to validate the input of a field. The auth
module provides four different password validators found in django.contrib.auth.password_validation
.
The default validators are:
UserAttributeSimilarityValidator
- invalidates the password if too similar to the user's details.MinimumLengthValidator
- invalidates the password below a minimum length.CommonPasswordValidator
- invalidates the password if found in a list of common passwords.NumericPasswordValidator
- invalidates the password if it is all numeric.
These are all enabled by default but you can configure them by setting AUTH_PASSWORD_VALIDATORS
in the settings.
This is the default configuration.
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',
},
]
Let's say the default password restrictions are too strict, and you want to enforce that the password must:
Be at least eight characters long.
Contain letters.
Contain numbers.
To do this, you would have to create custom validators. Let's see how.
Step 1: Create the Validators
First, create a new file called validators.py
to house your validators.
A validator is a class and only requires two methods; a validate
method and a get_help_text
method.
Let's build a validator to check if the password contains a letter.
# authentication/validators.py
from django.core.exceptions import ValidationError
class ContainsLetterValidator:
def validate(self, password, user=None):
if not any(char.isalpha() for char in password):
raise ValidationError(
'The password must contain a letter', code='password_no_letters')
def get_help_text(self):
return 'Your password must contain at least one upper or lower case letter.'
The validate
method checks the password and raises a ValidationError
if the password fails the criteria. The get_help_text
method provides the end user with guidance on how to pass the validation.
Step 2: Configure the Settings to Use the Custom Validator
Now that the validator is built, let’s add it to the settings. We will also use the MinimumLengthValidator
, which we can configure using the OPTIONS
key.
# fotoblog/settings.py
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
'OPTIONS': {
'min_length': 8,
}
},
{
'NAME': 'authentication.validators.ContainsLetterValidator',
},
]
Check out the sign up page now.
It looks like it worked! Try testing out the password functionality.
These validators are great for passwords, but you can also use them for other fields.
If you wanted to check if a postcode is valid, you could define a PostCodeValidator
in your validators file and then specify it in the form.
from . import validators
class PostCodeForm(forms.Form):
post_code = forms.CharField(max_length=10, validators=[validators.PostCodeValidator])
You can now validate inputs and make sure that the data in your database is consistent.
Exercise: Create Your Own Password Validator
You know how to check to see if user passwords contain any letters. Now check to see if they have at least one number.
Try creating a validator that will check the password to see if they contain a number using
if not any(character.is_digit() for character in password)
.
You will need to:
Create the validator
ContainsNumberValidator
inauthentication/validators.py
.Configure
AUTH_PASSWORD_VALIDATORS
in the settings to use the validator.
Once you finish, check your implementation against the solution in GitHub repo.
Find and Use Third-Party Django Packages
We have built an authentication
app that can handle signing up and logging in users, but we could add a lot more authentication functionality. For example:
Email verification.
Single sign in integration with social accounts.
Password reset emails.
To build all of these features would be a large undertaking. And yet, these are tasks that are common to many sites.
Do I have to recreate this for every project?
No, you don't.
A large community of developers supports Django, many of whom create and publish reusable packages that the community can use.
Look for these in Django Packages!
Follow along in the screencast to explore Django Packages and see how I would find one to set up authentication more quickly and easily.
There are several considerations when selecting a package.
It is actively maintained. An unmaintained project will not receive updates to be compatible with new Django versions, and bugs will not get fixed. You can see when the repository was last updated on GitHub.
It is popular. More popular projects are less likely to contain bugs and are also more likely to have a community of developers who will actively open pull requests to fix bugs. A good way to gauge the popularity of a repository is to see the number of stars on GitHub.
It is compatible with your Python version.
It is compatible with your Django version.
Beta or Production. Generally, you want a Production package. There are some robust Django packages out there that are in Beta phase. If it meets all the other criteria and the package is Beta, it's probably OK.
If you are working with a package and you find a bug, raise an issue on GitHub. If you feel courageous, fork the repository, fix it yourself, and then open up a pull request back into the repository. Congratulations, you are now an open-source contributor!
One last thing to note is that many packages are a labor of love, maintained by people on their own time, for no compensation. Questions and offers to help will often be well received, but demanding ones may not get a warm reception!
Let's Recap!
You can use Django generic authentication forms in your project to save time.
You can specify password validators in the
AUTH_PASSWORD_VALIDATORS
setting.Use Django Packages to find third-party packages to integrate into your site.
You have successfully built an authentication app. Now, time for a quiz on what you have learned!