It’s time for a confession: we’ve been deliberately ignoring an important aspect of Web development prior to this point. So far, we’ve thought of the traffic visiting our sites as some faceless, anonymous mass hurtling itself against our carefully designed pages.
This isn’t true, of course. The browsers hitting our sites have real humans behind them (most of the time, at least). That’s a big thing to ignore: the Internet is at its best when it serves to connect people , not machines. If we’re going to develop truly compelling sites, eventually we’re going to have to deal with the bodies behind the browsers.
Unfortunately, it’s not all that easy. HTTP is designed to be stateless – that is, each and every request happens in a vacuum. There’s no persistence between one request and the next, and we can’t count on any aspects of a request (IP address, user agent, etc.) to consistently indicate successive requests from the same person.
In this chapter you’ll learn how to handle this lack of state. We’ll start at the lowest level ( cookies ), and work up to the high-level tools for handling sessions, users and registration.
With all of these limitations and potential security holes, it’s obvious that cookies and persistent sessions are examples of those “pain points” in Web development. Of course, Django’s goal is to be an effective painkiller, so it comes with a session framework designed to smooth over these difficulties for you.
This session framework lets you store and retrieve arbitrary data on a per-site visitor basis. It stores data on the server side and abstracts the sending and receiving of cookies. Cookies use only a hashed session ID – not the data itself – thus protecting you from most of the common cookie problems.
Let’s look at how to enable sessions and use them in views.
Sessions are implemented via a piece of middleware (see Chapter 17) and a Django model. To enable sessions, you’ll need to follow these steps:
Edit your
MIDDLEWARE_CLASSES
setting and make sure
MIDDLEWARE_CLASSES
contains
'django.contrib.sessions.middleware.SessionMiddleware'
.
Make sure
'django.contrib.sessions'
is in your
INSTALLED_APPS
setting (and run
manage.py
syncdb
if you have to add it).
The default skeleton settings created by
startproject
have both of these
bits already installed, so unless you’ve removed them, you probably don’t have
to change anything to get sessions to work.
If you don’t want to use sessions, you might want to remove the
SessionMiddleware
line from
MIDDLEWARE_CLASSES
and
'django.contrib.sessions'
from your
INSTALLED_APPS
. It will save
you only a small amount of overhead, but every little bit counts.
When
SessionMiddleware
is activated, each
HttpRequest
object – the
first argument to any Django view function – will have a
session
attribute, which is a dictionary-like object. You can read it and write to it
in the same way you’d use a normal dictionary. For example, in a view
you could do stuff like this:
# Set a session value:
request.session["fav_color"] = "blue"
# Get a session value -- this could be called in a different view,
# or many requests later (or both):
fav_color = request.session["fav_color"]
# Clear an item from the session:
del request.session["fav_color"]
# Check if the session has a given key:
if "fav_color" in request.session:
...
You can also use other dictionary methods like
keys()
and
items()
on
request.session
.
There are a couple of simple rules for using Django’s sessions effectively:
Use normal Python strings as dictionary keys on
request.session
(as
opposed to integers, objects, etc.).
Session dictionary keys that begin with an underscore are reserved for internal use by Django. In practice, the framework uses only a small number of underscore-prefixed session variables, but unless you know what they all are (and you are willing to keep up with any changes in Django itself), staying away from underscore prefixes will keep Django from interfering with your application.
For example, don’t use a session key called
_fav_color
, like
this:
request.session['_fav_color'] = 'blue' # Don't do this!
Don’t replace
request.session
with a new object, and don’t access or
set its attributes. Use it like a Python dictionary. Examples:
request.session = some_other_object # Don't do this!
request.session.foo = 'bar' # Don't do this!
Let’s take a look at a few quick examples. This simplistic view sets a
has_commented
variable to
True
after a user posts a comment. It’s a
simple (if not particularly secure) way of preventing a user from
posting more than one comment:
def post_comment(request):
if request.method != 'POST':
raise Http404('Only POSTs are allowed')
if 'comment' not in request.POST:
raise Http404('Comment not submitted')
if request.session.get('has_commented', False):
return HttpResponse("You've already commented.")
c = comments.Comment(comment=request.POST['comment'])
c.save()
request.session['has_commented'] = True
return HttpResponse('Thanks for your comment!')
This simplistic view logs in a “member” of the site:
def login(request):
if request.method != 'POST':
raise Http404('Only POSTs are allowed')
try:
m = Member.objects.get(username=request.POST['username'])
if m.password == request.POST['password']:
request.session['member_id'] = m.id
return HttpResponseRedirect('/you-are-logged-in/')
except Member.DoesNotExist:
return HttpResponse("Your username and password didn't match.")
And this one logs out a member who has been logged in via
login()
above:
def logout(request):
try:
del request.session['member_id']
except KeyError:
pass
return HttpResponse("You're logged out.")
Note
In practice, this is a lousy way of logging users in. The authentication framework discussed shortly handles this task for you in a much more robust and useful manner. These examples are deliberately simplistic so that you can easily see what’s going on.
Internally, each session is just a normal Django model defined in
django.contrib.sessions.models
. Each session is identified by a more-or-less
random 32-character hash stored in a cookie. Because it’s a normal model, you
can access sessions using the normal Django database API:
>>> from django.contrib.sessions.models import Session
>>> s = Session.objects.get(pk='2b1189a188b44ad18c35e113ac6ceead')
>>> s.expire_date
datetime.datetime(2005, 8, 20, 13, 35, 12)
You’ll need to call
get_decoded()
to get the actual session data. This is
necessary because the dictionary is stored in an encoded format:
>>> s.session_data
'KGRwMQpTJ19hdXRoX3VzZXJfaWQnCnAyCkkxCnMuMTExY2ZjODI2Yj...'
>>> s.get_decoded()
{'user_id': 42}
By default, Django only saves to the database if the session has been modified – that is, if any of its dictionary values have been assigned or deleted:
# Session is modified.
request.session['foo'] = 'bar'
# Session is modified.
del request.session['foo']
# Session is modified.
request.session['foo'] = {}
# Gotcha: Session is NOT modified, because this alters
# request.session['foo'] instead of request.session.
request.session['foo']['bar'] = 'baz'
To change this default behavior, set
SESSION_SAVE_EVERY_REQUEST
to
True
. If
SESSION_SAVE_EVERY_REQUEST
is
True
, Django
will save the session to the database on every single request, even if it
wasn’t changed.
Note that the session cookie is sent only when a session has been created or
modified. If
SESSION_SAVE_EVERY_REQUEST
is
True
, the session cookie
will be sent on every request. Similarly, the
expires
part of a session
cookie is updated each time the session cookie is sent.
You might have noticed that the cookie Google sent us at the beginning of this
chapter contained
expires=Sun,
17-Jan-2038
19:14:07
GMT;
. Cookies can
optionally contain an expiration date that advises the browser on when to
remove the cookie. If a cookie doesn’t contain an expiration value, the browser
will expire it when the user closes his or her browser window. You can control
the session framework’s behavior in this regard with the
SESSION_EXPIRE_AT_BROWSER_CLOSE
setting.
By default,
SESSION_EXPIRE_AT_BROWSER_CLOSE
is set to
False
, which means
session cookies will be stored in users’ browsers for
SESSION_COOKIE_AGE
seconds (which defaults to two weeks, or 1,209,600 seconds). Use this if you
don’t want people to have to log in every time they open a browser.
If
SESSION_EXPIRE_AT_BROWSER_CLOSE
is set to
True
, Django will use
browser-length cookies.
Besides the settings already mentioned, a few other settings influence how Django’s session framework uses cookies, as shown in Table 14-2.
Setting |
Description |
Default |
---|---|---|
|
The domain to use for session
cookies. Set this to a string
such as
|
|
|
The name of the cookie to use for sessions. This can be any string. |
|
|
Whether to use a “secure”
cookie for the session
cookie. If this is set to
|
|
Technical Details
For the curious, here are a few technical notes about the inner workings of the session framework:
The session dictionary accepts any Python object capable of being
“pickled.” See the documentation for Python’s built-in
pickle
module for information about how this works.
Session data is stored in a database table named
django_session
.
Session data is fetched upon demand. If you never access
request.session
, Django won’t hit that database table.
Django only sends a cookie if it needs to. If you don’t set any
session data, it won’t send a session cookie (unless
SESSION_SAVE_EVERY_REQUEST
is set to
True
).
The Django sessions framework is entirely, and solely, cookie based. It does not fall back to putting session IDs in URLs as a last resort, as some other tools (PHP, JSP) do.
This is an intentional design decision. Putting sessions in URLs
don’t just make URLs ugly, but also make your site vulnerable to a
certain form of session ID theft via the
Referer
header.
If you’re still curious, the source is pretty straightforward; look in
django.contrib.sessions
for more details.
Sessions give us a way of persisting data through multiple browser requests; the second part of the equation is using those sessions for user login. Of course, we can’t just trust that users are who they say they are, so we need to authenticate them along the way.
Naturally, Django provides tools to handle this common task (and many others). Django’s user authentication system handles user accounts, groups, permissions, and cookie-based user sessions. This system is often referred to as an auth/auth (authentication and authorization) system. That name recognizes that dealing with users is often a two-step process. We need to
Verify ( authenticate ) that a user is who he or she claims to be (usually by checking a username and password against a database of users)
Verify that the user is authorized to perform some given operation (usually by checking against a table of permissions)
Following these needs, Django’s auth/auth system consists of a number of parts:
Users : People registered with your site
Permissions : Binary (yes/no) flags designating whether a user may perform a certain task
Groups : A generic way of applying labels and permissions to more than one user
Messages : A simple way to queue and display system messages to users
If you’ve used the admin tool (discussed in Chapter 6), you’ve already seen many of these tools, and if you’ve edited users or groups in the admin tool, you’ve actually been editing data in the auth system’s database tables.
Like the session tools, authentication support is bundled as a Django
application in
django.contrib
that needs to be installed. Also like the
session tools, it’s also installed by default, but if you’ve removed it, you’ll
need to follow these steps to install it:
Make sure the session framework is installed as described earlier in this chapter. Keeping track of users obviously requires cookies, and thus builds on the session framework.
Put
'django.contrib.auth'
in your
INSTALLED_APPS
setting and
run
manage.py
syncdb
to install the appropriate database tables.
Make sure that
'django.contrib.auth.middleware.AuthenticationMiddleware'
is in
your
MIDDLEWARE_CLASSES
setting –
after
SessionMiddleware
.
With that installation out of the way, we’re ready to deal with users in view
functions. The main interface you’ll use to access users within a view is
request.user
; this is an object that represents the currently logged-in
user. If the user isn’t logged in, this will instead be an
AnonymousUser
object (see below for more details).
You can easily tell if a user is logged in with the
is_authenticated()
method:
if request.user.is_authenticated():
# Do something for authenticated users.
else:
# Do something for anonymous users.
Once you have a
User
– often from
request.user
, but possibly through
one of the other methods discussed shortly – you have a number of fields and
methods available on that object.
AnonymousUser
objects emulate
some
of
this interface, but not all of it, so you should always check
user.is_authenticated()
before assuming you’re dealing with a bona fide user
object. Tables 14-3 and 14-4 list the fields and methods, respectively, on
User
objects.
Field |
Description |
---|---|
|
Required; 30 characters or fewer. Alphanumeric characters only (letters, digits, and underscores). |
|
Optional; 30 characters or fewer. |
|
Optional; 30 characters or fewer. |
|
Optional. E-mail address. |
|
Required. A hash of, and metadata about, the password (Django doesn’t store the raw password). See the “Passwords” section for more about this value. |
|
Boolean. Designates whether this user can access the admin site. |
|
Boolean. Designates whether this account can be used
to log in. Set this flag to
|
|
Boolean. Designates that this user has all permissions without explicitly assigning them. |
|
A datetime of the user’s last login. This is set to the current date/time by default. |
|
A datetime designating when the account was created. This is set to the current date/time by default when the account is created. |
Method |
Description |
---|---|
|
Always returns
|
|
Returns
|
|
Returns the
|
|
Sets the user’s password to the given
raw string, taking care of the password
hashing. This doesn’t actually save the
|
|
Returns
|
|
Returns a list of permission strings that the user has through the groups he or she belongs to. |
|
Returns a list of permission strings that the user has, both through group and user permissions. |
|
Returns
|
|
Returns
|
|
Returns
|
|
Returns a list of
|
|
Sends an email to the user. This email
is sent from the
|
Finally,
User
objects have two many-to-many fields:
groups
and
permissions
.
User
objects can access their related objects in the same
way as any other many-to-many field:
# Set a user's groups:
myuser.groups = group_list
# Add a user to some groups:
myuser.groups.add(group1, group2,...)
# Remove a user from some groups:
myuser.groups.remove(group1, group2,...)
# Remove a user from all groups:
myuser.groups.clear()
# Permissions work the same way
myuser.permissions = permission_list
myuser.permissions.add(permission1, permission2, ...)
myuser.permissions.remove(permission1, permission2, ...)
myuser.permissions.clear()
Django provides built-in view functions for handling logging in and out (and a
few other nifty tricks), but before we get to those, let’s take a look at how
to log users in and out “by hand.” Django provides two functions to perform
these actions in
django.contrib.auth
:
authenticate()
and
login()
.
To authenticate a given username and password, use
authenticate()
. It
takes two keyword arguments,
username
and
password
, and it returns a
User
object if the password is valid for the given username. If the
password is invalid,
authenticate()
returns
None
:
>>> from django.contrib import auth
>>> user = auth.authenticate(username='john', password='secret')
>>> if user is not None:
... print "Correct!"
... else:
... print "Invalid password."
authenticate()
only verifies a user’s credentials. To log in a user, use
login()
. It takes an
HttpRequest
object and a
User
object and saves
the user’s ID in the session, using Django’s session framework.
This example shows how you might use both
authenticate()
and
login()
within a view function:
from django.contrib import auth
def login_view(request):
username = request.POST.get('username', '')
password = request.POST.get('password', '')
user = auth.authenticate(username=username, password=password)
if user is not None and user.is_active:
# Correct password, and the user is marked "active"
auth.login(request, user)
# Redirect to a success page.
return HttpResponseRedirect("/account/loggedin/")
else:
# Show an error page
return HttpResponseRedirect("/account/invalid/")
To log out a user, use
django.contrib.auth.logout()
within your view. It
takes an
HttpRequest
object and has no return value:
from django.contrib import auth
def logout_view(request):
auth.logout(request)
# Redirect to a success page.
return HttpResponseRedirect("/account/loggedout/")
Note that
auth.logout()
doesn’t throw any errors if the user wasn’t logged
in.
In practice, you usually will not need to write your own login/logout functions; the authentication system comes with a set of views for generically handling logging in and out. The first step in using these authentication views is to wire them up in your URLconf. You’ll need to add this snippet:
from django.contrib.auth.views import login, logout
urlpatterns = patterns('',
# existing patterns here...
(r'^accounts/login/$', login),
(r'^accounts/logout/$', logout),
)
/accounts/login/
and
/accounts/logout/
are the default URLs that
Django uses for these views.
By default, the
login
view renders a template at
registration/login.html
(you can change this template name by passing an
extra view argument ,``template_name``). This form needs to contain a
username
and a
password
field. A simple template might look like this:
{% extends "base.html" %}
{% block content %}
{% if form.errors %}
<p class="error">Sorry, that's not a valid username or password</p>
{% endif %}
<form action="" method="post">
<label for="username">User name:</label>
<input type="text" name="username" value="" id="username">
<label for="password">Password:</label>
<input type="password" name="password" value="" id="password">
<input type="submit" value="login" />
<input type="hidden" name="next" value="{{ next|escape }}" />
</form>
{% endblock %}
If the user successfully logs in, he or she will be redirected to
/accounts/profile/
by default. You can override this by providing a hidden
field called
next
with the URL to redirect to after logging in. You can
also pass this value as a
GET
parameter to the login view and it will be
automatically added to the context as a variable called
next
that you can
insert into that hidden field.
The logout view works a little differently. By default it renders a template
at
registration/logged_out.html
(which usually contains a “You’ve
successfully logged out” message). However, you can call the view with an
extra argument,
next_page
, which will instruct the view to redirect after
a logout.
Of course, the reason we’re going through all this trouble is so we can limit access to parts of our site.
The simple, raw way to limit access to pages is to check
request.user.is_authenticated()
and redirect to a login page:
from django.http import HttpResponseRedirect
def my_view(request):
if not request.user.is_authenticated():
return HttpResponseRedirect('/accounts/login/?next=%s' % request.path)
# ...
or perhaps display an error message:
def my_view(request):
if not request.user.is_authenticated():
return render(request, 'myapp/login_error.html')
# ...
As a shortcut, you can use the convenient
login_required
decorator:
from django.contrib.auth.decorators import login_required
@login_required
def my_view(request):
# ...
login_required
does the following:
If the user isn’t logged in, redirect to
/accounts/login/
, passing
the current URL path in the query string as
next
, for example:
/accounts/login/?next=/polls/3/
.
If the user is logged in, execute the view normally. The view code can then assume that the user is logged in.
Limiting access based on certain permissions or some other test, or providing a different location for the login view works essentially the same way.
The raw way is to run your test on
request.user
in the view directly. For
example, this view checks to make sure the user is logged in and has the
permission
polls.can_vote
(more about how permissions
works follows):
def vote(request):
if request.user.is_authenticated() and request.user.has_perm('polls.can_vote')):
# vote here
else:
return HttpResponse("You can't vote in this poll.")
Again, Django provides a shortcut called
user_passes_test
. It
takes arguments and generates a specialized decorator for your particular
situation:
def user_can_vote(user):
return user.is_authenticated() and user.has_perm("polls.can_vote")
@user_passes_test(user_can_vote, login_url="/login/")
def vote(request):
# Code here can assume a logged-in user with the correct permission.
...
user_passes_test
takes one required argument: a callable that takes a
User
object and returns
True
if the user is allowed to view the page.
Note that
user_passes_test
does not automatically check that the
User
is authenticated; you should do that yourself.
In this example we’re also showing the second (optional) argument,
login_url
, which lets you specify the URL for your login page
(
/accounts/login/
by default). If the user doesn’t pass the test, then
the
user_passes_test
decorator will redirect the user to the
login_url
.
Because it’s a relatively common task to check whether a user has a particular
permission, Django provides a shortcut for that case: the
permission_required()
decorator. Using this decorator, the earlier example
can be written as follows:
from django.contrib.auth.decorators import permission_required
@permission_required('polls.can_vote', login_url="/login/")
def vote(request):
# ...
Note that
permission_required()
also takes an optional
login_url
parameter, which also defaults to
'/accounts/login/'
.
Limiting Access to Generic Views
One of the most frequently asked questions on the Django users list deals with limiting access to a generic view. To pull this off, you’ll need to write a thin wrapper around the view and point your URLconf to your wrapper instead of the generic view itself:
from django.contrib.auth.decorators import login_required
from django.views.generic.date_based import object_detail
@login_required
def limited_object_detail(*args, **kwargs):
return object_detail(*args, **kwargs)
You can, of course, replace
login_required
with any of the other
limiting decorators.
The easiest way by far to manage the auth system is through the admin interface. Chapter 6 discusses how to use Django’s admin site to edit users and control their permissions and access, and most of the time you’ll just use that interface.
However, there are low-level APIs you can dive into when you need absolute control, and we discuss these in the sections that follow.
Create users with the
create_user
helper function:
>>> from django.contrib.auth.models import User
>>> user = User.objects.create_user(username='john',
... email='jlennon@beatles.com',
... password='glass onion')
At this point,
user
is a
User
instance ready to be saved to the database
(
create_user()
doesn’t actually call
save()
itself). You can continue to
change its attributes before saving, too:
>>> user.is_staff = True
>>> user.save()
You can change a password with
set_password()
:
>>> user = User.objects.get(username='john')
>>> user.set_password('goo goo goo joob')
>>> user.save()
Don’t set the
password
attribute directly unless you know what you’re
doing. The password is actually stored as a
salted hash
and thus can’t be
edited directly.
More formally, the
password
attribute of a
User
object is a string in
this format:
hashtype$salt$hash
That’s a hash type, the salt, and the hash itself, separated by the dollar sign ($) character.
hashtype
is either
sha1
(default) or
md5
, the algorithm used to
perform a one-way hash of the password.
salt
is a random string used to salt
the raw password to create the hash, for example:
sha1$a1976$a36cc8cbf81742a8fb52e221aaeab48ed7f58ab4
The
User.set_password()
and
User.check_password()
functions handle the
setting and checking of these values behind the scenes.
Salted hashes
A hash is a one-way cryptographic function – that is, you can easily compute the hash of a given value, but it’s nearly impossible to take a hash and reconstruct the original value.
If we stored passwords as plain text, anyone who got their hands on the password database would instantly know everyone’s password. Storing passwords as hashes reduces the value of a compromised database.
However, an attacker with the password database could still run a brute- force attack, hashing millions of passwords and comparing those hashes against the stored values. This takes some time, but less than you might think.
Worse, there are publicly available rainbow tables , or databases of pre-computed hashes of millions of passwords. With a rainbow table, an experienced attacker could break most passwords in seconds.
Adding a salt – basically an initial random value – to the stored hash adds another layer of difficulty to breaking passwords. Because salts differ from password to password, they also prevent the use of a rainbow table, thus forcing attackers to fall back on a brute-force attack, itself made more difficult by the extra entropy added to the hash by the salt.
While salted hashes aren’t absolutely the most secure way of storing passwords, they’re a good middle ground between security and convenience.
We can use these low-level tools to create views that allow users to sign up for new accounts. Different developers implement registration differently, so Django leaves writing a registration view up to you. Luckily, it’s pretty easy.
At its simplest, we could provide a small view that prompts for the required user information and creates those users. Django provides a built-in form you can use for this purpose, which we’ll use in this example:
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.http import HttpResponseRedirect
from django.shortcuts import render
def register(request):
if request.method == 'POST':
form = UserCreationForm(request.POST)
if form.is_valid():
new_user = form.save()
return HttpResponseRedirect("/books/")
else:
form = UserCreationForm()
return render(request, "registration/register.html", {
'form': form,
})
This form assumes a template named
registration/register.html
. Here’s an
example of what that template might look like:
{% extends "base.html" %}
{% block title %}Create an account{% endblock %}
{% block content %}
<h1>Create an account</h1>
<form action="" method="post">
{{ form.as_p }}
<input type="submit" value="Create the account">
</form>
{% endblock %}
The current logged-in user and his or her permissions are made available in the
template context when you use the
render()
shortcut or explicitly use a
RequestContext
(see Chapter 9).
Note
Technically, these variables are only made available in the template
context if you use
RequestContext
and
your
TEMPLATE_CONTEXT_PROCESSORS
setting contains
"django.core.context_processors.auth"
, which is the default. Again, see
Chapter 9 for more information.
When using
RequestContext
, the current user (either a
User
instance
or an
AnonymousUser
instance) is stored in the template variable
{{
user
}}
:
{% if user.is_authenticated %}
<p>Welcome, {{ user.username }}. Thanks for logging in.</p>
{% else %}
<p>Welcome, new user. Please log in.</p>
{% endif %}
This user’s permissions are stored in the template variable
{{
perms
}}
.
This is a template-friendly proxy to a couple of permission methods described
shortly.
There are two ways you can use this
perms
object. You can use something like
{%
if
perms.polls
%}
to check if the user has
any
permissions for some given
application, or you can use something like
{%
if
perms.polls.can_vote
%}
to
check if the user has a specific permission.
Thus, you can check permissions in template
{%
if
%}
statements:
{% if perms.polls %}
<p>You have permission to do something in the polls app.</p>
{% if perms.polls.can_vote %}
<p>You can vote!</p>
{% endif %}
{% else %}
<p>You don't have permission to do anything in the polls app.</p>
{% endif %}
There are a few other bits of the authentication framework that we’ve only dealt with in passing. We’ll take a closer look at them in the following sections.
Permissions are a simple way to “mark” users and groups as being able to perform some action. They are usually used by the Django admin site, but you can easily use them in your own code.
The Django admin site uses permissions as follows:
Access to view the “add” form, and add an object is limited to users with the add permission for that type of object.
Access to view the change list, view the “change” form, and change an object is limited to users with the change permission for that type of object.
Access to delete an object is limited to users with the delete permission for that type of object.
Permissions are set globally per type of object, not per specific object instance. For example, it’s possible to say “Mary may change news stories,” but permissions don’t let you say “Mary may change news stories, but only the ones she created herself” or “Mary may only change news stories that have a certain status, publication date, or ID.”
These three basic permissions – add, change, and delete – are automatically
created for each Django model. Behind the scenes, these permissions are added
to the
auth_permission
database table when you run
manage.py
syncdb
.
These permissions will be of the form
"<app>.<action>_<object_name>"
. That
is, if you have a
polls
application with a
Choice
model, you’ll get
permissions named
"polls.add_choice"
,
"polls.change_choice"
, and
"polls.delete_choice"
.
Just like users, permissions are implemented in a Django model that lives in
django.contrib.auth.models
. This means that you can use Django’s database
API to interact directly with permissions if you like.
Groups are a generic way of categorizing users so you can apply permissions, or some other label, to those users. A user can belong to any number of groups.
A user in a group automatically has the permissions granted to that group. For
example, if the group
Site
editors
has the permission
can_edit_home_page
, any user in that group will have that permission.
Groups are also a convenient way to categorize users to give them some label, or
extended functionality. For example, you could create a group
'Special
users'
, and you could write code that could, say, give those users access to a
members-only portion of your site, or send them members-only e-mail messages.
Like users, the easiest way to manage groups is through the admin interface.
However, groups are also just Django models that live in
django.contrib.auth.models
, so once again you can always use Django’s
database APIs to deal with groups at a low level.
The message system is a lightweight way to queue messages for given users. A
message is associated with a
User
. There’s no concept of expiration or
timestamps.
Messages are used by the Django admin interface after successful actions. For example, when you create an object, you’ll notice a “The object was created successfully” message at the top of the admin page.
You can use the same API to queue and display messages in your own application. The API is simple:
To create a new message, use
user.message_set.create(message='message_text')
.
To retrieve/delete messages, use
user.get_and_delete_messages()
,
which returns a list of
Message
objects in the user’s queue (if any)
and deletes the messages from the queue.
In this example view, the system saves a message for the user after creating a playlist:
def create_playlist(request, songs):
# Create the playlist with the given songs.
# ...
request.user.message_set.create(
message="Your playlist was added successfully."
)
return render(request, "playlists/create.html")
When you use the
render()
shortcut or render a template with a
RequestContext
, the current logged-in user and his or her messages are made
available in the template context as the template variable
{{
messages
}}
.
Here’s an example of template code that displays messages:
{% if messages %}
<ul>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
Note that
RequestContext
calls
get_and_delete_messages
behind the
scenes, so any messages will be deleted even if you don’t display them.
Finally, note that this messages framework only works with users in the user database. To send messages to anonymous users, use the session framework directly.
The session and authorization system is a lot to absorb. Most of the time, you won’t need all the features described in this chapter, but when you need to allow complex interactions between users, it’s good to have all that power available.
In the next chapter , we’ll take a look at Django’s caching infrastructure, which is a convenient way to improve the performance of your application.