Login, Please!

This week I was gearing up to try to deploy the KidsTask app. My first inclination was to try out Docker, mainly as it would be potentially useful knowledge for some work projects that are coming up.  That idea fell at the first hurdle.  I use an old laptop for my main development machine and Docker is only supported on 64 bit Linux.  While I did see ways to rebuild patched source to get Docker to run on a 32-bit system, that was more than I was interested in this week.

Next up: Heroku.  I’ve heard good things about this as a place to put very small apps and there are many resources for how to deploy a Django app to Heroku.  Putting the app on the external web, however, posed a new issue; KidsTasks didn’t have any form of authentication.  Anyone could go in and modify the data, which seems less-than-ideal in today’s world.

I (mistakenly) thought that adding user access to KidsTasks would be a good chunk of work.  After a little research and messing around, it turned out to be quite easy!  It turns out that the auth module shipped with Django was adequate for my needs and that configuring it was fairly straight forward.

Settings

You need to tell the auth module two pieces of information through the settings.  The first is where to go once a new user has logged in. This is specified by the name you supplied in the urlpatterns, not the url itself.  In my case this was just the “today” page which lists the daily tasks.  (NOTE: both of these setting are stand-alone variables inside the settings file, they are not part of any list or other structure)

LOGIN_REDIRECT_URL = 'today'

The next setting comes into play when you want to prevent users from accessing views without being logged in.  We’ll discuss how to set that up toward the end of this post, but for now, we’ll need to tell the auth module what the url is for the login page.

LOGIN_URL = 'login'

URLS

Next step is to add URLs to the urls.py file.  Most projects will have two or more urls.py files.  I chose the main project urls.py, rather than the one in my app, tasks, as those urls are prefixed by “tasks” and I wanted the login page to be reachable by just “login”.

We’re going to be using the built-in views in the auth module, so we need to import those first:

from django.contrib.auth import views as auth_views

Then we need to add the urls to the urlpatterns list.

urlpatterns = [
...
    url(r'^login/$', auth_views.login, name='login'),
    url(r'^logout/$', auth_views.logout, {'next_page': 'login'}, name='logout'),

Note the next_page parmaeter we’re passing to the logout view.  This is a nice little trick which forces a load of the logout url to log the user out and then redirect to the login page.

You must logout!

I almost forgot that we still need a way to get to the logout page.  I added a button to the navbar which is shown on all of the pages which points to logout.  Simple.

                  <li>
                  <a href="{% url 'logout' %}">Logout</a>
                  </li>

Login, please

The login template was by far the biggest section of work (and it wasn’t that large).  The template is very simple, just providing a form for the login information.  I’m still not happy with how it turned out, however.
I started by using the base.html like all of the other templates.  This, unfortunately put the navbar on the login page, which seemed odd.  So I ended up replicating some of the base.html code to get my css theme applied.  I’m still not happy with this solution, however, as I suspect there should be a better way not repeat code.  I might explore some of these options at a later point.

Another issue with the login template is that it wants to live in the ‘registration’ app by default.  Rather than figuring out how to change the default, I simply added a registration directory under the tasks app’s templates directory.  (I started by creating a top-level registration app which simply had the template in it.   I liked this even less.).  This is another ‘technical-debt’ issue that I hope to clean up in the future.

The template itself is seen below.  Nothing too shocking; basic html wrapper, bootstrap initialization, and a form.

<html>
<div>
   <head>
   {% load bootstrap3 %}
   {% bootstrap_css %}
   {% bootstrap_javascript %}
   {% bootstrap_messages %}

   <title>KidTasks</title>
   </head>
   <body>

   <div class="page-header">
      <h1>{% block title %}KidTasks Login{% endblock %}</h1>
   </div>

   <div>
      <form method="post">
         {% csrf_token %}
         {% bootstrap_form form %}
         {% buttons %}
         <button type="submit" name="save" value=True class="btn btn-success">Login</button>
         {% endbuttons %}
      </form>
   </div>
   </body>
</div>
</html>

Login Required!

Once you’ve done all of the above, you’ll be able to log in to your app.  The app will still allow you to access all of the pages without logging in.  It turns out there is a remarkably clever and simple solution for this: the @login_required decorator.

In your views file, you’ll need to import

from django.contrib.auth.decorators import login_required

and then add this decorator to each view function you want to be restricted.

@login_required

Works like a charm!

While I left a couple of loose ends I’ll eventually have to clean up, the task of adding login restrictions to KidsTasks was much easier than I expected.

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.