A behavioral design pattern allowing us to abstract away common parts of an algorithm while ensuring that the overarching said algorithm is still followed. The following is a very simple example to show you the big picture:

""" template method pattern"""

class FrameworkClass(object):
    """
        The class defined by the framework,
        containing the main operations.
    """

    def template_method(self):
        """
            main algorithm,
            operation_1 always called first
            operation_2 always called second
        """
        self.operation_1()
        self.operation_2()

    def operation_1(self):
        """ a step that can be overriden"""
        print("operation 1")

    def operation_2(self):
        """ another step that can be overriden"""
        print("operation 2")
from some.interesting.module import FrameworkClass

class UserClass(FrameworkClass):
    """ The class defined by the user, to redefine some operations"""
    def operation_2(self):
        print("operation 2 customized")


def main():
    user_class = UserClass()
    user_class.template_method()


if __name__ == "__main__":
    main()


Executing this script, the output will be:

~
⤷ python template_pattern.py
operation 1
operation 2 customized
~


Advantages

  • separation of variant parts from invariant parts.
  • avoiding code duplication, encouraging code reuse.
  • giving flexibility as some methods may be overriden.

Drawbacks

  • difficulty of debugging, totally hidden flow control.
  • changes at any level of the class hierarchy can disturb the implementation.
  • customizing the behaviour implies to look up the source code.

Okay, can we have a real life, real world example?

Django Class Based Generic Views, a concept that feels like black magic at first sight. But everything become a little bit easier to grok, when you know they've been implemented using Template method design pattern, and also when you spend some time reading the implementation, in the source code.

Let's suppose we have everything correctly configured:

  • the settings
  • the models
  • the urls patterns
  • the templates.

A view to display all Publishers will be:

from django.views.generic import ListView
from books.models import Publisher

class PublisherList(ListView):
    model = Publisher


A view displaying all the Publishers with a code name equal to "foo" will be:

from django.views.generic import ListView
from books.models import Publisher

class PublisherList(ListView):
    queryset = Publisher.object.filter(code_name="foo")


A view displaying all the Publishers owned by the current logged in user will be:

from django.views.generic import ListView
from books.models import Publisher

class PublisherList(ListView):
    def get_queryset(self):
        return Publisher.objects.filter(user=self.request.user)



How does Django knows when and what queryset to run?

The answer to this question is right inside the source code, basically:

  def get_queryset(self):

        if self.queryset is not None:
            queryset = self.queryset
            if isinstance(queryset, QuerySet):
                queryset = queryset.all()
        elif self.model is not None:
            queryset = self.model._default_manager.all()
        else:
            raise ImproperlyConfigured(
                "%(cls)s is missing a QuerySet. Define "
                "%(cls)s.model, %(cls)s.queryset, or override "
                "%(cls)s.get_queryset()." % {
                    'cls': self.__class__.__name__
                }
            )
        ordering = self.get_ordering()
        if ordering:
            if isinstance(ordering, str):
                ordering = (ordering,)
            queryset = queryset.order_by(*ordering)

        return queryset


  • if the queryset attribute is defined inside your view, it will be used to build the queryset.
  • if no queryset attribute, the queryset will be built, based on the model attribute defined inside your view.
  • if no model attribute, and no queryset attribute, an exception will be raised.
  • override the get_queryset method, and it will be used instead of the one in the parent class, because mro.

How does the view know what to do and in which order to do it?

Indeed, we do not see the flow of execution. That is because, the skeleton, the algorithm, were already defined inside the parents classes. Throwing the two eyes at the documentation for the ListView class, we can see, there is a method flowchart showing us what methods will be called in which order.

Method Flowchart:

  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()

We can also use the Python Debugger to get the answer to this question. Let's take the last example, and insert pdb.set_trace() to set a breakpoint:

from django.views.generic import ListView
from books.models import Publisher

class PublisherList(ListView):
    def get_queryset(self):
        return Publisher.objects.filter(user=self.request.user)


[27]   /home/nsukami/GITHUB/how_to_django/env/lib/python3.6/site-packages/django/views/generic/base.py(68)view()
-> return self.dispatch(request, *args, **kwargs)
[28]   /home/nsukami/GITHUB/how_to_django/env/lib/python3.6/site-packages/django/views/generic/base.py(88)dispatch()
-> return handler(request, *args, **kwargs)
[29]   /home/nsukami/GITHUB/how_to_django/env/lib/python3.6/site-packages/django/views/generic/list.py(160)get()
-> self.object_list = self.get_queryset()
[30] > /home/nsukami/GITHUB/how_to_django/dummy/publishing/views.py(9)get_queryset()-><QuerySet [<Publisher: pub1>]>
-> return Publisher.objects.filter(user=self.request.user)
(Pdb++) n
[29] > /home/nsukami/GITHUB/how_to_django/env/lib/python3.6/site-packages/django/views/generic/list.py(161)get()
-> allow_empty = self.get_allow_empty()
(Pdb++) n
[29] > /home/nsukami/GITHUB/how_to_django/env/lib/python3.6/site-packages/django/views/generic/list.py(163)get()
-> if not allow_empty:
(Pdb++) n
[29] > /home/nsukami/GITHUB/how_to_django/env/lib/python3.6/site-packages/django/views/generic/list.py(175)get()
-> context = self.get_context_data()
(Pdb++) n
[29] > /home/nsukami/GITHUB/how_to_django/env/lib/python3.6/site-packages/django/views/generic/list.py(176)get()
-> return self.render_to_response(context)



I really hope I've been able to shed some light on this topic, probably not enough. If you want to go further, you may find the following links interesting: