Extending Templates

Recently at work, I had a quite long code review from a very young, very rushed co-worker.  Let many such reviews (my own included) there were several instances of copy-paste coding, where the same block of code is copied to several locations with only slight modifications.

This was fresh in my mind when I started in on this series of tutorials (which are quite good):  Building Data Products with Python

In the middle of the first page, there is a great description of how I have been doing copy-paste coding in the KidTasks app – navigation links in the templates.

Fortunately for me, they also give a good example of how to fix this by using the extends tag to reference shared template code, similar to using a base class.

The Problem

Two of my templates end with the following four lines of code

         <br/> <a href="{% url 'task_new' %}?from={{ request.path|urlencode }}">Add New Task</a>
         <br/> <a href="{% url 'rep_task_new' %}?from={{ request.path|urlencode }}">Add New Repeating Task</a>
         <br/> <a href="{% url 'kid_new' %}?from={{ request.path|urlencode }}">Add New Kid</a>
         <br/> <a href="{% url 'today' %}">View Today's Tasks</a>

While there are currently only two instances of this, there are likely to be more in the future (or in a real app).

The Fix

Fortunately, the solution was quite simple.  First you create the base template which includes the shared code and some blocks for parts of the template content.  In my case, for such simple pages, there was really only a title block and a content block.  The base template looks like this:

<div>
   <h1>{% block title %}(no title){% endblock %}</h1>

   {% block content %}(no content){% endblock %}

   <nav>
      <div id="navbar">
         <br/> <a href="{% url 'task_new' %}?from={{ request.path|urlencode }}">Add New Task</a>
         <br/> <a href="{% url 'rep_task_new' %}?from={{ request.path|urlencode }}">Add New Repeating Task</a>
         <br/> <a href="{% url 'kid_new' %}?from={{ request.path|urlencode }}">Add New Kid</a>
         <br/> <a href="{% url 'today' %}">View Today's Tasks</a>
      </div>
   </nav>
</div>

Note that this puts the navigation links on the bottom of the page (where they were previously).  It also adds a div with an id to the nav bar which, I suspect, is going to be handy when I start diving in to CSS and making the site look a bit more professional.

Another thing to note is that this will also move the formatting of the title for these pages to the base template rather than having it in each individual template.  This is helpful for maintaining a consistent look throughout your app.

The change to the sub-templates was quite small.  First I had to move the title from inside the content block into its own block:

{% block title %} Today's Tasks {% endblock %}

…and then all I had to do was use the extends tag to reference the base template:

{% extends 'tasks/base.html' %}

NOTE: the extends tag is required to be the first line in the template.  Django will complain loudly if it is not.

One of the full sub-templates ended up looking like this:

{% extends 'tasks/base.html' %}

{% block title %} Today's Tasks {% endblock %}

{% block content %}
{% if kids %}
    {% for name, tasks in kids.items %}
        <h1>{{ name }} on {{ day }}</h1>
        {% if tasks %}
            <ul>
            {% for task in tasks %}
                <li><a href="{% url 'update' task.id %}">
                {% if task.completed %} <strike> {% endif %}
                    {{ task.name }}
                {% if task.completed %} </strike> {% endif %}
                </a></li>
            {% endfor %}
            </ul>
        {% else %}
            <p>No tasks for {{ name }}.</p>
        {% endif %}
    {% endfor %}
{% else %}
    <p>No kids are present.</p>
{% endif %}
{% endblock %}

That’s all there is to it!  Thanks to Jose A Dianes for the well-written tutorial referenced above.

Setting up for Django development

Let’s start with a quick note on tools and environment.  I’m developing on Linux Mint (17) so the details will be geared toward that system.  I am not planning on doing many posts that are system-specific, so this generally shouldn’t be an issue, but this post is about getting set up and contains linux/ubuntu specific details.  If you’re working on a Windows system, you’ll need to do some translation here.  I’m not the person to ask about that, however.

Python versions

Mint is currently shipping with both Python 2.7 and 3.4.3 installed.  The work I’m doing here, and, if you’re starting new projects, the work you will be doing should be in Python 3.  Most of my python experience to this point is in 2.7, but that’s largely due to a key library (Cheetah templates) not supporting Python 3 when I started with it.  For new code, 3.x is the way to go.  (We’ll see if I still say that after coding in it for a while.)

virtualenv

Unfortunately, Mint ships with 2.7 as the default.  My immediate thought on learning this was to try to change it.  (NOTE: do NOT try to uninstall
python2.7 from Mint.  You’ll just end up re-installing mint.).  The recommended method for doing this to use virtualenv.  Details on how to use virtualenv can be found here:

http://docs.python-guide.org/en/latest/dev/virtualenvs/

but the install is quite simple:

$ sudo pip install virtualenv

I’ll give examples below showing how to invoke it.

Installing django 1.10

Again, this information is covered in greater detail on other sites.  My
intention here is to provide the quick method for install on a similar system for developers who have an idea of what’s going on.  I think the basic instruction for this install are pretty good here:

http://tutorial.djangogirls.org/en/django_installation/

but the basic premise is shown in the following capture:

$ virtualenv -p /usr/bin/python3 venv
Running virtualenv with interpreter /usr/bin/python3
Using base prefix '/usr'
New python executable in /home/jima/coding/server1/venv/bin/python3
Also creating executable in /home/jima/coding/server1/venv/bin/python
Installing setuptools, pip, wheel...done.

$ source venv/bin/activate
$ pip install django~=1.10
Collecting django~=1.10
  Downloading Django-1.10-py2.py3-none-any.whl (6.8MB)
    100% |████████████████████████████████| 6.8MB 105kB/s
Installing collected packages: django
Successfully installed django-1.10

Note that I’m installing django into the virtualenv rather than on my machine.  I’m doing this to allow myself to play with different versions as there’s a small project I’m thinking about that may involve converting from previous django versions.

Starting a new project

Finally, to wrap up the getting started quickly page, here’s how to start a project and test that your install is correct, from beginning to test.

$ virtualenv -p /usr/bin/python3 venv
Running virtualenv with interpreter /usr/bin/python3
Using base prefix '/usr'
New python executable in /home/jima/coding/testing/venv/bin/python3
Also creating executable in /home/jima/coding/testing/venv/bin/python
Installing setuptools, pip, wheel...done.

$ source venv/bin/activate
$ pip install django~=1.10
Collecting django~=1.10
  Using cached Django-1.10.1-py2.py3-none-any.whl
Installing collected packages: django
Successfully installed django-1.10.1
$ django-admin startproject myproject
$ cd myproject/
$ python manage.py runserver
Performing system checks...

System check identified no issues (0 silenced).

You have 13 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.

September 02, 2016 - 18:47:17
Django version 1.10.1, using settings 'myproject.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

At that point you should be able to point your browser at the development server and see the “welcome” page.

Follow Along!

The first series of this blog will be developing a web app to manage tasks for my kids.  I’ll be walking through this in steps as I learn.  If you’d like to follow along, the project is on github at

git@github.com:jima80525/KidTasks.git

and I’ll be creating branches for each blog post in which I work on it.  For this post, simple as it is, the branch can be found at

git checkout blog/01-InitialSetup

I’ll be adding more next week as I describe the basic requirements and start in on the data model!

Wrap up

That’s it! This post is much more recipe-based than I’m intending for the rest of this blog. Next time I’ll start discussing the design process and some ideas on the first app I’ll be developing.  Hopefully then we’ll start delving into things that are not just “how-to” lists.