Video

Want to see the full-length video right now for free?

Notes

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.

Template Method Pattern

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 Posts. If instead we wanted to hide draft Posts, we can simple implement the get_queryset method to return only the desired Posts:

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 Posts 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][]:

  1. dispatch()
  2. http_method_not_allowed()
  3. get_template_names()
  4. get_queryset()
  5. get_context_object_name()
  6. get_context_data()
  7. get()
  8. 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

Highlighting What's Different

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.

Potential Drawbacks of the Template Method Pattern

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/

Single Responsibility Principle

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,
+         }

Focused Responsibilities

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:

  • A form - A "bag of fields" and the ability to copy data into a Model and save it.
  • Many fields - Each field is responsible for doing type conversions, for example taking a string (the only thing HTTP really knows about), and turning it into a date.
  • Widgets - A widget is responsible for taking data and rendering it in the UI.

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

Conclusion

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.