Want to see the full-length video right now for free?
On this episode of The Weekly Iteration, George Brocklehurst, development director from thoughtbot's NYC office, joins us to take a tour of a handful of design patterns through the lens of the Django web framework. Specifically, we'll take a look at Django's use of the Template Method Pattern for composing complex operations and the Single Responsibility Principle.
Here we'll be looking at Django View
classes, which are similar to Rails
controller actions.
from django.views import generic
from .models import Post
class PostListView(generic.ListView):
model = Post
class PostDetailView(generic.DetailView):
model = Post
class PostCreateView(generic.CreateView):
model = Post
fields = ('title', 'body', 'published', 'date')
In the case of these classes, it may look like very little is going on, but in
fact we are opining into implicit behavior implemented on the parent classes
(generic.ListView
, generic.DetailView
, etc) which are combined via the
[Template Method Pattern][].
At a high level, the Template Method Pattern makes use of an outline or skeleton of method calls, where we can choose to override the behavior of any of these child method calls that makes up the sequence by providing a concrete implementation. By using the template method pattern, we're able to strike a nice balance between avoiding boilerplate, while still providing explicit points to configure and override the behavior of our system.
In our example, we began with the default behavior for the PostListView
which will display all Post
s. If instead we wanted to hide draft Post
s, we
can simple implement the get_queryset
method to return only the desired
Post
s:
def get_queryset(self):
return Post.objects.filter(published=True)
What's nice here is that we were able to change just that one small piece,
specifically which Post
s to query, without needing to alter anything about
how things are sorted or rendered or otherwise displayed.
For each generic view there is a specific set of methods that will be called, giving well-defined points to override the behavior. This, for instance, is the [Method Flowchart for the generic.ListView][]:
dispatch()
http_method_not_allowed()
get_template_names()
get_queryset()
get_context_object_name()
get_context_data()
get()
render_to_response()
[Template Method Pattern]: https://en.wikipedia.org/wiki/Template_method_pattern [Method Flowchart for the generic.ListView]: https://docs.djangoproject.com/en/dev/ref/class-based-views/generic-display/#listview
One of the key features of the Template Method Pattern is that it causes you to write code that highlights the things that are differed. The common pieces are hidden away and what's left are the bits that make your code unique.
One potential downfall is that using the Template Method Pattern increases the amount a developer needs to know and be comfortable remembering. Thankfully, the Django generic views are nicely focused classes with largely a single responsibility. In addition, the Python and Django communities do a great job with documentation which goes a long way with a pattern like this. That said, it's always good to be aware of the trade offs, and with the template method pattern's simplicity and ease of extension does come a potentially higher learning curve to fully master it.
On the topic of documentation, one particularly good spot to visit is the [Classy Class-Based Views][] page.
[Classy Class-Based Views]: http://ccbv.co.uk/
Another area where Django shines is in the [Single Responsibility
Principle][]. To dive into this, we'll take a look at the PostCreateView
class. In our default implementation, the form being rendered on the page was
created from a form class dynamically generated by Django for us. That said,
we can easily take this over by implementing our own.
from .forms import PostForm
# ... others omitted
class PostCreateView(generic.CreateView):
model = Post
def get_form_class(self):
return PostForm
Here rather than using the generated form class, we're importing and using our own. That form class is implemented as:
from django import forms
from .models import Post
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ('title', 'body', 'published', 'date')
The above is now an explicit version of the exact form Django had previously been generating for us. Now we can go ahead and override some of the default behavior, specifically changing out the way the date fields are rendered:
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ('title', 'body', 'published', 'date')
+ widgets = {
+ 'date': foms.SelectDateWidget,
+ }
With the above as context, we can take a look into how Django provides us with really nice dividing lines of responsibility. Thus far we've looked at:
Model
and
save it.These objects all know how to work with each other to build a full page, but individually they are very focused simple objects.
[Single Responsibility Principle]: https://en.wikipedia.org/wiki/Single_responsibility_principle
While we might not be writing Python or Django code in the near future, we can often benefit from taking a look at how others are approaching the same sorts of problems we solve each day and perhaps borrow a few things. In this case, Django's use of the Template Method Pattern, as well as its excellent use of Single Responsibility and a clear execution model are an excellent model to allow for code that hit's the sweet spot between concise and focused, while remaining flexible and easy to change.