Existing users can now log in, log out, change their password, and reset the password if they forgot it. Now, we need to build a view to allow visitors to create a user account.
Let's create a simple view to allow user registration in our website. Initially, we have to create a form to let the user enter a username, their real name, and a password. Edit the forms.py
file located inside the account
application directory and add the following code to it:
from django.contrib.auth.models import User class UserRegistrationForm(forms.ModelForm): password = forms.CharField(label='Password', widget=forms.PasswordInput) password2 = forms.CharField(label='Repeat password', widget=forms.PasswordInput) class Meta: model = User fields = ('username', 'first_name', 'email') def clean_password2(self): cd = self.cleaned_data if cd['password'] != cd['password2']: raise forms.ValidationError('Passwords don't match.') return cd['password2']
We have created a model form for the User
model. In our form we include only the username
, first_name
, and email
fields of the model. These fields will be validated based on their corresponding model fields. For example, if the user chooses a username that already exists, their will get a validation error. We have added two additional fields password
and password2
for the user to set his or her password and confirm it. We have defined a clean_password2()
method to check the second password against the first one and not let the form validate if the passwords don't match. This check is done when we validate the form calling its is_valid()
method. You can provide a clean_<fieldname>()
method to any of your form fields in order to clean the value or raise form validation errors for the specific field. Forms also include a general clean()
method to validate the entire form, which is useful to validate fields that depend on each other.
Django also provides a UserCreationForm
form that you can use, which resides in django.contrib.auth.forms
and is very similar to the one we have created.
Edit the views.py
file of the account
application and add the following code to it:
from .forms import LoginForm, UserRegistrationForm
def register(request):
if request.method == 'POST':
user_form = UserRegistrationForm(request.POST)
if user_form.is_valid():
# Create a new user object but avoid saving it yet
new_user = user_form.save(commit=False)
# Set the chosen password
new_user.set_password(
user_form.cleaned_data['password'])
# Save the User object
new_user.save()
return render(request,
'account/register_done.html',
{'new_user': new_user})
else:
user_form = UserRegistrationForm()
return render(request,
'account/register.html',
{'user_form': user_form})
The view for creating user accounts is quite simple. Instead of saving the raw password entered by the user, we use the set_password()
method of the User
model that handles encryption to save for safety.
Now, edit the urls.py
file of your account
application and add the following URL pattern:
url(r'^register/$', views.register, name='register'),
Finally, create a new template in the account/
template directory, name it register.html
, and make it look as follows:
{% extends "base.html" %} {% block title %}Create an account{% endblock %} {% block content %} <h1>Create an account</h1> <p>Please, sign up using the following form:</p> <form action="." method="post"> {{ user_form.as_p }} {% csrf_token %} <p><input type="submit" value="Create my account"></p> </form> {% endblock %}
Add a template file in the same directory and name it register_done.html
. Add the following code to it:
{% extends "base.html" %} {% block title %}Welcome{% endblock %} {% block content %} <h1>Welcome {{ new_user.first_name }}!</h1> <p>Your account has been successfully created. Now you can <a href="{% url "login" %}">log in</a>.</p> {% endblock %}
Now, open http://127.0.0.1:8000/account/register/
in your browser. You will see the registration page you created:
Fill in the details for a new user and click the Create my account button. If all fields are valid, the user will be created and you will get the following success message:
Click the log in link and enter your username and password to verify that you can access your account.
Now, you can also add a link for registration to your login template. Edit the registration/login.html
template and replace this line:
<p>Please, use the following form to log-in:</p>
...with this one:
<p>Please, use the following form to log-in. If you don't have an account <a href="{% url "register" %}">register here</a></p>
We made the sign up page accessible from the login page.
When you have to deal with user accounts, you will find out that the User
model of the Django authentication framework is suitable for common cases. However, the User
model comes with very basic fields. You may wish to extend the User
model to include additional data. The best way to do this is by creating a profile model that contains all additional fields and a one-to-one relationship with the Django User
model.
Edit the models.py
file of your account
application and add the following code to it:
from django.db import models from django.conf import settings class Profile(models.Model): user = models.OneToOneField(settings.AUTH_USER_MODEL) date_of_birth = models.DateField(blank=True, null=True) photo = models.ImageField(upload_to='users/%Y/%m/%d', blank=True) def __str__(self): return 'Profile for user {}'.format(self.user.username)
The user
one-to-one field allows us to associate profiles with users. The photo field is an ImageField
field. You will need to install one of the Python packages to manage images, which are PIL (Python Imaging Library) or Pillow, which is a PIL fork. Install Pillow by running the following command in your shell:
pip install Pillow==2.9.0
For Django to serve media files uploaded by users with the development server, add the following settings to the settings.py
file of your project:
MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
MEDIA_URL
is the base URL to serve the media files uploaded by users, and MEDIA_ROOT
is the local path where they reside. We build the path dynamically relative to our project path to make our code more generic.
Now, edit the main urls.py
file of the bookmarks
project and modify the code as follows:
from django.conf.urls import include, url from django.contrib import admin from django.conf import settings from django.conf.urls.static import static urlpatterns = [ url(r'^admin/', include(admin.site.urls)), url(r'^account/', include('account.urls')), ] if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
In this way, the Django development server will be in charge of serving the media files during development.
The static()
helper function is suitable for development but not for production use. Never serve your static files with Django in a production environment.
Open the shell and run the following command to create the database migration for the new model:
python manage.py makemigrations
You will get this output:
Migrations for 'account': 0001_initial.py: - Create model Profile
Next, sync the database with the following command:
python manage.py migrate
You will see an output that includes the following line:
Applying account.0001_initial... OK
Edit the admin.py
file of the account
application and register the Profile
model in the administration site, like this:
from django.contrib import admin from .models import Profile class ProfileAdmin(admin.ModelAdmin): list_display = ['user', 'date_of_birth', 'photo'] admin.site.register(Profile, ProfileAdmin)
Run the development server again using the python manage.py runserver
command. Now, you should be able to see the Profile
model in the administration site of your project, as follows:
Now, we are going to let users edit their profile
in the website. Add the following model forms to the forms.py
file of the account
application:
from .models import Profile class UserEditForm(forms.ModelForm): class Meta: model = User fields = ('first_name', 'last_name', 'email') class ProfileEditForm(forms.ModelForm): class Meta: model = Profile fields = ('date_of_birth', 'photo')
These forms are as follows:
UserEditForm
: Will allow users to edit their first name, last name, and e-mail, which are stored in the built-in User
model.ProfileEditForm
: Will allow users to edit the extra data we save in the custom Profile
model. Users will be able to edit their date of birth and upload a picture for their profile.Edit the views.py
file of the account
application and import the Profile model like this:
from .models import Profile
And add the following lines to the register
view below new_user.save()
:
# Create the user profile profile = Profile.objects.create(user=new_user)
When users register in our site, we create an empty profile associated to them. You should create a Profile
object manually using the administration site for the users you created before.
Now, we are going to let users edit their profile. Add the following code to the same file:
from .forms import LoginForm, UserRegistrationForm, UserEditForm, ProfileEditForm @login_required def edit(request): if request.method == 'POST': user_form = UserEditForm(instance=request.user, data=request.POST) profile_form = ProfileEditForm( instance=request.user.profile, data=request.POST, files=request.FILES) if user_form.is_valid() and profile_form.is_valid(): user_form.save() profile_form.save() else: user_form = UserEditForm(instance=request.user) profile_form = ProfileEditForm( instance=request.user.profile) return render(request, 'account/edit.html', {'user_form': user_form, 'profile_form': profile_form})
We use the login_required
decorator because users have to be authenticated to edit their profile. In this case, we are using two model forms: UserEditForm
to store the data of the built-in User model and ProfileEditForm
to store the additional profile data. To validate the submitted data, we check that the is_valid()
method of both forms returns True
. In this case, we save both forms to update the corresponding object in the database.
Add the following URL pattern to the urls.py
file of the account
application:
url(r'^edit/$', views.edit, name='edit'),
Finally, create a template for this view in templates/account/
and name it edit.html
. Add the following code to it:
{% extends "base.html" %} {% block title %}Edit your account{% endblock %} {% block content %} <h1>Edit your account</h1> <p>You can edit your account using the following form:</p> <form action="." method="post" enctype="multipart/form-data"> {{ user_form.as_p }} {{ profile_form.as_p }} {% csrf_token %} <p><input type="submit" value="Save changes"></p> </form> {% endblock %}
Register a new user and open http://127.0.0.1:8000/account/edit/
. You should see the following page:
Now, you can also edit the dashboard page and include links to edit profile and change password pages. Open the account/dashboard.html
template and replace this line:
<p>Welcome to your dashboard.</p>
...with this one:
<p>Welcome to your dashboard. You can <a href="{% url "edit" %}">edit your profile</a> or <a href="{% url "password_change" %}">change your password</a>.</p>
Users can now access the form to edit their profile from their dashboard.
Django also offers a way to substitute the whole User
model with your own custom model. Your user class should inherit from Django's AbstractUser
class, which provides the full implementation of the default user as an abstract model. You can read more about this method at https://docs.djangoproject.com/en/1.8/topics/auth/customizing/#substituting-a-custom-user-model.
Using a custom user model will give you more flexibility, but it might also result in more difficult integration with pluggable applications that interact with the User
model.
When dealing with user actions, you might want to inform your users about the result of their actions. Django has a built-in messages framework that allows you to display one-time notifications to your users. The messages framework is located at django.contrib.messages
and it is included in the default INSTALLED_APPS
list of the settings.py
file when you create new projects using python manage.py
startproject
. You will notice that your settings file contains a middleware named django.contrib.messages.middleware.MessageMiddleware
in the list of MIDDLEWARE_CLASSES
of your settings. The messages framework provides a simple way to add messages to users. Messages are stored in the database and displayed in the next request the user does. You can use the messages framework in your views by importing the messages module and adding new messages with simple shortcuts like this:
from django.contrib import messages messages.error(request, 'Something went wrong')
You can create new messages using the add_message()
method or any of the following shortcut methods:
success()
: Success messages to display after an action was successfulinfo()
: Informational messageswarning()
: Something has not yet failed but may fail imminentlyerror()
: An action was not successful or something faileddebug()
: Debug messages that will be removed or ignored in a production environmentLet's display messages to users. Since the messages framework applies globally to the project, we can display messages for the user in our base template. Open the base.html
template and add the following code between the <div>
element with the id header and the <div>
element with the id content:
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li class="{{ message.tags }}">
{{ message|safe }}
<a href="#" class="close">✖</a>
</li>
{% endfor %}
</ul>
{% endif %}
The messages framework includes a context processor that adds a messages
variable to the request context. So you can use this variable in your templates to display current messages to the user.
Now, let's modify our edit view to use the messages framework. Edit the views.py
file of your application and make the edit
view look as follows:
from django.contrib import messages @login_required def edit(request): if request.method == 'POST': # ... if user_form.is_valid() and profile_form.is_valid(): user_form.save() profile_form.save() messages.success(request, 'Profile updated ' 'successfully') else: messages.error(request, 'Error updating your profile') else: user_form = UserEditForm(instance=request.user) # ...
We add a success message when the user successfully updates their profile. If any of the forms are invalid, we add an error message instead.
Open http://127.0.0.1:8000/account/edit/
in your browser and edit your profile. When the profile is successfully updated, you should see the following message:
When the form is not valid, you should see the following message: