User authentication and auto-management
In the first section of this chapter you learned how to create users. But recall that for this process to work, you either had to use a Django command line tool or the Django admin. These techniques while valid are not intended and won't scale for end users that visit an application.
Similarly, up to this point in the chapter, the only location for users to log in and log out has been through the Django admin authentication form. This last form is not an ideal location for end users either, not only because it lacks a layout made for an application, but also because it's not intended for users who never plan to interact with a project's database.
If you plan on end users
authenticating themselves into an application, you need to provide
a way for users to sign up, a way for users to log in and log out
as well as a way for users to remember and change their passwords.
The same django.contrib.auth
package that supports the
User
model, also includes a series of pre-built
constructs designed to create user authentication workflows.
The first pre-built mechanism available to authenticate users is a set of urls that allow users to: log in and log out of an application, allow users to change their password, as well as allow users to reset their password supported by an email notification.
Listing 10-11 illustrates how to
add the full set of django.contrib.auth
urls to the
main urls.py
file -- using an include
statement -- as well as the equivalent individual url
statements in case you want to selectively pick and choose which
urls to use in a project.
Listing 10-11. Configure urls from django.contrib.auth package.
from django.conf.urls import url from django.contrib.auth import views # Option 1 to include all urls (See option 2 for included urls) urlpatterns = [ url(r'^accounts/', include('django.contrib.auth.urls')), ] # Option 2) (Explicit urls, all included in django.contrib.auth) urlpatterns = [ url(r'^accounts/login/$', views.LoginView.as_view(), name='login'), url(r'^accounts/logout/$', views.LogoutView.as_view(), name='logout'), url(r'^accounts/password_change/$', views.PasswordChangeView.as_view(), name='password_change'), url(r'^accounts/password_change/done/$', views.PasswordChangeDoneView.as_view(), name='password_change_done'), url(r'^accounts/password_reset/$', views.PasswordResetView.as_view(), name='password_reset'), url(r'^accounts/password_reset/done/$', views.PasswordResetDoneView.as_view(), name='password_reset_done'), url(r'^accounts/reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'), url(r'^accounts/reset/done/$', views.PasswordResetCompleteView.as_view(), name='password_reset_complete'), ]
The first option in listing 10-11
configures all the django.contrib.auth
urls on the
accounts
url. This begets the question, why the
accounts
url ? Because, by default all
django.contrib.auth
actions use this url as their
root. (e.g. in previous sections, recall that attempting to access
resources that require log in, performs a redirect to
/accounts/login
). So by using this url
include()
statement with
django.contrib.auth.urls
, all
django.contrib.auth
actions get a functioning url,
that are also backed by the necessary view method logic! But more
on view method logic shortly.
Because the url
include()
statement has a 'use all urls or none'
behavior, the second option in listing 10-11 represents the use of
granular url statements to configure the same
django.contrib.auth
urls. This last option is helpful
if you want to disable certain django.contrib.auth
urls but still keep other django.contrib.auth
urls
active (e.g. disable password changing urls, but keep log in and
log out urls).
As you can see, using either
option in listing 10-11 provides you with a quick solution to
hook-up django.contrib.auth
actions to urls, the
latter of which are also hooked up to default class-based views
that are also part of the django.contrib.auth
package.
Log in and log out workflow
The entry points into the log in
and log out workflows -- as you can see in listing 10-11 -- are the
/accounts/login/
and /accounts/logout/
urls, respectively. If a call is made on the
/accounts/login/
url the
django.contrib.auth.views.LoginView
class-based view
is triggered, and if a call is made on the
/accounts/logout/
url the
django.contrib.auth.views.LogoutView
class-based view
is triggered.
Like all Django built-in
class-based views, both the LoginView
and
LogoutView
class-based views require little to nothing
in terms of code and configuration. The only thing they both
require is a template to present a login form and a template with a
logout success message, respectively.
The LoginView
class-based view looks for the template
registration/login.html
under a directory defined as
part of the TEMPLATES/DIRS
variable in
settings.py
. And the LogoutView
class-based view looks for the template
registration/logout.html
, also under a directory
defined as part of the TEMPLATES/DIRS
variable in
settings.py
.
Tip See the book's accompanying source code for the layout and fields required by theregistration/login.html
andregistration/logout.html
templates.
The log in workflow offers two
configuration options in settings.py
that allow you to
modify its behavior without the need to customize the
LoginView
class-based view. The LOGIN_URL
variable defaults to /accounts/login/
and is used as
the url where users are redirected when they attempt to access
resources that require authentication and aren't logged in. The
LOGIC_REDIRECT
variable defaults to
/accounts/profile/
and is the the url where users are
redirected to after a successful log in. The
django.contrib.auth
package provides no entry point
for the /accounts/profile/
url, so you must either
configure this url or simply change it to a different location
(e.g. LOGIN_REDIRECT='/'
to redirect users to the home
page after a successful log in).
The log out workflow supports the
LOGOUT_REDIRECT
variable in settings.py
to define where users are taken when they log out. The
LOGOUT_REDIRECT
variable defaults to
/accounts/logout/
, but can be updated to redirect
users to a different location (e.g.
LOGOUT_REDIRECT='/'
to redirect users to the home page
after they log out).
Other than creating the
templates, setting up the urls -- described in listing 10-11 -- and
optionally changing the log in url location and log in/out redirect
behaviors, there is nothing else you need to do to enable the log
in and log out workflows provided by the
django.contrib.auth
package. If you want a user to log
in, simply point them to the /accounts/login/
url and
if you want them to log out point them to the
/accounts/logout/
url. All the other workflow details
(e.g. authentication, password verification, error form handling,
session expiration) are taken care of by the LoginView
and LogoutView
class-based views.
Password change workflow
The password change workflow
requires two url entry points. The
/accounts/password_change/
url which triggers the
PasswordChangeView
class-based view and the
/accounts/password_change/done/
url which triggers the
PasswordChangeDoneView
class-based view.
The
PasswordChangeView
class-based view looks for a
template containing a form to change passwords in
registration/password_change_form.html
under a
directory defined as part of the TEMPLATES/DIRS
variable in settings.py
. And the
PasswordChangeDoneView
class-based view looks for a
template containing a success message in
registration/password_change_done.html
, also under a
directory defined as part of the TEMPLATES/DIRS
variable in settings.py
.
Tip See the book's accompanying source code for the layout and fields required by theregistration/password_change_form.html
andregistration/password_change_done .html
templates.
Other than creating the templates
and setting up the urls -- described in listing 10-11 -- there is
nothing else you need to do to enable the password change workflow
provided by the django.contrib.auth
package. If you
want a user to change his password, simply point them to the
/accounts/password_change/
url. All the other workflow
details (e.g. password verification, database update, form error
handling) are taken care of by the PasswordChangeView
and PasswordChangeDoneView
class-based views.
Password reset workflow
The password reset workflow consists of two sub-workflows. The first sub-workflow captures a user's email and sends him an email with a link to reset his password. The second sub-workflow processes the reset link and validates a new password for the user.
The first sub-workflow uses the
/
accounts/password_reset/
url which
triggers the PasswordResetView
class-based view and
the /accounts/password_reset/done/
url which triggers
the PasswordResetDoneView
class-based view. The second
sub-workflow uses the /accounts/reset/
url which
triggers the PasswordResetConfirmView
class-based view
and the /accounts/reset/done/
url which triggers the
PasswordResetCompleteView
class-based view.
The
PasswordResetView
class-based view looks for a
template containing a form to reset a user's password in
registration/password_reset_form.html
and the
PasswordResetDoneView
class-based view looks for a
success message template in
registration/password_reset_done.html
, both under a
directory defined as part of the TEMPLATES/DIRS
variable in settings.py
. For the second sub-workflow,
the PasswordResetConfirmView
class-based view looks
for a template with a form to introduce a new user password in
registration/password_reset_confirm.html
and the
PasswordResetCompleteView
class-based view looks for a
success message template in
registration/password_reset_complete.html
, both under
a directory defined as part of the TEMPLATES/DIRS
variable in settings.py
.
By default, the password reset
workflow generates an email with instructions to take a user to the
second sub-workflow to reset his password. Also by default, the
reset email contains a link to the domain
localhost:8000
, which can be customized by installing
the Django site django.contrib.sites
app (e.g. add
django.contrib.sites
to INSTALLED_APPS
and SITE_ID=1
in settings.py
, and update
the site with id 1 in the Django admin to reflect a new domain).
Additionally, you can define a custom email layout in the
registration/password_reset_email.html
template and
place it under a directory defined as part of the
TEMPLATES
variable in settings.py
.
Tip See the book's accompanying source code for the layout and fields required by theregistration/password_reset_form.html,
registration/password_reset_done.html
,registration/password_reset_confirm.html
,registration/password_reset_complete.html
, andregistration/password_reset_email.html
templates.
Other than creating the templates
and setting up the urls -- described in listing 10-11 -- and
optionally changing the default email layout, there is nothing else
you need to do to enable to allow users to remember their passwords
using workflow provided by the django.contrib.auth
package. If you want a user to remember his password, simply point
them to the /accounts/password_reset/
url. All the
other workflow details (e.g. email token validation, password
verification, database update, form error handling) are taken care
of by the PasswordResetView
,
PasswordResetDoneView
,
PasswordResetConfirmView
and
PasswordResetCompleteView
class-based views.
User sign up workflow
Users can sign up automatically
to a Django application with the help a few constructs from the
django.contrib.auth
package. Although the user sign up
workflow is not as baked-in to the django.contrib.auth
package as the previous user related workflows, it's still easy to
create.
The first step to create a user sign up workflow is to configure a url entry point to allow users to create an account, as illustrated in listing 10-12.
Listing 10-12. Configure url for user sing up workflow.
# urls.py main from django.conf.urls import url from django.contrib.auth import views from coffeehouse.registration import views as registration_views urlpatterns = [ url(r'^accounts/', include('django.contrib.auth.urls')), url(r'^accounts/signup/$',registration_views.UserSignUp.as_view(),name="signup"), ]
Listing 10-12 illustrates a url
accessible at /accounts/signup/
, in addition to all
the urls provided by the django.contrib.auth
package
that include the previous user related workflows. Notice the
/accounts/signup/
url is set to be processed by the
UserSignUp
class-based view from the
coffeehouse.registration
app, which means it's a
class-based view you need to create for the project.
Listing 10-13 illustrates the
UserSignUp
class-based view tasked with the user sign
up workflow, which automates the creation of User
model records, that was previously done using the Django command
line tool or the Django admin.
Listing 10-13. Sign-up workflow fulfilled by custom CreateView class-based view.
from django.core.urlresolvers import reverse_lazy from django.http import HttpResponseRedirect from django.contrib.messages.views import SuccessMessageMixin from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.models import User from django.contrib.auth import authenticate, login class UserSignupForm(UserCreationForm): email = forms.EmailField(required=True) class Meta: model = User fields = ("username", "email", "password1", "password2") class UserSignUp(SuccessMessageMixin,CreateView): model = User form_class = UserSignupForm success_url = reverse_lazy('items:index') success_message = "User created successfully" template_name = "registration/signup.html" def form_valid(self, form): super(UserSignUp,self).form_valid(form) # The form is valid, automatically sign-in the user user = authenticate(self.request, username=form.cleaned_data['username'], password=form.cleaned_data['password1']) if user == None: # User not validated for some reason, return standard form_valid() response return self.render_to_response(self.get_context_data(form=form)) else: # Log the user in login(self.request, user) # Redirect to success url return HttpResponseRedirect(self.get_success_url())
The UserSignUp
class-based view in listing 10-13 is a standard
CreateView
class-based view that uses the mixin
SuccessMessageMixin
-- if you're unfamiliar with this
type of class-based view to create model records or the concept of
mixins, see the previous chapter which explains both these
topics.
The UserSignUp
class-based view sets the model option to User
to tell
Django to create django.contrib.auth.models.User
records. Next, the form_class
option is set to
UserSignupForm
which is also defined in listing 10-13.
Followed are the success_url
and
success_message
options to indicate a url and success
message when a User
record record is created, as well
as the template_name
to specify a template with the
form presented to capture the User
fields.
The custom
UserSignupForm
model form in listing 10-13 is based on
the UserCreationForm
from the
django.contrib.auth
package. From a practical
standpoint, the UserSignUp
class-based view could have
used UserCreationForm
as its form_class
,
however, this last built-in form only contains
username
and password
fields. In this
case, to solicit an email as part of the user sign up process, the
custom UserSignupForm
model form adds the
email
field to the base UserCreationForm
form class. Note that because the backing User
model
already includes an email
field, no modification is
required to the model.
In order to automatically sign in
users once a User
model record is created, the
UserSignUp
class-based view includes custom logic in
its form_valid()
method. In the case of listing 10-13,
the authenticate
method from the
django.contrib.auth
package is invoked with the form
values, verifying the credentials are valid. If the credentials are
valid -- which given the workflow should be 100% of the time,
unless an unforeseen hacking event takes place -- the
authenticate
method returns a User
instance and immediately executes the login
method --
also from the django.contrib.auth
package -- which
creates a session (i.e. signs in) the user, after which control is
redirect to the success page of the class-based view. In the
unforeseen event the authenticate
method doesn't
return a User
instance, the form_valid()
method returns its standard payload which is the validated
form.
As you can see from listing 10-12 and listing 10-13, you can create a user sign up workflow to let users create their own accounts and lift the administrative burden of creating user accounts from the Django admin or Django command line tool.
Tip The sign up workflow is listing 10-13 is
permissive in the sense it doesn't verify emails and automatically
signs users in. This was done for simplicity and may work for most
projects as is. But you can add extra safeguards to the sign up
workflow (e.g. send a verification link, set users to inactive
until verification link is clicked) by adding this logic to the
different class-based view methods, before or after the
User
record is created.
Tip The Django allauth package, described later in this chapter, has built-in support for email verification.