Django Design Patterns and Best Practices - Sample Chapter
Django Design Patterns and Best Practices - Sample Chapter
ee
$ 44.99 US
29.99 UK
P U B L I S H I N G
Arun Ravindran
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
Sa
m
Arun Ravindran
Mixins
Decorators
Designing URLs
[ 51 ]
Our two-line view function is quite simple to understand. We are currently not
doing anything with the request argument. We can examine a request to better
understand the context in which the view was called, for example by looking at
the GET/POST parameters, URI path, or HTTP headers such as REMOTE_ADDR.
Its corresponding lines in URLConf would be as follows:
# In urls.py
url(r'^hello-fn/(?P<name>\w+)/$', views.hello_fn),
url(r'^hello-fn/$', views.hello_fn),
We are reusing the same view function to support two URL patterns. The first
pattern takes a name argument. The second pattern doesn't take any argument from
the URL, and the view function will use the default name of World in this case.
Again, the corresponding URLConf would have two lines, as shown in the
following commands:
# In urls.py
url(r'^hello-cl/(?P<name>\w+)/$', views.HelloView.as_view()),
url(r'^hello-cl/$', views.HelloView.as_view()),
There are several interesting differences between this view class and our earlier
view function. The most obvious one being that we need to define a class. Next,
we explicitly define that we will handle only the GET requests. The previous view
function gives the same response for GET, POST, or any other HTTP verb, as shown
in the following commands using the test client in Django shell:
>>> from django.test import Client
>>> c = Client()
>>> c.get("http://0.0.0.0:8000/hello-fn/").content
[ 52 ]
Chapter 4
b'Hello World!'
>>> c.post("http://0.0.0.0:8000/hello-fn/").content
b'Hello World!'
>>> c.get("http://0.0.0.0:8000/hello-cl/").content
b'Hello World!'
>>> c.post("http://0.0.0.0:8000/hello-cl/").content
b''
While it is not impossible to customize the view function in a similar manner, you
would need to add several keyword arguments with default values. This can quickly
get unmanageable. This is exactly why generic views migrated from view functions
to class-based views.
[ 53 ]
Django Unchained
After spending 2 weeks hunting for good Django developers, Steve
started to think out of the box. Noticing the tremendous success of
their recent hackathon, he and Hart organized a Django Unchained
contest at S.H.I.M. The rules were simplebuild one web application
a day. It could be a simple one but you cannot skip a day or break the
chain. Whoever creates the longest chain, wins.
The winnerBrad Zanni was a real surprise. Being a traditional
designer with hardly any programming background, he had once
attended week-long Django training just for kicks. He managed to
create an unbroken chain of 21 Django sites, mostly from scratch.
The very next day, Steve scheduled a 10 o' clock meeting with him
at his office. Though Brad didn't know it, it was going to be his
recruitment interview. At the scheduled time, there was a soft knock
and a lean bearded guy in his late twenties stepped in.
As they talked, Brad made no pretense of the fact that he was not
a programmer. In fact, there was no pretense to him at all. Peering
through his thick-rimmed glasses with calm blue eyes, he explained
that his secret was quite simpleget inspired and then focus.
He used to start each day with a simple wireframe. He would then
create an empty Django project with a Twitter bootstrap template. He
found Django's generic class-based views a great way to create views
with hardly any code. Sometimes, he would use a mixin or two from
Django-braces. He also loved the admin interface for adding data on
the go.
His favorite project was Labyrintha Honeypot disguised as a
baseball forum. He even managed to trap a few surveillance bots
hunting for vulnerable sites. When Steve explained about the
SuperBook project, he was more than happy to accept the offer. The
idea of creating an interstellar social network truly fascinated him.
With a little more digging around, Steve was able to find half a dozen
more interesting profiles like Brad within S.H.I.M. He learnt that rather
that looking outside he should have searched within the organization
in the first place.
[ 54 ]
Chapter 4
Generic views were created because Django developers felt that they were recreating
the same kind of views in every project. Nearly every project needed a page showing
a list of objects (ListView), details of an object (DetailView), or a form to create
an object (CreateView). In the spirit of DRY, these reusable views were bundled
with Django.
A convenient table of generic views in Django 1.7 is given here:
Type
Class Name
View
Description
Base
Base
TemplateView
Base
RedirectView
List
ListView
Detail
DetailView
Edit
FormView
Edit
CreateView
Edit
UpdateView
Edit
DeleteView
Date
ArchiveIndexView
Date
YearArchiveView
Date
MonthArchiveView
Date
WeekArchiveView
Date
DayArchiveView
Date
TodayArchiveView
Date
DateDetailView
[ 55 ]
Most people confuse class-based views and class-based generic views. Their names
are similar but they are not the same things. This has led to some interesting
misconceptions as follows:
The only generic views are the ones bundled with Django: Thankfully,
this is wrong. There is no special magic in the generic class-based views
that are provided.
You are free to roll your own set of generic class-based views. You
can also use a third-party library such as django-vanilla-views
(http://django-vanilla-views.org/), which has a simpler
implementation of the standard generic views. Remember that using
custom generic views might make your code unfamiliar to others.
Class-based views must always derive from a generic view: Again, there
is nothing magical about the generic view classes. Though 90 percent of the
time, you will find a generic class such as View to be ideal for use as a base
class, you are free to implement similar features yourself.
View mixins
Mixins are the essence of DRY code in class-based views. Like model mixins, a
view mixin takes advantage of Python's multiple inheritance to easily reuse chunks
of functionality. They are often parent-less classes in Python 3 (or derived from
object in Python 2 since they are new-style classes).
Mixins intercept the processing of views at well-defined places. For example,
most generic views use get_context_data to set the context dictionary. It is a
good place to insert an additional context, such as a feed variable that points
to all posts a user can view, as shown in the following command:
class FeedMixin(object):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["feed"] = models.Post.objects.viewable_posts(self.
request.user)
return context
The get_context_data method first populates the context by calling its namesake in
all the bases classes. Next, it updates the context dictionary with the feed variable.
[ 56 ]
Chapter 4
Now, this mixin can be easily used to add the user's feed by including it in the list of
base classes. Say, if SuperBook needs a typical social network home page with a form
to create a new post followed by your feed, then you can use this mixin as follows:
class MyFeed(FeedMixin, generic.CreateView):
model = models.Post
template_name = "myfeed.html"
success_url = reverse_lazy("my_feed")
Order of mixins
You might have come across code with several mixins as follows:
class ComplexView(MyMixin, YourMixin, AccessMixin, DetailView):
It can get quite tricky to figure out the order to list the base classes. Like most things
in Django, the normal rules of Python apply. Python's Method Resolution Order
(MRO) determines how they should be arranged.
In a nutshell, mixins come first and base classes come last. The more specialized the
parent class is, the more it moves to the left. In practice, this is the only rule you will
need to remember.
To understand why this works, consider the following simple example:
class A:
def do(self):
print("A")
class B:
[ 57 ]
As you would expect, if B is mentioned before A in the list of base classes, then B's
method gets called and vice versa.
Now imagine A is a base class such as CreateView and B is a mixin such as
FeedMixin. The mixin is an enhancement over the basic functionality of the
base class. Hence, the mixin code should act first and in turn, call the base
method if needed. So, the correct order is BA (mixins first, base last).
The order in which base classes are called can be determined by checking
the __mro__ attribute of the class:
>>> AB.__mro__
(__main__.AB, __main__.A, __main__.B, object)
So, if AB calls super(), first A gets called; then, A's super() will call B, and so on.
Python's MRO usually follows a depth-first, left-to-right order to
select a method in the class hierarchy. More details can be found at
http://www.python.org/download/releases/2.3/mro/.
Decorators
Before class-based views, decorators were the only way to change the behavior of
function-based views. Being wrappers around a function, they cannot change the
inner working of the view, and thus effectively treat them as black boxes.
A decorator is function that takes a function and returns the decorated function.
Confused? There is some syntactic sugar to help you. Use the annotation notation
@, as shown in the following login_required decorator example:
@login_required
[ 58 ]
Chapter 4
def simple_view(request):
return HttpResponse()
Since login_required wraps around the view, a wrapper function gets the control
first. If the user was not logged in, then it redirects to settings.LOGIN_URL.
Otherwise, it executes simple_view as if it did not exist.
Decorators are less flexible than mixins. However, they are simpler. You can
use both decorators and mixins in Django. In fact, many mixins are implemented
with decorators.
View patterns
Let's take a look at some common design patterns seen in designing views.
Problem details
Most websites have pages that can be accessed only if you are logged in. Certain
other pages are accessible to anonymous or public visitors. If an anonymous visitor
tries to access a page, which needs a logged-in user, they could be routed to the login
page. Ideally, after logging in, they should be routed back to the page they wished
to see in the first place.
Similarly, there are pages that can only be seen by certain groups of users. For
example, Django's admin interface is only accessible to the staff. If a non-staff user
tries to access the admin pages, they would be routed to the login page.
Finally, there are pages that grant access only if certain conditions are met. For
example, the ability to edit a post should be only accessible to the creator of the
post. Anyone else accessing this page should see a Permission Denied error.
[ 59 ]
Solution details
There are two ways to control access to a view:
1. By using a decorator on a function-based view or class-based view:
@login_required(MyView.as_view())
We really don't need the decorator here. The more explicit form
recommended is as follows:
class LoginRequiredMixin:
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated():
raise PermissionDenied
return super().dispatch(request, *args, **kwargs)
[ 60 ]
Chapter 4
Staff members in Django are users with the is_staff flag set in the user model.
Again, you can use a django-braces mixin called UserPassesTestMixin, as follows:
from braces.views import UserPassesTestMixin
class SomeStaffView(UserPassesTestMixin, TemplateView):
def test_func(self, user):
return user.is_staff
You can also create mixins to perform specific checks, such as if the object is being
edited by its author or not (by comparing it with the logged-in user):
class CheckOwnerMixin:
# To be used with classes derived from SingleObjectMixin
def get_object(self, queryset=None):
obj = super().get_object(queryset)
if not obj.owner == self.request.user:
raise PermissionDenied
return obj
Problem details
Django templates can only show variables that are present in its context dictionary.
However, sites need the same information in several pages. For instance, a sidebar
showing the recent posts in your feed might be needed in several views.
However, if we use a generic class-based view, we would typically have a limited
set of context variables related to a specific model. Setting the same context variable
in each view is not DRY.
Solution details
Most generic class-based views are derived from ContextMixin. It provides
the get_context_data method, which most classes override, to add their own
context variables. While overriding this method, as a best practice, you will need
to call get_context_data of the superclass first and then add or override your
context variables.
[ 61 ]
We can add this mixin to our views and use the added context variables in our
templates. Notice that we are using the model manager defined in Chapter 3,
Models, to filter the posts.
A more general solution is to use StaticContextMixin from django-braces for
static-context variables. For example, we can add an additional context variable
latest_profile that contains the latest user to join the site:
class CtxView(StaticContextMixin, generic.TemplateView):
template_name = "ctx.html"
static_context = {"latest_profile": Profile.objects.latest('pk')}
Here, static context means anything that is unchanged from a request to request. In
that sense, you can mention QuerySets as well. However, our feed context variable
needs self.request.user to retrieve the user's viewable posts. Hence, it cannot be
included as a static context here.
Pattern services
Problem: Information from your website is often scraped and processed by
other applications
Solution: Create lightweight services that return data in machine-friendly formats,
such as JSON or XML
Problem details
We often forget that websites are not just used by humans. A significant
percentage of web traffic comes from other programs like crawlers, bots, or
scrapers. Sometimes, you will need to write such programs yourself to extract
information from another website.
Generally, pages designed for human consumption are cumbersome for mechanical
extraction. HTML pages have information surrounded by markup, requiring
extensive cleanup. Sometimes, information will be scattered, needing extensive
data collation and transformation.
[ 62 ]
Chapter 4
A machine interface would be ideal in such situations. You can not only reduce
the hassle of extracting information but also enable the creation of mashups.
The longevity of an application would be greatly increased if its functionality is
exposed in a machine-friendly manner.
Solution details
Service-oriented architecture (SOA) has popularized the concept of a service. A
service is a distinct piece of functionality exposed to other applications as a service.
For example, Twitter provides a service that returns the most recent public statuses.
A service has to follow certain basic principles:
Composable: This should be easy to reuse and combine with other services
In Django, you can create a basic service without any third-party packages. Instead
of returning HTML, you can return the serialized data in the JSON format. This form
of a service is usually called a web Application Programming Interface (API).
For example, we can create a simple service that returns five recent public posts
from SuperBook as follows:
class PublicPostJSONView(generic.View):
def get(self, request, *args, **kwargs):
msgs = models.Post.objects.public_posts().values(
"posted_by_id", "message")[:5]
return HttpResponse(list(msgs), content_type="application/
json")
For a more reusable implementation, you can use the JSONResponseMixin class
from django-braces to return JSON using its render_json_response method:
from braces.views import JSONResponseMixin
class PublicPostJSONView(JSONResponseMixin, generic.View):
def get(self, request, *args, **kwargs):
msgs = models.Post.objects.public_posts().values(
[ 63 ]
If we try to retrieve this view, we will get a JSON string rather than an HTML
response:
>>> from django.test import Client
>>> Client().get("http://0.0.0.0:8000/public/").content
b'[{"posted_by_id": 23, "message": "Hello!"},
{"posted_by_id": 13, "message": "Feeling happy"},
...
Note that we cannot pass the QuerySet method directly to render the JSON
response. It has to be a list, dictionary, or any other basic Python built-in data
type recognized by the JSON serializer.
Of course, you will need to use a package such as Django REST framework if you
need to build anything more complex than this simple API. Django REST framework
takes care of serializing (and deserializing) QuerySets, authentication, generating
a web-browsable API, and many other features essential to create a robust and
full-fledged API.
Designing URLs
Django has one of the most flexible URL schemes among web frameworks.
Basically, there is no implied URL scheme. You can explicitly define any URL
scheme you like using appropriate regular expressions.
However, as superheroes love to say"With great power comes great
responsibility." You cannot get away with a sloppy URL design any more.
URLs used to be ugly because they were considered to be ignored by users. Back
in the 90s when portals used to be popular, the common assumption was that your
users will come through the front door, that is, the home page. They will navigate
to the other pages of the site by clicking on links.
Search engines have changed all that. According to a 2013 research report, nearly
half (47 percent) of all visits originate from a search engine. This means that any
page in your website, depending on the search relevance and popularity can be
the first page your user sees. Any URL can be the front door.
More importantly, Browsing 101 taught us security. Don't click on a blue link in
the wild, we warn beginners. Read the URL first. Is it really your bank's URL or
a site trying to phish your login details?
[ 64 ]
Chapter 4
Today, URLs have become part of the user interface. They are seen, copied, shared,
and even edited. Make them look good and understandable from a glance. No more
eye sores such as:
http://example.com/gallery/default.asp?sid=9DF4BC0280DF12D3ACB6009027
1E26A8&command=commntform
Short and meaningful URLs are not only appreciated by users but also by search
engines. URLs that are long and have less relevance to the content adversely affect
your site's search engine rankings.
Finally, as implied by the maxim "Cool URIs don't change," you should try to maintain
your URL structure over time. Even if your website is completely redesigned, your old
links should still work. Django makes it easy to ensure that this is so.
Before we delve into the details of designing URLs, we need to understand the
structure of a URL.
URL anatomy
Technically, URLs belong to a more general family of identifiers called Uniform
Resource Identifiers (URIs). Hence, a URL has the same structure as a URI.
A URI is composed of several parts:
URI = Scheme + Net Location + Path + Query + Fragment
[ 65 ]
Even though Django documentation prefers to use the term URLs, it might more
technically correct to say that you are working with URIs most of the time. We
will use the terms interchangeably in this book.
Django URL patterns are mostly concerned about the 'Path' part of the URI. All
other parts are tucked away.
[ 66 ]
Chapter 4
The first argument of the patterns function is the prefix. It is usually blank
for the root URLConf. The remaining arguments are all URL patterns.
Each URL pattern is created using the url function, which takes five
arguments. Most patterns have three arguments: the regular expression
pattern, view callable, and name of the view.
The jobs pattern is the only example here of a named regular expression.
[ 67 ]
Mnemonic
I admit that the syntax for name-based arguments is quite difficult to remember.
Often, I use a simple mnemonic as a memory aid. The phrase "Parents Question
Pink Action-figures" stands for the first letters of Parenthesis, Question mark,
(the letter) P, and Angle brackets.
Put them together and you get (?P< . You can enter the name of the pattern and
figure out the rest yourself.
It is a handy trick and really easy to remember. Just imagine a furious parent
holding a pink-colored hulk action figure.
Another tip is to use an online regular expression generator such as https://pythex.
org/ or https://www.debuggex.com/ to craft and test your regular expressions.
Chapter 4
Names must be unique. If two patterns have the same name, they will not work. So,
some Django packages used to add prefixes to the pattern name. For example, an
application named blog might have to call its edit view as 'blog-edit' since 'edit'
is a common name and might cause conflict with another application.
Namespaces were created to solve such problems. Pattern names used in a
namespace have to be only unique within that namespace and not the entire project.
It is recommended that you give every app its own namespace. For example, we
can create a 'blog' namespace with only the blog's URLs by including this line in
the root URLconf:
url(r'^blog/', include('blog.urls', namespace='blog')),
Now the blog app can use pattern names, such as 'edit' or anything else as long as
they are unique within that app. While referring to a name within a namespace, you
will need to mention the namespace, followed by a ':' before the name. It would be
"blog:edit" in our example.
As Zen of Python says"Namespaces are one honking great idealet's do more of
those." You can create nested namespaces if it makes your pattern names cleaner,
such as "blog:comment:edit". I highly recommend that you use namespaces in
your projects.
Pattern order
Order your patterns to take advantage of how Django processes them, that is,
top-down. A good rule of thumb is to keep all the special cases at the top. Broader
patterns can be mentioned further down. The broadesta catch-allif present,
can go at the very end.
For example, the path to your blog posts might be any valid set of characters, but
you might want to handle the About page separately. The right sequence of patterns
should be as follows:
urlpatterns = patterns(
'',
url(r'^about/$', AboutView.as_view(), name='about'),
url(r'^(?P<slug>\w+)/$', ArticleView.as_view(), name='article'),
)
If we reverse the order, then the special case, the AboutView, will never get called.
[ 69 ]
The beauty of this layout is that it is so easy to climb up to the parent section.
Once you remove the tail end after the slash, you are one level up.
For example, you can create a similar structure for the articles section, as shown here:
# project's main urls.py
urlpatterns = patterns(
'',
url(r'^articles/$', include(articles.urls), namespace="articles"),
)
# articles/urls.py
urlpatterns = patterns(
'',
url(r'^$', ArticlesIndex.as_view(), name='index'),
url(r'^(?P<slug>\w+)/$', ArticleView.as_view(), name='article'),
)
Notice the 'index' pattern that will show an article index in case a user climbs up
from a particular article.
[ 70 ]
Chapter 4
RESTful URLs
In 2000, Roy Fielding introduced the term Representational state transfer (REST) in
his doctoral dissertation. Reading his thesis (http://www.ics.uci.edu/~fielding/
pubs/dissertation/top.htm) is highly recommended to better understand the
architecture of the web itself. It can help you write better web applications that do
not violate the core constraints of the architecture.
One of the key insights is that a URI is an identifier to a resource. A resource can
be anything, such as an article, a user, or a collection of resources, such as events.
Generally speaking, resources are nouns.
The web provides you with some fundamental HTTP verbs to manipulate resources:
GET, POST, PUT, PATCH, and DELETE. Note that these are not part of the URL itself.
Hence, if you use a verb in the URL to manipulate a resource, it is a bad practice.
For example, the following URL is considered bad:
http://site.com/articles/submit/
Instead, you should remove the verb and use the POST action to this URL:
http://site.com/articles/
Best Practice
Keep verbs out of your URLs if HTTP verbs can be used instead.
Note that it is not wrong to use verbs in a URL. The search URL for your site can
have the verb 'search' as follows, since it is not associated with one resource as
per REST:
http://site.com/search/?q=needle
RESTful URLs are very useful for designing CRUD interfaces. There is almost a
one-to-one mapping between the Create, Read, Update, and Delete database
operations and the HTTP verbs.
Note that the RESTful URL style is complimentary to the departmental store URL
style. Most sites mix both the styles. They are separated for clarity and better
understanding.
[ 71 ]
Summary
Views are an extremely powerful part of the MVC architecture in Django. Over
time, class-based views have proven to be more flexible and reusable compared to
traditional function-based views. Mixins are the best examples of this reusability.
Django has an extremely flexible URL dispatch system. Crafting good URLs takes
into account several aspects. Well-designed URLs are appreciated by users too.
In the next chapter, we will take a look at Django's templating language and how
best to leverage it.
[ 72 ]
www.PacktPub.com
Stay Connected: