Django by Example - Sample Chapter
Django by Example - Sample Chapter
ee
$ 44.99 US
28.99 UK
P U B L I S H I N G
Antonio Mel
Django By Example
Django By Example
pl
C o m m u n i t y
E x p e r i e n c e
D i s t i l l e d
Django By Example
Create your own line of successful web applications with Django
Sa
m
Antonio Mel
Antonio has also worked as a CTO for several technology-based start-ups. His father
inspired his passion for computers and programming.
Preface
Django is a powerful Python web framework that encourages rapid development
and clean, pragmatic design, offering a relatively shallow learning curve. This makes
it attractive to both novice and expert programmers.
This book will guide you through the entire process of developing professional web
applications with Django. The book not only covers the most relevant aspects of the
framework, it also teaches you how to integrate other popular technologies into your
Django projects.
The book will walk you through the creation of real-world applications, solving
common problems, and implementing best practices with a step-by-step approach
that is easy to follow.
After reading this book, you will have a good understanding of how Django works
and how to build practical, advanced web applications.
Preface
Chapter 3, Extending Your Blog Application explores how to create custom template
tags and filters. The chapter will also show you how to use the sitemap framework
and create an RSS feed for your posts. You will complete your blog application by
building a search engine with Solr.
Chapter 4, Building a Social Website explains how to build a social website. You will
use the Django authentication framework to create user account views. You will
learn how to create a custom user profile model and build social authentication into
your project using major social networks.
Chapter 5, Sharing Content in Your Website teaches you how to transform your social
application into an image bookmarking website. You will define many-to-many
relationships for models, and you will create an AJAX bookmarklet in JavaScript
and integrate it into your project. The chapter will show you how to generate image
thumbnails and create custom decorators for your views.
Chapter 6, Tracking User Actions shows you how to build a follower system for users.
You will complete your image bookmarking website by creating a user activity
stream application. You will learn how to optimise QuerySets, and you will work
with signals. You will integrate Redis into your project to count image views.
Chapter 7, Building an Online Shop explores how to create an online shop. You will
build the catalog models, and you will create a shopping cart using Django sessions.
You will learn to manage customer orders and send asynchronous notifications to
users using Celery.
Chapter 8, Managing Payments and Orders explains you how to integrate a payment
gateway into your shop and handle payment notifications. You will also customize
the administration site to export orders to CSV files, and you will generate PDF
invoices dynamically.
Chapter 9, Extending Your Shop teaches you how to create a coupon system to apply
discounts to orders. The chapter will show you how to add internationalization to
your project and
how to translate models. You will also build a product recommendation engine
using Redis.
Chapter 10, Building an e-Learning Platform guides you through creating an e-learning
platform. You will add fixtures to your project, use model inheritance, create custom
model fields, use class-based views, and manage groups and permissions. You will
create a content management system and handle formsets.
Chapter 11, Caching Content shows you how to create a student registration system and
manage student enrollment in courses. You will render diverse course contents and
you will learn how to use the cache framework.
Preface
Chapter 12, Building an API explores building a RESTful API for your project using
the Django REST framework.
[ 169 ]
We are using the user model provided by Django and we want to avoid
altering it
Edit the models.py file of your account application and add the following code to it:
from django.contrib.auth.models import User
class Contact(models.Model):
user_from = models.ForeignKey(User,
related_name='rel_from_set')
user_to = models.ForeignKey(User,
related_name='rel_to_set')
created = models.DateTimeField(auto_now_add=True,
db_index=True)
class Meta:
ordering = ('-created',)
def __str__(self):
return '{} follows {}'.format(self.user_from,
self.user_to)
This is the Contact model we will use for user relationships. It contains the
following fields:
Chapter 6
The related managers rel_from_set and rel_to_set will return a QuerySet for
the Contact model. In order to access the end side of the relationship from the User
model, it would be desirable that User contained a ManyToManyField as follows:
following = models.ManyToManyField('self',
through=Contact,
related_name='followers',
symmetrical=False)
In this example, we tell Django to use our custom intermediary model for the
relationship by adding through=Contact to the ManyToManyField. This is a
many-to-many relationship from the User model to itself: We refer to 'self'
in the ManyToManyField field to create a relationship to the same model.
When you need additional fields in a many-to-many relationship,
create a custom model with a ForeignKey for each side of the
relationship. Add a ManyToManyField in one of the related
models and indicate Django to use your intermediary model by
including it in the through parameter.
If the User model was part of our application, we could add the previous field to
the model. However, we cannot alter the User class directly because it belongs to
the django.contrib.auth application. We are going to take a slightly different
approach, by adding this field dynamically to the User model. Edit the models.py
file of the account application and add the following lines:
# Add following field to User dynamically
User.add_to_class('following',
models.ManyToManyField('self',
through=Contact,
related_name='followers',
symmetrical=False))
[ 171 ]
We simplify the way we retrieve related objects using the Django ORM
with user.followers.all() and user.following.all(). We use the
intermediary Contact model and avoid complex queries that would involve
additional database joins, as it would have been if we had defined the
relationship in our custom Profile model.
The table for this many-to-many relationship will be created using the
Keep in mind that in most cases, it is preferable to add fields to the Profile model
we created before, instead of monkey-patching the User model. Django also allows
you to use custom user models. If you want to use your custom user model, take a
look at the documentation at https://docs.djangoproject.com/en/1.8/topics/
auth/customizing/#specifying-a-custom-user-model.
You can see that the relationship includes symmetrical=False. When you define a
ManyToManyField to the model itself, Django forces the relationship to be symmetrical.
In this case, we are setting symmetrical=False to define a non-symmetric relation.
This is, if I follow you, it doesn't mean you automatically follow me.
Run the following command to generate the initial migrations for the account
application:
python manage.py makemigrations account
[ 172 ]
Chapter 6
Now run the following command to sync the application with the database:
python manage.py migrate account
The Contact model is now synced to the database and we are able to create
relationships between users . However, our site doesn't offer a way to browse
through users or see a particular user profile yet. Let's build list and detail
views for the User model.
These are simple list and detail views for User objects. The user_list view gets all
active users. The Django User model contains a flag is_active to designate whether
the user account is considered active. We filter the query by is_active=True to
return only active users. This view returns all results, but you can improve it by
adding pagination the same way we did for the image_list view.
[ 173 ]
We are going to use the user_detail URL pattern to generate the canonical URL
for users. You have already defined a get_absolute_url() method in a model to
return the canonical URL for each object. Another way to specify an URL for a model
is by adding the ABSOLUTE_URL_OVERRIDES setting to your project.
Edit the settings.py file of your project and add the following code to it:
ABSOLUTE_URL_OVERRIDES = {
'auth.user': lambda u: reverse_lazy('user_detail',
args=[u.username])
}
The returned URL is as expected. We need to create templates for the views we just
built. Add the following directory and files to the templates/account/ directory of
the account application:
/user/
detail.html
list.html
[ 174 ]
Chapter 6
Edit the account/user/list.html template and add the following code to it:
{% extends "base.html" %}
{% load thumbnail %}
{% block title %}People{% endblock %}
{% block content %}
<h1>People</h1>
<div id="people-list">
{% for user in users %}
<div class="user">
<a href="{{ user.get_absolute_url }}">
{% thumbnail user.profile.photo "180x180" crop="100%" as im
%}
<img src="{{ im.url }}">
{% endthumbnail %}
</a>
<div class="info">
<a href="{{ user.get_absolute_url }}" class="title">
{{ user.get_full_name }}
</a>
</div>
</div>
{% endfor %}
</div>
{% endblock %}
This template allows us to list all the active users in the site. We iterate over the given
users and use sorl-thumbnail's {% thumbnail %} template tag to generate profile
image thumbnails.
Open the base.html template of your project and include the user_list URL in the
href attribute of the following menu item:
<li {% if section == "people" %}class="selected"{% endif %}><a
href="{% url "user_list" %}">People</a></li>
[ 175 ]
Start the development server with the command python manage.py runserver and
open http://127.0.0.1:8000/account/users/ in your browser. You should see a
list of users like the following one:
[ 176 ]
Chapter 6
Follow
{% else %}
Unfollow
{% endif %}
</a>
<div id="image-list" class="image-container">
{% include "images/image/list_ajax.html" with images=user.
images_created.all %}
</div>
{% endwith %}
{% endblock %}
In the detail template we display the user profile and we use the {% thumbnail %}
template tag to display the profile image. We show the total number of followers and
a link to follow/unfollow the user. We prevent users from following themselves
by hiding this link if the user is watching their own profile. We are going to perform
an AJAX request to follow/unfollow a particular user. We add data-id and dataaction attributes to the <a> HTML element including the user ID and the initial
action to perform when it's clicked, follow or unfollow, that depends on the user
requesting the page being or not a follower of this user. We display the images
bookmarked by the user with the list_ajax.html template.
Open your browser again and click on a user that has bookmarked some images.
You will see a profile detail like the following one:
[ 177 ]
@ajax_required
@require_POST
@login_required
def user_follow(request):
user_id = request.POST.get('id')
action = request.POST.get('action')
if user_id and action:
try:
user = User.objects.get(id=user_id)
if action == 'follow':
Contact.objects.get_or_create(
user_from=request.user,
user_to=user)
else:
Contact.objects.filter(user_from=request.user,
user_to=user).delete()
return JsonResponse({'status':'ok'})
except User.DoesNotExist:
return JsonResponse({'status':'ko'})
return JsonResponse({'status':'ko'})
The user_follow view is quite similar to the image_like view we created before.
Since we are using a custom intermediary model for the users' many-to-many
relationship, the default add() and remove() methods of the automatic manager
of ManyToManyField are not available. We use the intermediary Contact model
to create or delete user relationships.
Import the view you just created in the urls.py file of the account application and
add the following URL pattern to it:
url(r'^users/follow/$', views.user_follow, name='user_follow'),
[ 178 ]
Chapter 6
Make sure that you place this pattern before the user_detail URL pattern.
Otherwise, any requests to /users/follow/ will match the regular expression of
the user_detail pattern and it will be executed instead. Remember that in every
HTTP request Django checks the requested URL against each pattern in order of
appearance and stops at the first match.
Edit the user/detail.html template of the account application and append the
following code to it:
{% block domready %}
$('a.follow').click(function(e){
e.preventDefault();
$.post('{% url "user_follow" %}',
{
id: $(this).data('id'),
action: $(this).data('action')
},
function(data){
if (data['status'] == 'ok') {
var previous_action = $('a.follow').data('action');
// toggle data-action
$('a.follow').data('action',
previous_action == 'follow' ? 'unfollow' : 'follow');
// toggle link text
$('a.follow').text(
previous_action == 'follow' ? 'Unfollow' : 'Follow');
// update total followers
var previous_followers = parseInt(
$('span.count .total').text());
$('span.count .total').text(previous_action == 'follow' ?
previous_followers + 1 : previous_followers - 1);
}
}
);
});
{% endblock %}
This is the JavaScript code to perform the AJAX request to follow or unfollow a
particular user and also toggle the follow/unfollow link. We use jQuery to perform
the AJAX request and set both the data-action attribute and the text of the HTML
<a> element based on its previous value. When the AJAX action is performed, we also
update the count of total followers displayed on the page. Open the user detail page
of an existing user and click the Follow link to try the functionality we just built.
[ 179 ]
Edit the models.py file of the actions application and add the following code to it:
from django.db import models
from django.contrib.auth.models import User
class Action(models.Model):
user = models.ForeignKey(User,
related_name='actions',
db_index=True)
verb = models.CharField(max_length=255)
created = models.DateTimeField(auto_now_add=True,
db_index=True)
class Meta:
ordering = ('-created',)
[ 180 ]
Chapter 6
This is the Action model that will be used for storing user activities. The fields of
this model are as follows:
user: The user that performed the action. This is a ForeignKey to the Django
User model.
verb: The verb describing the action that the user has performed.
created: The date and time when this action was created. We use auto_now_
add=True to automatically set this to the current datetime when the object is
With this basic model, we can only store actions such as User X did something. We
need an extra ForeignKey field in order to save actions that involve a target object,
such as User X bookmarked image Y or User X is now following user Y. As you
already know, a normal ForeignKey can only point to one other model. Instead,
we need a way for the action's target object to be an instance of any existing model.
This is where the Django contenttypes framework comes on the scene.
app_label: The name of the application the model belongs to. This is
automatically taken from the app_label attribute of the model Meta options.
For example, our Image model belongs to the application images.
[ 181 ]
Let's take a look at how we can interact with ContentType objects. Open the
Python console using the python manage.py shell command. You can get the
ContentType object corresponding to a specific model by performing a query
with the app_label and model attributes such as this:
>>> from django.contrib.contenttypes.models import ContentType
>>> image_type = ContentType.objects.get(app_label='images',
model='image')
>>> image_type
<ContentType: image>
You can also retrieve the model class back from a ContentType object by calling its
model_class() method:
>>> image_type.model_class()
<class 'images.models.Image'>
It's also common to get the ContentType object for a particular model class as
follows:
>>> from images.models import Image
>>> ContentType.objects.get_for_model(Image)
<ContentType: image>
These are just some examples of using contenttypes. Django offers more ways to work
with them. You can find the official documentation about the contenttypes framework
at https://docs.djangoproject.com/en/1.8/ref/contrib/contenttypes/.
A ForeignKey field to ContentType. This will tell us the model for the
relationship.
A field to store the primary key of the related object. This will usually be a
PositiveIntegerField to match Django automatic primary key fields.
A field to define and manage the generic relation using the two previous
fields. The contenttypes framework offers a GenericForeignKey field for
this purpose.
[ 182 ]
Chapter 6
Edit the models.py file of the actions application and make it look like this:
from
from
from
from
class Action(models.Model):
user = models.ForeignKey(User,
related_name='actions',
db_index=True)
verb = models.CharField(max_length=255)
target_ct = models.ForeignKey(ContentType,
blank=True,
null=True,
related_name='target_obj')
target_id = models.PositiveIntegerField(null=True,
blank=True,
db_index=True)
target = GenericForeignKey('target_ct', 'target_id')
created = models.DateTimeField(auto_now_add=True,
db_index=True)
class Meta:
ordering = ('-created',)
related object.
Django does not create any field in the database for GenericForeignKey fields. The
only fields that are mapped to database fields are target_ct and target_id. Both
fields have blank=True and null=True attributes so that a target object is not
required when saving Action objects.
You can make your applications more flexible by using generic
relationships instead of foreign-keys when it makes sense to have
a generic relation.
[ 183 ]
Run the following command to create initial migrations for this application:
python manage.py makemigrations actions
Then, run the next command to sync the application with the database:
python manage.py migrate
The output of the command should indicate that the new migrations have been
applied as follows:
Applying actions.0001_initial... OK
Let's add the Action model to the administration site. Edit the admin.py file of the
actions application and add the following code to it:
from django.contrib import admin
from .models import Action
class ActionAdmin(admin.ModelAdmin):
list_display = ('user', 'verb', 'target', 'created')
list_filter = ('created',)
search_fields = ('verb',)
admin.site.register(Action, ActionAdmin)
You just registered the Action model in the administration site. Run the command
python manage.py runserver to initialize the development server and open
http://127.0.0.1:8000/admin/actions/action/add/ in your browser. You
should see the page for creating a new Action object as follows:
[ 184 ]
Chapter 6
As you can see, only the target_ct and target_id fields that are mapped to actual
database fields are shown, and the GenericForeignKey field does not appear here.
The target_ct allows you to select any of the registered models of your Django
project. You can restrict the contenttypes to choose from to a limited set of models
by using the limit_choices_to attribute in the target_ct field: The limit_
choices_to attribute allows you to restrict the content of ForeignKey fields to a
specific set of values.
Create a new file inside the actions application directory and name it utils.py.
We will define a shortcut function that will allow us to create new Action objects in
a simple way. Edit the new file and add the following code to it:
from django.contrib.contenttypes.models import ContentType
from .models import Action
def create_action(user, verb, target=None):
action = Action(user=user, verb=verb, target=target)
action.save()
[ 185 ]
First, we get the current time using the timezone.now() method provided
by Django. This method does the same as datetime.datetime.now() but
returns a timezone-aware object. Django provides a setting called USE_TZ
to enable or disable timezone support. The default settings.py file created
using the startproject command includes USE_TZ=True.
[ 186 ]
Chapter 6
We use the last_minute variable to store the datetime one minute ago and
we retrieve any identical actions performed by the user since then.
Edit the views.py file of the images application and add the following import:
from actions.utils import create_action
In the image_create view, add create_action() after saving the image like this:
new_item.save()
create_action(request.user, 'bookmarked image', new_item)
In the image_like view, add create_action() after adding the user to the
users_like relationship as follows:
image.users_like.add(request.user)
create_action(request.user, 'likes', image)
Now edit the views.py file of the account application and add the following import:
from actions.utils import create_action
In the register view, add create_action() after creating the Profile object
as follows:
new_user.save()
profile = Profile.objects.create(user=new_user)
create_action(new_user, 'has created an account')
[ 187 ]
As you can see, thanks to our Action model and our helper function, it's very easy
to save new actions to the activity stream.
In this view, we retrieve all actions from the database, excluding the ones performed
by the current user. If the user is not following anybody yet, we display the latest
actions performed by other users on the platform. This is the default behavior when
the user is not following any other users yet. If the user is following other users, we
restrict the query to only display actions performed by the users he follows. Finally,
we limit the result to the first 10 actions returned. We are not using order_by() here
because we are relying on the default ordering we provided in the Meta options
of the Action model. Recent actions will come first, since we have set ordering =
('-created',) in the Action model.
[ 188 ]
Chapter 6
Using select_related
Django offers a QuerySet method called select_related() that allows you to
retrieve related objects for one-to-many relationships. This translates to a single,
more complex QuerySet, but you avoid additional queries when accessing the
related objects. The select_related method is for ForeignKey and OneToOne fields.
It works by performing a SQL JOIN and including the fields of the related object
in the SELECT statement.
To take advantage of select_related(), edit the following line of the previous code:
actions = actions.filter(user_id__in=following_ids)
We are using user__profile to join the profile table too in one single SQL query.
If you call select_related() without passing any arguments to it, it will retrieve
objects from all ForeignKey relationships. Always limit select_related() to the
relationships that will be accessed afterwards.
Using select_related() carefully can vastly improve execution time.
Using prefetch_related
As you see, select_related() will help you boost performance for retrieving related
objects in one-to-many relationships. However, select_related() cannot work for
many-to-many or many-to-one relationships (ManyToMany or reverse ForeignKey
fields). Django offers a different QuerySet method called prefetch_related that
works for many-to-many and many-to-one relations in addition to the relations
supported by select_related(). The prefetch_related() method performs a
separate lookup for each relationship and joins the results using Python. This method
also supports prefetching of GenericRelation and GenericForeignKey.
[ 189 ]
This query is now optimized for retrieving the user actions including related objects.
Edit the actions/action/detail.html template file and add the following lines
to it:
{% load thumbnail %}
{% with user=action.user profile=action.user.profile %}
<div class="action">
<div class="images">
{% if profile.photo %}
{% thumbnail user.profile.photo "80x80" crop="100%" as im %}
<a href="{{ user.get_absolute_url }}">
<img src="{{ im.url }}" alt="{{ user.get_full_name }}"
class="item-img">
</a>
{% endthumbnail %}
{% endif %}
{% if action.target %}
{% with target=action.target %}
{% if target.image %}
{% thumbnail target.image "80x80" crop="100%" as im %}
<a href="{{ target.get_absolute_url }}">
<img src="{{ im.url }}" class="item-img">
</a>
{% endthumbnail %}
{% endif %}
[ 190 ]
Chapter 6
{% endwith %}
{% endif %}
</div>
<div class="info">
<p>
<span class="date">{{ action.created|timesince }} ago</span>
<br />
<a href="{{ user.get_absolute_url }}">
{{ user.first_name }}
</a>
{{ action.verb }}
{% if action.target %}
{% with target=action.target %}
<a href="{{ target.get_absolute_url }}">{{ target }}</a>
{% endwith %}
{% endif %}
</p>
</div>
</div>
{% endwith %}
This is the template to display an Action object. First, we use the {% with %}
template tag to retrieve the user performing the action and their profile. Then, we
display the image of the target object if the Action object has a related target object.
Finally, we display the link to the user that performed the action, the verb and the
target object, if any.
Now, edit the account/dashboard.html template and append the following code
to the bottom of the content block:
<h2>What's happening</h2>
<div id="action-list">
{% for action in actions %}
{% include "actions/action/detail.html" %}
{% endfor %}
</div>
[ 191 ]
We just created a complete activity stream for our users and we can easily add new
user actions to it. You can also add infinite scroll functionality to the activity stream
by implementing the same AJAX paginator we used for the image_list view.
[ 192 ]
Chapter 6
pre_save and post_save: Sent before or after calling the save() method
of a model
These are just a subset of the signals provided by Django. You can find the list of all
built-in signals at https://docs.djangoproject.com/en/1.8/ref/signals/.
Let's say you want to retrieve images by popularity. You can use the Django
aggregation functions to retrieve images ordered by then number of users who like
them. Remember you used Django aggregation functions in Chapter 3, Extending Your
Blog Application. The following code will retrieve images by their number of likes:
from django.db.models import Count
from images.models import Image
images_by_popularity = Image.objects.annotate(
total_likes=Count('users_like')).order_by('-total_likes')
However, ordering images by counting their total likes is more expensive in terms
of performance than ordering them by a field which stores total counts. You can
add a field to the Image model to denormalize the total number of likes to boost
performance in queries that involve this field. How to keep this field updated?
Edit the models.py file of the images application and add the following field to the
Image model:
total_likes = models.PositiveIntegerField(db_index=True,
default=0)
The total_likes field will allow us to store the total count of users that like each
image. Denormalizing counts is useful when you want to filter or order QuerySets
by them.
[ 193 ]
There are several ways to improve performance that you have to take into
account before denormalizing fields. Consider database indexes, query
optimization and caching before starting to denormalize your data.
Run the following command to create the migrations for adding the new field to the
database table:
python manage.py makemigrations images
[ 194 ]
Chapter 6
You have to connect your receiver function to a signal, so that it gets called every
time the signal is sent. The recommended method for registering your signals is
by importing them in the ready() method of your application configuration class.
Django provides an application registry that allows you to configure and introspect
your applications.
Create a new file inside the images application directory and name it apps.py.
Add the following code to it:
from django.apps import AppConfig
class ImagesConfig(AppConfig):
name = 'images'
verbose_name = 'Image bookmarks'
def ready(self):
# import signal handlers
import images.signals
[ 195 ]
The name attribute defines the full Python path to the application. The verbose_name
attribute sets the human-readable name for this application. It's displayed in the
administration site. The ready() method is where we import the signals for this
application.
Now we need to tell Django where our application configuration resides. Edit the
__init__.py file located inside the images application directory and add the
default_app_config = 'images.apps.ImagesConfig'
Open your browser to view an image detail page and click on the like button. Go
back to the administration site and take a look at the total_likes attribute. You
should see that the total_likes attribute is updated like in the following example:
Now you can use the total_likes attribute to order images by popularity or
display the value anywhere, avoiding complex queries to calculate it. The following
query to get images ordered by their like count:
images_by_popularity = Image.objects.annotate(
likes=Count('users_like')).order_by('-likes')
This results in a much less expensive SQL query. This is just an example about how
to use Django signals.
Use signals with caution, since they make difficult to know the control
flow. In many cases you can avoid using signals if you know which
receivers need to be notified.
[ 196 ]
Chapter 6
You will need to set initial counts to match the current status of the database. Open
the shell with the command python manage.py shell and run the following code:
from images.models import Image
for image in Image.objects.all():
image.total_likes = image.users_like.count()
image.save()
Installing Redis
Download the latest Redis version from http://redis.io/download. Unzip
the tar.gz file, enter the redis directory and compile Redis using the make
command as follows:
cd redis-3.0.4
make
After installing it use the following shell command to initialize the Redis server:
src/redis-server
You should see an output that ends with the following lines:
# Server started, Redis version 3.0.4
* DB loaded from disk: 0.001 seconds
* The server is now ready to accept connections on port 6379
[ 197 ]
By default, Redis runs on port 6379, but you can also specify a custom port using the
--port flag, for example redis-server --port 6655. When your server is ready,
you can open the Redis client in another shell using the following command:
src/redis-cli
You should see the Redis client shell like the following:
127.0.0.1:6379>
The Redis client allows you to execute Redis commands directly from the shell.
Let's try some commands. Enter the SET command in the Redis shell to store a
value in a key:
127.0.0.1:6379> SET name "Peter"
OK
The previous command creates a name key with the string value "Peter" in the
Redis database. The OK output indicates that the key has been saved successfully.
Then, retrieve the value using the GET command as follows:
127.0.0.1:6379> GET name
"Peter"
You can also check if a key exists by using the EXISTS command. This command
returns 1 if the given key exists, 0 otherwise:
127.0.0.1:6379> EXISTS name
(integer) 1
You can set the time for a key to expire using the EXPIRE command, which allows
you to set time to live in seconds. Another option is using the EXPIREAT command
that expects a Unix timestamp. Key expiration is useful to use Redis as a cache or
to store volatile data:
127.0.0.1:6379> GET name
"Peter"
127.0.0.1:6379> EXPIRE name 2
(integer) 1
Wait for 2 seconds and try to get the same key again:
127.0.0.1:6379> GET name
(nil)
[ 198 ]
Chapter 6
The (nil) response is a null response and means no key has been found. You can
also delete any key using the DEL command as follows:
127.0.0.1:6379> SET total 1
OK
127.0.0.1:6379> DEL total
(integer) 1
127.0.0.1:6379> GET total
(nil)
These are just basic commands for key operations. Redis includes a large set of
commands for other data types such as strings, hashes, sets, ordered sets, and so on.
You can take a look at all Redis commands at http://redis.io/commands and all
Redis data types at http://redis.io/topics/data-types.
This code creates a connection with the Redis database. In Redis, databases are
identified by an integer index instead of a database name. By default, a client is
connected to database 0. The number of available Redis databases is set to 16,
but you can change this in the redis.conf file.
Now set a key using the Python shell:
>>> r.set('foo', 'bar')
True
[ 199 ]
The command returns True indicating that the key has been successfully created.
Now you can retrieve the key using the get() command:
>>> r.get('foo')
'bar'
As you can see, the methods of StrictRedis follow the Redis command syntax.
Let's integrate Redis into our project. Edit the settings.py file of the bookmarks
project and add the following settings to it:
REDIS_HOST = 'localhost'
REDIS_PORT = 6379
REDIS_DB = 0
These are the settings for the Redis server and the database that we will use for
our project.
Here we establish the Redis connection in order to use it in our views. Edit the
image_detail view and make it look as follows:
def image_detail(request, id, slug):
image = get_object_or_404(Image, id=id, slug=slug)
# increment total image views by 1
total_views = r.incr('image:{}:views'.format(image.id))
return render(request,
'images/image/detail.html',
{'section': 'images',
'image': image,
'total_views': total_views})
[ 200 ]
Chapter 6
In this view, we use the INCR command that increments the value of a key by 1 and
sets the value to 0 before performing the operation if the key does not exist. The
incr() method returns the value of the key after performing the operation and we
store it in the total_views variable. We build the Redis key using a notation like
object-type:id:field (for example image:33:id).
The convention for naming Redis keys is to use a colon sign as separator
for creating namespaced keys. By doing so, the key names are specially
verbose and related keys share part of the same schema in their names.
Edit the image/detail.html template and add the following code to it after the
existing <span class="count"> element:
<span class="count">
<span class="total">{{ total_views }}</span>
view{{ total_views|pluralize }}
</span>
Now open an image detail page in your browser and load it several times. You will
see that each time the view is executed the total views displayed are incremented by
1. See the following example:
You have successfully integrated Redis into your project to store item counts.
[ 201 ]
We use the zincrby() command to store image views in a sorted set with the key
image:ranking. We are storing the image id, and a score of 1 that will be added to
the total score of this element in the sorted set. This will allow us to keep track of all
image views globally and have a sorted set ordered by the total number of views.
Now create a new view to display the ranking of the most viewed images. Add the
following code to the views.py file:
@login_required
def image_ranking(request):
# get image ranking dictionary
image_ranking = r.zrange('image_ranking', 0, -1,
desc=True)[:10]
image_ranking_ids = [int(id) for id in image_ranking]
# get most viewed images
most_viewed = list(Image.objects.filter(
id__in=image_ranking_ids))
most_viewed.sort(key=lambda x: image_ranking_ids.index(x.id))
return render(request,
'images/image/ranking.html',
{'section': 'images',
'most_viewed': most_viewed})
[ 202 ]
Chapter 6
This is the image_ranking view. We use the zrange() command to obtain the
elements in the sorted set. This command expects a custom range by lowest and
highest score. By using 0 as lowest and -1 as highest score we are telling Redis
to return all elements in the sorted set. We also specify desc=True to retrieve the
elements ordered by descending score. Finally, we slice the results using [:10] to get
the first 10 elements with highest scores. We build a list of returned image IDs and we
store it in the image_ranking_ids variable as a list of integers. We retrieve the Image
objects for those IDs and force the query to be executed by using the list() function.
It is important to force the QuerySet execution because next we use the sort() list
method on it (at this point we need a list of objects instead of a queryset). We sort the
Image objects by their index of appearance in the image ranking. Now we can use the
most_viewed list in our template to display the 10 most viewed images.
Create a new image/ranking.html template file and add the following code to it:
{% extends "base.html" %}
{% block title %}Images ranking{% endblock %}
{% block content %}
<h1>Images ranking</h1>
<ol>
{% for image in most_viewed %}
<li>
<a href="{{ image.get_absolute_url }}">
{{ image.title }}
</a>
</li>
{% endfor %}
</ol>
{% endblock %}
The template is pretty straightforward, as we just iterate over the Image objects
contained in the most_viewed list.
Finally create an URL pattern for the new view. Edit the urls.py file of the images
application and add the following pattern to it:
url(r'^ranking/$', views.image_ranking, name='create'),
[ 203 ]
Counting: As you have seen, it is very easy to manage counters with Redis.
You can use incr() and incrby() for counting stuff.
Storing latest items: You can add items to the start/end of a list using
lpush() and rpush(). Remove and return first/last element using lpop() /
rpop().You can trim the list length using ltrim() to maintain its length.
Rankings and leaderboards: Redis sorted sets with scores make it very easy
to create leaderboards.
Real-time tracking: Redis fast I/O makes it perfect for real-time scenarios.
Summary
In this chapter, you have built a follower system and a user activity stream. You have
learned how Django signals work and you have integrated Redis into your project.
In the next chapter, you will learn how to build an on-line shop. You will create a
product catalog and build a shopping cart using sessions. You will also learn how
to launch asynchronous tasks with Celery.
[ 204 ]
www.PacktPub.com
Stay Connected: