Matthew Daly's Blog

I'm a web developer in Norfolk. This is my blog...

My First jQuery Plugin

In my day job, I recently finished PagePooch, a tool for monitoring web pages for changes in content or prices. It’s a project that I’m immensely proud of, because it was my first big and largely solo project as a developer.

During the development of this, I initially created the user interface with jQuery Mobile, but later on added a desktop interface as well. During development of the desktop interface, I needed to recreate the functionality of the filter available for listviews in jQuery Mobile, and wound up creating a plugin for jQuery to do so.

I recently got permission to open-source this, as I figured the functionality was trivial enough that we couldn’t reasonably sell it, but by making it freely available, we’d maybe get some goodwill, and if we were really lucky, a few bugfixes and/or improvements.

It’s now available via the jQuery plugin registry. Please let me know what you think, and feel free to fork it and hack on it as you see fit.

Running the PHP Development Server With Laravel

One thing that I’ve really never liked about PHP is the requirement to install a full-blown web server, so I was pleased when PHP 5.4 shipped with a built-in development server. However, it seems like no PHP framework has yet embraced this to the extent that their counterparts for languages like Python have.

I’ve recently decided that CodeIgniter is no longer fulfilling what I need from my main go-to PHP framework, and I’ve been looking at Laravel as a likely replacement. It occurred to me that I could create an Artisan task to run the development server quite easily, and after a little tinkering, I put this together, which worked well:

application/tasks/runserver.php
1
2
3
4
5
6
7
8
9
10
11
12
<?php

class Runserver_Task {

    public function run($arguments)
    {
        $port = !isset($arguments[0]) ? 8000: $arguments[0];
        echo 'Running PHP development server on port '.$port.'...';
        passthru('php -S localhost:'.$port.' -t '.getcwd().'/public');
    }
}
?>

Once this is in place, you can just run php artisan runserver to run the development server, and hit Ctrl-C to stop it, giving you an experience much like that with Django. Note this requires PHP 5.4 or greater. You also have the option of specifying a different port eg php artisan runserver 7000 for port 7000.

Testing PHP Web Applications With Cucumber

Ever since I first heard of Cucumber, it’s seemed like something I would find really useful. Like many developers, especially those who use PHP regularly, I know full well that I should make a point of writing proper automated tests for my web apps, but invariably wind up just thinking “I haven’t got time to get my head around a testing framework and it’ll take ages to set up, so I’ll just click around and look for bugs”. This does get very, very tedious quite quickly, however.

At work I’ve reached a point with a web app I’m building where I needed to test it extensively to make sure it worked OK. I soon began to get very, very fed up of the repetitive clicking around necessary to test the application, so I began looking around for a solution. I gave Selenium IDE a try, but I found that to be annoyingly unreliable when recording tests. I’d heard of Cucumber, so I did some googling, found some resources, and began tinkering with that. Quite quickly, I had a few basic acceptance tests up and running that were much more reliable than Selenium IDE, and much less tedious to use than manual testing. Within a very short space of time, I realised that Cucumber was one of those tools that was going to dramatically improve my coding experience, much like when I switched from Subversion to Git.

What’s so great about Cucumber compared to other acceptance testing solutions?

  • Cucumber scenarios are written using Gherkin, a simple syntax that makes it easy for customers to set out exactly what behaviour they want to see. Far from being tedious requirement documents, these set out in a simple and intuitive way what should happen once the application is complete. By requiring customers to think carefully about what they want and get it down in writing, you can ensure the customer has a good idea what they want before you write any code, making it much less likely they’ll turn around afterwards and say “No, that’s not what we want”. This, more than anything, is for me the true power of Cucumber - it allows customers and developers to easily collaborate to set out what the web app will do, and gets you automated tests into the bargain as well.
  • Because Cucumber is packaged as a Ruby gem, it’s easy to install it and any other Ruby modules it may require.
  • You can use Capybara to test your web app. Capybara is a very handy Ruby gem that allows you to easily interact with your web app, and it allows several different drivers to be used. If you don’t need JavaScript, for instance, you can use Mechanize for faster tests. If you do, you can use selenium-webdriver to automate the browser instead, and it will load an instance of Firefox and use that for testing.
  • It can also be used for testing RESTful web services. HTTParty is another handy Ruby gem that can be used for testing an API.

One question you may ask is ‘Why use a Ruby tool to test PHP apps?’. Well, there is Behat, a very similar tool for PHP, so you can use that if you’d prefer. However, I personally have found that it’s not too much of a problem switching context between writing Ruby code for the acceptance tests and PHP code for the application itself. Ruby also has some advantages here - RVM is a very handy tool for running multiple instances of Ruby, and RubyGems makes it easy to install any additional modules you may need. You don’t really need to know much Ruby to use it - this is essentially my first encounter with Ruby barring a few small tutorials, but I haven’t had any significant issues with it. Finally, the Cucumber community seems to be very active, which is always a plus.

When searching for a tutorial on getting Cucumber working with PHP, I only found one good one, and that didn’t cover a lot of the issues I’d have liked to cover, not did it cover actually using Cucumber as part of the development process, so I had to puzzle out much of it myself. So hopefully, by covering more of the ground that your average PHP developer is likely to need, I can show you just how useful Cucumber can be when added to your PHP development toolkit.

In this tutorial, we’ll build a very simple todo-list application using the Slim framework, but we’ll use Cucumber to test it throughout to ensure that it works the way we want it to. Hopefully, by doing this, we’ll get a rock-solid web app that meets our requirements exactly.

First of all, you’ll want to install RVM to make it easier to manage multiple Ruby installs. You may be able to use your system’s Ruby install, but RVM is usually a safer bet:

1
\curl -L https://get.rvm.io | bash -s stable --ruby

This was sufficient to install RVM on Mac OS X. On Ubuntu, I also had to install the openssl and zlib packages. Before installing RVM, use apt-get to install the required packages:

1
sudo apt-get install curl git git-core zlib1g-dev zlibc libxml2-dev libxslt1-dev libyaml-dev build-essential checkinstall openssl libreadline6 libreadline6-dev zlib1g libssl-dev libsqlite3-dev sqlite3 autoconf libc6-dev ncurses-dev automake libtool bison subversion pkg-config

Once RVM is installed, then close and reopen your terminal so that RVM is loaded. Then, install the correct packages:

1
rvm pkg install openssl zlib

Now we can install our new copy of Ruby. On Ubuntu, I had to install Ruby 1.8.7 first:

1
2
rvm install 1.8.7
rvm use 1.8.7

Then I installed Ruby 1.9.3:

1
rvm install 1.9.3 --with-openssl-dir=$HOME/.rvm/usr

Whereas on OS X, this is all that was required:

1
rvm install 1.9.3

Once that’s done, run the following to set the version of Ruby being used

1
rvm use 1.9.3

With that done, you should be able to install the required Ruby gems. Now, you could install these manually, like this:

1
2
3
4
5
6
gem install cucumber
gem install rspec
gem install mechanize
gem install capybara
gem install selenium-webdriver
gem install capybara-mechanize

However, there’s a more convenient way. First, create a file in the project’s root directory called Gemfile and put the following content into it:

Gemfile
1
2
3
4
5
6
7
source "http://rubygems.org"
gem "cucumber"
gem "rspec"
gem "mechanize"
gem "capybara"
gem "selenium-webdriver"
gem "capybara-mechanize"

Then install the Bundler gem:

1
gem install bundler

Then use Bundler to install the required gems:

1
bundle install

This makes it easier to get your project set up somewhere else because you can put the Gemfile under version control, making it easier to duplicate this setup elsewhere.

With that out of the way, let’s start work on our app. To save time, we’ll use the Slim framework to do some of the heavy lifting for our application. Download Slim and put it in a folder on your local web server.

Now, before we actually write any code, we’ll set out our first Cucumber scenario. Create a folder inside the folder you put Slim inside and call it features. Inside it, create a new file called todo.feature and put the following content into it:

features/todo.feature
1
2
3
4
5
6
7
8
9
10
11
12
Feature: Todo

    In order to use the site
    As a user
    I want to be able to submit, view and delete to-do list items

    Scenario: New item
        Given I am on the home page
        When I click on New Item
        And I fill in the item
        And I click the button Submit
        Then I should see the new item added to the list

Notice how simple this is? Everything is written as an example of how an end user would interact with the site. There’s nothing hard about this - it just describes what the site needs to do.

The first line is just the name of this feature. The following three lines are just a comment. Then the Scenario line gives a name to this particular scenario - a Scenario is just a series of steps that describes an action.

Then, we see the Given line. This sets out the starting conditions. Note that you can easily set out multiple starting conditions using the And keyword on subsequent lines, as we do later in the file. Here, we’re just making sure we’re on the home page.

Next, we see the When line. This, and the subsequent And lines, set out what actions we want to take when going through this step. In this example, we’re clicking on a link marked ‘New Item’, filling in a text input, and clicking the Submit button. So we’re already thinking about how our application is going to work, before we’ve written a line of code.

Finally, we see the Then line. This sets out what should have happened once we’ve finished going through this step. Here we want to make sure the new item has been added to the list.

Now, go to the folder you unpacked Slim into and run cucumber from the shell. You should see something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
Feature: Todo

    In order to use the site
    As a user
    I want to be able to submit, view and delete to-do list items

  Scenario: New item                                 # features/todo.feature:7
    Given I am on the home page                      # features/todo.feature:8
    When I click on New Item                         # features/todo.feature:9
    And I fill in the item                           # features/todo.feature:10
    And I click the button Submit                    # features/todo.feature:11
    Then I should see the new item added to the list # features/todo.feature:12

1 scenario (1 undefined)
5 steps (5 undefined)
0m0.004s

You can implement step definitions for undefined steps with these snippets:

Given /^I am on the home page$/ do
  pending # express the regexp above with the code you wish you had
end

When /^I click on New Item$/ do
  pending # express the regexp above with the code you wish you had
end

When /^I fill in the item$/ do
  pending # express the regexp above with the code you wish you had
end

When /^I click the button Submit$/ do
  pending # express the regexp above with the code you wish you had
end

Then /^I should see the new item added to the list$/ do
  pending # express the regexp above with the code you wish you had
end

If you want snippets in a different programming language,
just make sure a file with the appropriate file extension
exists where cucumber looks for step definitions.

At this stage, Cucumber isn’t doing anything much, it’s just telling you that these steps haven’t been defined as yet. To define a step, you simply write some Ruby code that expresses that step.

Let’s do that. Under features, create a new directory called step_definitions. Inside that, create a file called todo_steps.rb and paste the code snippets returned by Cucumber into it. Once that has been saved, run cucumber again and you should see something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Feature: Todo

    In order to use the site
    As a user
    I want to be able to submit, view and delete to-do list items

  Scenario: New item                                 # features/todo.feature:7
    Given I am on the home page                      # features/step_definitions/todo_steps.rb:1
      TODO (Cucumber::Pending)
      ./features/step_definitions/todo_steps.rb:2:in `/^I am on the home page$/'
      features/todo.feature:8:in `Given I am on the home page'
    When I click on New Item                         # features/step_definitions/todo_steps.rb:5
    And I fill in the item                           # features/step_definitions/todo_steps.rb:9
    And I click the button Submit                    # features/step_definitions/todo_steps.rb:13
    Then I should see the new item added to the list # features/step_definitions/todo_steps.rb:17

1 scenario (1 pending)
5 steps (4 skipped, 1 pending)
0m0.004s

So far, the steps we’ve written don’t actually do anything - each step contains nothing but the pending statement. We need to replace the code inside each of those steps with some Ruby code that implements that step. As the first step in this scenario is still pending, Cucumber skips all the remaining steps.

Let’s implement these steps. First of all, we need to set some configuration options. In the features folder, create a new folder called support, and under that create a new file called env.rb. In there, place the following code:

features/support/env.rb
1
2
3
4
5
6
7
8
9
10
11
12
require 'rspec/expectations'
require 'capybara'
require 'capybara/mechanize'
require 'capybara/cucumber'
require 'test/unit/assertions'
require 'mechanize'

World(Test::Unit::Assertions)

Capybara.default_driver = :mechanize
Capybara.app_host = "http://localhost"
World(Capybara)

This includes all of the Ruby gems required for our purposes, and sets Capybara to use the Mechanize driver for testing web apps. If you’ve not heard of it before, Capybara can be thought of as a way of scripting a web browser that supports numerous drivers, some of which are headless and some of which aren’t. Here we’re using Mechanize, which is headless, but later on we’ll use Selenium to show you how it would work with a non-headless web browser.

With that done, the next job is to actually implement the steps. Head back to features/step_definitions/todo_steps.rb and edit it as follows:

features/step_definitions/todo_steps.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Given /^I am on the home page$/ do
    visit "http://localhost/~matthewdaly/todo/index.php"
end

When /^I click on New Item$/ do
      pending # express the regexp above with the code you wish you had
end

When /^I fill in the item$/ do
      pending # express the regexp above with the code you wish you had
end

When /^I click the button Submit$/ do
      pending # express the regexp above with the code you wish you had
end

Then /^I should see the new item added to the list$/ do
      pending # express the regexp above with the code you wish you had
end

Don’t forget to replace the URL in that first step with the one pointing at your index.php for your local copy of Slim. At this point we’re only implementing the first step, so that’s all we need to do for now. Once that’s done, go back to the root of the web app and run cucumber again. You should see something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Feature: Todo

    In order to use the site
    As a user
    I want to be able to submit, view and delete to-do list items

  Scenario: New item                                 # features/todo.feature:7
    Given I am on the home page                      # features/step_definitions/todo_steps.rb:1
    When I click on New Item                         # features/step_definitions/todo_steps.rb:5
      TODO (Cucumber::Pending)
      ./features/step_definitions/todo_steps.rb:6:in `/^I click on New Item$/'
      features/todo.feature:9:in `When I click on New Item'
    And I fill in the item                           # features/step_definitions/todo_steps.rb:9
    And I click the button Submit                    # features/step_definitions/todo_steps.rb:13
    Then I should see the new item added to the list # features/step_definitions/todo_steps.rb:17

1 scenario (1 pending)
5 steps (3 skipped, 1 pending, 1 passed)
0m0.036s

Our first step has passed! Now, we move onto the next step. Open features/step_definitions/todo_steps.rb again, and amend the second step definition as follows:

features/step_definitions/todo_steps.rb
1
2
3
When /^I click on New Item$/ do
   click_link ('New Item')
end

Now, hang on a minute here. This Ruby code is pretty easy to understand - it just clicks on a link with the title, ID or text ‘New Item’. But we don’t want to have to rewrite this step for every single link in the application. Wouldn’t it be great if we could have this step definition accept any text and click on the appropriate link, so we could reuse it elsewhere? Well, we can. Change the second step to look like this:

features/step_definitions/todo_steps.rb
1
2
3
When /^I click on (.*)$/ do |link|
    click_link (link)
end

What’s happening here is that we capture the text after the word ‘on’ using a regular expression and pass it through to the step definition as the variable link. Then, we have Capybara click on that link. Pretty simple, and it saves us on some work in future.

Now run cucumber again, and you should see something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Feature: Todo

    In order to use the site
    As a user
    I want to be able to submit, view and delete to-do list items

  Scenario: New item                                 # features/todo.feature:7
    Given I am on the home page                      # features/step_definitions/todo_steps.rb:1
    When I click on New Item                         # features/step_definitions/todo_steps.rb:5
      no link with title, id or text 'New Item' found (Capybara::ElementNotFound)
      (eval):2:in `send'
      (eval):2:in `click_link'
      ./features/step_definitions/todo_steps.rb:6:in `/^I click on (.*)$/'
      features/todo.feature:9:in `When I click on New Item'
    And I fill in the item                           # features/step_definitions/todo_steps.rb:9
    And I click the button Submit                    # features/step_definitions/todo_steps.rb:13
    Then I should see the new item added to the list # features/step_definitions/todo_steps.rb:17

Failing Scenarios:
cucumber features/todo.feature:7 # Scenario: New item

1 scenario (1 failed)
5 steps (1 failed, 3 skipped, 1 passed)
0m0.042s

We’ve got our second step in place, but it’s failing because there is no link with the text ‘New Item’. Let’s remedy that. Head back to the folder you put Slim in, and open index.php.

index.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
require 'Slim/Slim.php';

\Slim\Slim::registerAutoloader();

$app = new \Slim\Slim();

// GET route
$app->get('/', function () {
    $template = <<<EOT
<!DOCTYPE html>
<html>
    <head>
        <title>Todo list</title>
    </head>
    <body>
        <a href="index.php/newitem">New Item</a>
    </body>
</html>
EOT;
    echo $template;
});

$app->run();
?>

Here I’ve stripped out most of the default code and comments so we can see more easily what’s happening. If you haven’t used Slim before, it works by letting you define routes that are accessed via HTTP GET, POST, PUT or DELETE methods, and define what the response will be to each one. Here, we’ve defined a simple controller for GET requests to ‘/’, and we return a template that includes a link with the text ‘New Item’.

Now, run cucumber again and you should see the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Feature: Todo

    In order to use the site
    As a user
    I want to be able to submit, view and delete to-do list items

  Scenario: New item                                 # features/todo.feature:7
    Given I am on the home page                      # features/step_definitions/todo_steps.rb:1
    When I click on New Item                         # features/step_definitions/todo_steps.rb:5
      Received the following error for a GET request to http://localhost/~matthewdaly/todo/newitem: '404 => Net::HTTPNotFound for http://localhost/~matthewdaly/todo/newitem -- unhandled response' (RuntimeError)
      (eval):2:in `send'
      (eval):2:in `click_link'
      ./features/step_definitions/todo_steps.rb:6:in `/^I click on (.*)$/'
      features/todo.feature:9:in `When I click on New Item'
    And I fill in the item                           # features/step_definitions/todo_steps.rb:9
    And I click the button Submit                    # features/step_definitions/todo_steps.rb:13
    Then I should see the new item added to the list # features/step_definitions/todo_steps.rb:17

Failing Scenarios:
cucumber features/todo.feature:7 # Scenario: New item

1 scenario (1 failed)
5 steps (1 failed, 3 skipped, 1 passed)
0m0.153s

Our second step is still failing, but only because we haven’t yet defined a route for the destination when we click on the link, so let’s fix that. Open up index.php again and change it to look like this:

index.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php
require 'Slim/Slim.php';

\Slim\Slim::registerAutoloader();

$app = new \Slim\Slim();

// GET route
$app->get('/', function () {
    $template = <<<EOT
<!DOCTYPE html>
<html>
    <head>
        <title>Todo list</title>
    </head>
    <body>
        <a href="index.php/newitem">New Item</a>
    </body>
</html>
EOT;
    echo $template;
});

$app->get('/newitem', function () {
    $template = <<<EOT
<!DOCTYPE html>
<html>
    <head>
        <title>Todo list</title>
    </head>
    <body>
        <form action="index.php/submitnewitem" method="POST">
            <label>New todo item text<input type="text" name="item" /></label>
            <input type="submit" value="Submit" />
        </form>
    </body>
</html>
EOT;
    echo $template;
});

$app->run();
?>

We’re just adding a new route to handle what happens when we click the link here. The new page also has a form for submitting the new item.

With that done, the second step should be in place. Run cucumber again and you should see something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Feature: Todo

    In order to use the site
    As a user
    I want to be able to submit, view and delete to-do list items

  Scenario: New item                                 # features/todo.feature:7
    Given I am on the home page                      # features/step_definitions/todo_steps.rb:1
    When I click on New Item                         # features/step_definitions/todo_steps.rb:5
    And I fill in the item                           # features/step_definitions/todo_steps.rb:9
      TODO (Cucumber::Pending)
      ./features/step_definitions/todo_steps.rb:10:in `/^I fill in the item$/'
      features/todo.feature:10:in `And I fill in the item'
    And I click the button Submit                    # features/step_definitions/todo_steps.rb:13
    Then I should see the new item added to the list # features/step_definitions/todo_steps.rb:17

1 scenario (1 pending)
5 steps (2 skipped, 1 pending, 2 passed)
0m0.048s

So onto the third step. We’ve already created the input for filling in the item, so all we need to do to make this step pass is write an appropriate step definition:

features/step_definitions/todo_steps.rb
1
2
3
When /^I fill in the item$/ do
    fill_in 'item', :with => 'Feed cat'
end

With that done, run cucumber again and this step should pass:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Feature: Todo

    In order to use the site
    As a user
    I want to be able to submit, view and delete to-do list items

  Scenario: New item                                 # features/todo.feature:7
    Given I am on the home page                      # features/step_definitions/todo_steps.rb:1
    When I click on New Item                         # features/step_definitions/todo_steps.rb:5
    And I fill in the item                           # features/step_definitions/todo_steps.rb:9
    And I click the button Submit                    # features/step_definitions/todo_steps.rb:13
      TODO (Cucumber::Pending)
      ./features/step_definitions/todo_steps.rb:14:in `/^I click the button Submit$/'
      features/todo.feature:11:in `And I click the button Submit'
    Then I should see the new item added to the list # features/step_definitions/todo_steps.rb:17

1 scenario (1 pending)
5 steps (1 skipped, 1 pending, 3 passed)
0m0.117s

Now we need to implement the step for clicking the Submit button. As with clicking on the New Item link, we can make this step generic to save us time later:

features/step_definitions/todo_steps.rb
1
2
3
When /^I click the button (.*)$/ do |button|
    click_button (button)
end

With that done, run cucumber again and you should see something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Feature: Todo

    In order to use the site
    As a user
    I want to be able to submit, view and delete to-do list items

  Scenario: New item                                 # features/todo.feature:7
    Given I am on the home page                      # features/step_definitions/todo_steps.rb:1
    When I click on New Item                         # features/step_definitions/todo_steps.rb:5
    And I fill in the item                           # features/step_definitions/todo_steps.rb:9
    And I click the button Submit                    # features/step_definitions/todo_steps.rb:13
      Received the following error for a POST request to http://localhost/~matthewdaly/todo/index.php/index.php/submitnewitem: '404 => Net::HTTPNotFound for http://localhost/~matthewdaly/todo/index.php/index.php/submitnewitem -- unhandled response' (RuntimeError)
      (eval):2:in `send'
      (eval):2:in `click_button'
      ./features/step_definitions/todo_steps.rb:14:in `/^I click the button (.*)$/'
      features/todo.feature:11:in `And I click the button Submit'
    Then I should see the new item added to the list # features/step_definitions/todo_steps.rb:17

Failing Scenarios:
cucumber features/todo.feature:7 # Scenario: New item

1 scenario (1 failed)
5 steps (1 failed, 1 skipped, 3 passed)
0m0.210s

The step is failing here because submitting the new item generates a 404 error. We need to handle the POST. Open up index.php again and edit it to look like this:

index.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<?php
require 'Slim/Slim.php';

\Slim\Slim::registerAutoloader();

$app = new \Slim\Slim();

// GET route
$app->get('/', function () {
    $template = <<<EOT
<!DOCTYPE html>
<html>
    <head>
        <title>Todo list</title>
    </head>
    <body>
        <a href="index.php/newitem">New Item</a>
    </body>
</html>
EOT;
    echo $template;
});

$app->get('/newitem', function () {
    $template = <<<EOT
<!DOCTYPE html>
<html>
    <head>
        <title>Todo list</title>
    </head>
    <body>
        <form action="index.php/submitnewitem" method="POST">
            <label>New todo item text<input type="text" name="item" /></label>
            <input type="submit" value="Submit" />
        </form>
    </body>
</html>
EOT;
    echo $template;
});

$app->post('/submitnewitem', function () {
    $item = $_POST['item'];
    $template = <<<EOT
<!DOCTYPE html>
<html>
    <head>
        <title>Todo list</title>
    </head>
    <body>
        <p>$item</p>
    </body>
</html>
EOT;
    echo $template;
});

$app->run();
?>

Here we’re cheating a little bit. In a working application we’d want to store the to-do list items in a database, but to keep this tutorial simple we’ll just output the result of the POST request and leave implementing a database to store the items as an exercise for the reader.

Now, run cucumber again and you should see that this step now passes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Feature: Todo

    In order to use the site
    As a user
    I want to be able to submit, view and delete to-do list items

  Scenario: New item                                 # features/todo.feature:7
    Given I am on the home page                      # features/step_definitions/todo_steps.rb:1
    When I click on New Item                         # features/step_definitions/todo_steps.rb:5
    And I fill in the item                           # features/step_definitions/todo_steps.rb:9
    And I click the button Submit                    # features/step_definitions/todo_steps.rb:13
    Then I should see the new item added to the list # features/step_definitions/todo_steps.rb:17
      TODO (Cucumber::Pending)
      ./features/step_definitions/todo_steps.rb:18:in `/^I should see the new item added to the list$/'
      features/todo.feature:12:in `Then I should see the new item added to the list'

1 scenario (1 pending)
5 steps (1 pending, 4 passed)
0m0.067s

On to our final step. We want to make sure the page contains the text we submitted, which is very easy to do with Capybara. Change the final step to look like this:

features/step_definitions/todo_steps.rb
1
2
3
Then /^I should see the new item added to the list$/ do
    page.should have_content('Feed cat')
end

Now run cucumber again and you should see that the scenario has now passed:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Feature: Todo

    In order to use the site
    As a user
    I want to be able to submit, view and delete to-do list items

  Scenario: New item                                 # features/todo.feature:7
    Given I am on the home page                      # features/step_definitions/todo_steps.rb:1
    When I click on New Item                         # features/step_definitions/todo_steps.rb:5
    And I fill in the item                           # features/step_definitions/todo_steps.rb:9
    And I click the button Submit                    # features/step_definitions/todo_steps.rb:13
    Then I should see the new item added to the list # features/step_definitions/todo_steps.rb:17

1 scenario (1 passed)
5 steps (5 passed)
0m0.068s

We’re nearly done here, but first there’s a couple of other handy things you can do with Cucumber that I’d like to show you. We’ve been using the Mechanize driver for Capybara, which is very fast and efficient. However, it’s effectively a text-mode browser like Lynx, so it can’t be used to test any functionality that relies on JavaScript. However, Mechanize isn’t the only driver available for Capybara, and you can switch to the JavaScript driver when necessary so you can test. The default JavaScript driver is Selenium, which will launch an instance of Firefox and use that for the test.

It’s easy to switch to the JavaScript driver when you need it. Just tag the scenario with @javascript, as in this example:

features/todo.feature
1
2
3
4
5
6
7
8
9
10
11
12
13
Feature: Todo

    In order to use the site
    As a user
    I want to be able to submit, view and delete to-do list items

    @javascript
    Scenario: New item
        Given I am on the home page
        When I click on New Item
        And I fill in the item
        And I click the button Submit
        Then I should see the new item added to the list

Now run cucumber again and this time it will fire up an instance of Firefox and use that to run the tests. This can also be handy for debugging purposes since, unlike with Mechanize, you can see the pages.

Finally, what about if you want to test the same functionality multiple times with different input? You don’t want to have to write out multiple scenarios that are virtually identical, even if you have refactored them to make them more useful. What you need is a way to repeat the same test, only with different input each time.

Handily, Cucumber can do this too. First, let’s refactor the code for our step definitions so the final step can handle any text:

features/step_definitions/todo_steps.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Given /^I am on the home page$/ do
    visit "http://localhost/~matthewdaly/todo/index.php"
end

When /^I click on (.*)$/ do |link|
    click_link (link)
end

When /^I fill in the item with (.*)$/ do |item|
    fill_in 'item', :with => item
end

When /^I click the button (.*)$/ do |button|
    click_button (button)
end

Then /^I should see the text (.*)$/ do |text|
    page.should have_content(text)
end

Here we’ve changed the third and fifth items so we can pass any value we want through to them. As I mentioned earlier, this is good practice since it means we don’t have to write more code for our tests than we need to.

With that done, open up the feature file and amend it to look like this:

features/todo.feature
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Feature: Todo

    In order to use the site
    As a user
    I want to be able to submit, view and delete to-do list items

    @javascript
    Scenario Outline: New item
        Given I am on the home page
        When I click on New Item
        And I fill in the item with <item>
        And I click the button Submit
        Then I should see the text <item>
    Examples:
        | item                      |   
        | Feed cat                  |   
        | Stop milk                 |   
        | Take over world           |

If you then run cucumber again, the scenario should run three times, each time entering different text:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Feature: Todo

    In order to use the site
    As a user
    I want to be able to submit, view and delete to-do list items

  @javascript
  Scenario Outline: New item           # features/todo.feature:8
    Given I am on the home page        # features/step_definitions/todo_steps.rb:1
    When I click on New Item           # features/step_definitions/todo_steps.rb:5
    And I fill in the item with <item> # features/step_definitions/todo_steps.rb:9
    And I click the button Submit      # features/step_definitions/todo_steps.rb:13
    Then I should see the text <item>  # features/step_definitions/todo_steps.rb:17

    Examples:
      | item            |
      | Feed cat        |
      | Stop milk       |
      | Take over world |

3 scenarios (3 passed)
15 steps (15 passed)
0m25.936s

With only a few changes, we’re now running the same scenario over and over again with different input, and testing the output is correct for each one. This makes it very easy to test repetitive content. For instance, if you had an e-commerce site with lots of products and you wanted to test the pages for some of the products, you could put them in a table like this. You can have more than one column if necessary, so you could write a scenario like this:

features/todo.feature
1
2
3
4
5
6
7
8
9
10
Scenario Outline: Test products
    Given I am on the home page
    When I search for <product>
    And I click on the first result
    Then I should not see any errors
    And I should see the text <productname>

Examples:
    | product           | productname                           |
    | supersprocket     | Super Sprocket 3000                   |

As you can see, Cucumber is a really simple way to start testing your web apps, and can really improve the quality of your code. Even if you’ve never used Ruby before, Capybara’s API is very simple and intuitive, and should adequately cover most of what you need to do when testing a web app.

As I mentioned, the PHP community in general has been a bit slack in terms of getting proper automated tests working. But Cucumber makes it so simple, and offers so many other benefits, such as human-readable tests and getting stakeholders more involved in the development process, that there’s really no excuse not to use it. Hope you’ve enjoyed this tutorial, and that it’s encouraged you to start using Cucumber to test your own web apps.

Counting Commits in Git

The other day I got to wondering about how I could quickly and easily count how many commits I was making to a specific Git repository every day.

Now, with the default log output, Git shows the date and time at the top of each entry. So in order to determine how many commits have been made to a given repository, all you need to do is determine today’s date, and see how many times that date appears in the log.

So, first task is to get the log. Then, we need to get today’s date, format it appropriately, and use grep to filter for just the lines containing that date. So, we use the date command to fetch the date, then pipe it into awk, which we use to format it the same way as Git does. We then use grep to filter out the lines that have the resulting date. Finally, we use wc to count the number of lines returned by grep.

Here’s what I came up with:

1
git log | grep "`date | awk '{print $1, $2, $3}'`" | wc -l

I’m not that great with tools like awk, so I’m quite proud of managing to do this. It’s quite handy to have around.

Falling Back to Local Copies of jQuery and jQuery Mobile

I use jQuery Mobile a lot at work, and it’s brilliant. For quickly knocking together a high-quality user interface that works well on mobile devices, it’s unbeatable.

Like many web developers, I favour using a CDN-hosted version of both jQuery and jQuery Mobile, because it makes it more likely that the user won’t have to download the appropriate files as their web browser has already cached them. However, by doing this you run the risk of your site being negatively affected if the CDN provider goes down for any length of time. So, I think it’s a good idea to have a fallback for both jQuery and jQuery Mobile.

I spent a while looking and finally managed to come up with a solution that works well, and borrows heavily from a similar solution for jQuery UI by Tim James. First of all, load the stylesheets and JavaScript files as usual:

1
2
3
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.1.1/jquery.mobile.structure-1.1.1.min.css" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script src="http://code.jquery.com/mobile/1.1.1/jquery.mobile-1.1.1.min.js"></script>

Below this, place the following inside another set of script tags:

1
2
3
4
if (typeof jQuery == 'undefined')
{
    document.write(unescape("%3Cscript src='http://path-to-jquery.min.js' type='text/javascript'%3E%3C/script%3E"));
}

If jQuery is not defined, this will write another set of script tags that will load the local copy of jQuery. Now, inside a second set of script tags, place the following code:

1
2
3
4
5
6
7
8
9
10
if(typeof $.mobile == 'undefined')
{
    document.write(unescape("%3Cscript src='http://path-to-jquery-mobile.min.js' type='text/javascript'%3E%3C/script%3E"));
}

$(function() {
    if($('.ui-helper-hidden:first').is(':visible') === true){
        $('<link rel="stylesheet" type="text/css" href="http://path-to-jquery-mobile-structure.min.css" />').appendTo('head');
    }
});

This won’t work if it’s placed in the same set of script tags as the code above, because it requires that jQuery be working already. The first part works similarly to the jQuery fallback - if jQuery Mobile is not defined, it writes a new script tag. The second relies on an element in the DOM with a class of ui-helper-hidden, which jQuery Mobile would hide by default if it were loaded. If it is visible, the jQuery Mobile structure CSS file has not been loaded and so a link to the local copy of the stylesheet is created. Of course, this means you have to create this element, so add the following code to the very top of the body, directly under the opening body tag:

1
<div class="ui-helper-hidden"></div>

If you download copies of the appropriate files and set the paths to them correctly, you should now be able to enjoy all the advantages of using a CDN for hosting jQuery and jQuery Mobile while also having the security of knowing that if the CDN goes down, your application will still work fine. Exactly the same approach will work with jQuery UI as well.

Dumping WordPress for Octopress

Lately I’ve gotten more and more fed up with WordPress as a blogging platform. It’s certainly a great content management system, and for non-technical bloggers it’s absolutely perfect, but I’ve been increasingly finding myself hitting barriers that make it harder than it should be to get stuff done. It’s just not the best platform for blogging about web development.

Take my recent series of Django tutorials, for example. You wouldn’t believe the amount of time I spent trying to format some of the code properly in TinyMCE for those. Furthermore, the end result, even though the theme was a custom one I’d built myself that was somewhat optimised for showing off code, wasn’t exactly great.

In my opinion, Markdown is a far better option for writing blog posts in than using a rich text editor like TinyMCE. You normally have a pretty good idea what the end results will look like, and it’s generally well-formatted HTML. Also, it means I can easily write my blog posts in Vim.

Octopress was therefore an obvious choice. It’s absolutely brilliant for sharing code - compared to WordPress, code samples look stunning. Also, because it generates static HTML, it loads an awful lot faster than WordPress does, and the default theme is extremely nice. So that’s what I’ve gone for.

The only issue is that my web host are a bit funny about offering SSH access, so I’ve resorted to FTP-ing the files across, which isn’t ideal. Still, it’s not that big an issue, and I’ll have to see how it goes with Octopress. I do have a low-end VPS I could point this domain name at instead and run it from there if necessary, so I can always resort to that if this is too cumbersome.

Yet Another Tutorial for Building a Blog Using Python and Django - Part 5

In this instalment I’ll be showing you how we can make our blogging engine a little nicer to look at by adding some CSS and images, as well as expanding on Django’s templating system.

First of all, let’s add some CSS to our blog. When developing a web app with Django, you should place static files such as stylesheets and images in a folder inside your app (not project) folder called static. My project is called DjangoBlog, and my app is called blogengine, so all my static content should go in DjangoBlog/blogengine/static/. Here’s the stylesheet, which I’ve saved as style.css:

blogengine/static/style.css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
body {
    background-color: #f0f0f0;
    font-family: Arial, Helvetica, sans-serif;
}

#main {
    width: 800px;
    height: 100%;
    margin: 50px auto;
}

ul#pageList {
    margin: 0px;
    padding: 10px 0px 10px 0px;
}

ul#pageList li {
    display: inline;
    margin-right: 10px;
    font-size: 18px;
}

.post, .page {
    width: 600px;
    padding: 20px;
    margin-bottom: 20px;
    background-color: #ffffff;
}

In the same folder, I have a PNG icon for an RSS feed, and if you had some JavaScript files you wanted to use (such as a copy of jQuery), you would put them here too. Note that there’s nothing to stop you creating subfolders within /static, and in fact I would recommend you do so for any future project so you can separate out images, CSS and JavaScript easily.

With that done, we now need to change our templates to make use of this CSS. Here’s what header.html should look like:

templates/header.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html>
    <head>
        <title>My Django Blog</title>
        <link rel="stylesheet" type="text/css" href="/static/style.css" />
    </head>
    <body>
        <div id="main">
            <h1>My Django Blog</h1>
            <a href="/feeds/posts/"><img src="/static/rss.png" width="50px" height="50px"></a>
            <ul id="pageList">
                <li><a href="/">Home</a></li>
                {% load flatpages %}
                {% get_flatpages as flatpages %}
                {% for flatpage in flatpages %}
                <li><a href="{{ flatpage.url }}">{{ flatpage.title }}</a></li>
                {% endfor %}
            </ul>

Next, here’s footer.html:

templates/footer.html
1
2
3
    </div>
</body>
</html>

Now here’s category.html:

templates/category.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{% include 'header.html' %}
        {% load comments %}
        <h1>Posts for {{ category.title }}</h1>
        {% if posts %}
        {% for post in posts %}
        <div class="post">
        <h1><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h1>
        {{ post.text }}
        {% get_comment_count for post as comment_count %}
        <h3>Comments: {{ comment_count }}</h3>
        </div>
        {% endfor %}
        <br />
        {% if page.has_previous %}
        <a href="/{{ page.previous_page_number }}/">Previous Page</a>
        {% endif %}
        {% if page.has_next %}
        <a href="/{{ page.next_page_number }}/">Next Page</a>
        {% endif %}
        {% else %}
        <div class="post">
        <p>No posts matched</p>
        </div>
        {% endif %}
{% include 'footer.html' %}

Then, posts.html:

templates/posts.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{% include 'header.html' %}
        {% load comments %}
        {% if posts %}
        {% for post in posts %}
        <div class="post">
        <h1><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h1>
        {{ post.text }}
        {% get_comment_count for post as comment_count %}
        <h3>Comments: {{ comment_count }}</h3>
        </div>
        {% endfor %}
        <br />
        {% if page.has_previous %}
        <a href="/{{ page.previous_page_number }}/">Previous Page</a>
        {% endif %}
        {% if page.has_next %}
        <a href="/{{ page.next_page_number }}/">Next Page</a>
        {% endif %}
        {% else %}
        <div class="post">
        <p>No posts matched</p>
        </div>
        {% endif %}
{% include 'footer.html' %}

Here’s single.html:

templates/single.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{% include 'header.html' %}
        {% load comments %}
        {% for post in posts %}
        <div class="post">
        <h1><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h1>
        <h3>{{ post.pub_date }}</h3>
        {{ post.text }}
        <h3>By {{ post.author.first_name }} {{ post.author.last_name }}</h3>
        <h3>Categories: {% for category in post.categories.all %} {{ category.title }} {% endfor %}</h3>
        {% get_comment_count for post as comment_count %}
        <h3>Comments: {{ comment_count }}</h3>
        <ol>
        {% get_comment_list for post as comments %}
        {% for comment in comments %}
        <li>{{ comment }}</li>
        </ol>
        {% endfor %}
        {% render_comment_form for post %}
        {% endfor %}
        </div>
        <br />
        {% if page.has_previous %}
        <a href="/{{ page.previous_page_number }}/">Previous Page</a>
        {% endif %}
        {% if page.has_next %}
        <a href="/{{ page.next_page_number }}/">Next Page</a>
        {% endif %}
{% include 'footer.html' %}

And finally, flatpages/default.html:

templates/flatpages/default.html
1
2
3
4
5
6
{% include 'header.html' %}
        <div class="page">
        <h1>{{ flatpage.title }}</h1>
        {{ flatpage.content }}
        </div>
{% include 'footer.html' %}

Phew! There’s quite a lot there, so you may wish to grab these files from the GitHub repository rather than enter them yourself.

Now, all of the references to the CSS or image file need to refer to the /static folder under the root of the web server. Here’s the reference to our stylesheet:

1
<link rel="stylesheet" type="text/css" href="/static/style.css" />

And here’s where we get the image:

1
<a href="/feeds/posts/"><img src="/static/rss.png" width="50px" height="50px"></a>

All of our static files can be referenced via the /static folder by default, without needing to set up a rule to cover them in urls.py.

One other point worth noting is that we’ve added some code to the header to display links to all of the flat pages. This particular snippet of code in header.html is noteworthy:

header.html
1
2
3
4
5
{% load flatpages %}
                {% get_flatpages as flatpages %}
                {% for flatpage in flatpages %}
                <li><a href="{{ flatpage.url }}">{{ flatpage.title }}</a></li>
                {% endfor %}

Here, we first of all load the flat pages. Then we retrieve them, and loop through each of them. For each page, we create a new list item containing a link to the flat page, with the text being the flat page’s title. Note that we’re just referring to each flatpage object’s attributes here. Then we end the for loop.

The only problem with this is that all of the pages except the flat pages are handled by the blogengine application, not the flatpages one, so we can’t get the values for the flat pages, so we need to amend blogengine/views.py. Open it and add the following line near the top:

blogengine/views.py
1
from django.contrib.flatpages.models import FlatPage

Now, nowhere else in the view is the FlatPage application needed, but it’s required in the template, so by importing it here we make it available in the template.

With that done, our Django-powered blog is beginning to look a bit more presentable, so I’ll leave it to you to style it however you wish, using this as a starting point. The blog is now pretty much feature-complete, however there’s one more thing I’d like to demonstrate before we finish up, namely generic views.

As you may have gathered by now, Django uses slightly different terminology to many other web development frameworks. Although it can be considered an MVC (Model-View-Controller) framework like many others, it’s generally referred to as an MTV (Model-Template-View) framework, with views containing the logic needed to present the data. While Django ships with a number of built-in applications to do certain repetitive tasks easily, not every task lends itself well to being handled by one generic application. However, these tasks may still require something similar be implemented over and over again, and that’s what generic views are for.

We don’t yet have a list of all of the available categories, so let’s use a generic view to do that. In urls.py, add the following lines at the top:

DjangoBlog/urls.py
1
2
from django.views.generic import ListView
from blogengine.models import Category

Then, add the following lines at the top of the section for categories:

DjangoBlog/urls.py
1
2
3
url(r'^categories/?$', ListView.as_view(
    model=Category,
    )),

Then, go into your templates folder and create a new folder in there called blogengine (or whatever you’re calling your blog application). In there, create a new file called category_list.html and enter the following code in it:

templates/blogengine/category_list.html
1
2
3
4
5
6
{% include 'header.html' %}
        {% for category in object_list %}
            <h3>{{ category.title }}</h3>
            <p>{{ category.description }}</p>
        {% endfor %}
{% include 'footer.html' %}

Now, ensure the development server is running, and go to http://127.0.0.1:8000/categories/, and you should see a list of your categories.

Now, you didn’t write a view for this at all. Instead, this is handled by a generic view. In urls.py, we imported the ListView generic view, which is nothing more than a list of objects. We then import the Category model. Then, we define a URLconf that maps the categories/ url to ListView, which displays a list of all the Category objects. The template used is determined automatically, and we create that template as normal. Note that in the template we refer to object_list - this demonstrates that we’re referring to the objects passed through generically, and in theory this same template could display any objects with attributes called title and description.

Now, it probably won’t have escaped your notice that a blog is effectively a list of posts, so can’t we use a generic view to display them? Well, yes we can! So why don’t we cut down on the amount of code we need to maintain and use a generic view, rather than writing our own view?

Go into blogengine/views.py and delete the getPosts function in its entirety. Next, go into urls.py and delete the part that deals with showing the posts (the two lines just under the Home page comment), and replace them with this:

DjangoBlog/urls.py
1
2
3
4
url(r'^(?P<page>\d+)?/?$', ListView.as_view(
    model=Post,
    paginate_by=5,
    )),

Note here that we specify how many items we paginate by. The ListView generic view supports pagination, making it ideally suited for any list of objects that may be spread across multiple pages - you just import the model you want and pass it through in the model parameter.

We also need to import the Post object in urls.py. Amend the line where you imported the Category model as follows:

DjangoBlog/urls.py
1
from blogengine.models import Category, Post

Then move posts.html into your templates/blogengine folder and rename it post_list.html, then amend it to look like the following:

templates/blogengine/post_list.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{% include 'header.html' %}
        {% load comments %}
        {% if object_list %}
        {% for post in object_list %}
        <div class="post">
        <h1><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h1>
        {{ post.text }}
        {% get_comment_count for post as comment_count %}
        <h3>Comments: {{ comment_count }}</h3>
        </div>
        {% endfor %}
        <br />
        {% if page_obj.has_previous %}
        <a href="/{{ page_obj.previous_page_number }}/">Previous Page</a>
        {% endif %}
        {% if page_obj.has_next %}
        <a href="/{{ page_obj.next_page_number }}/">Next Page</a>
        {% endif %}
        {% else %}
        <div class="post">
        <p>No posts matched</p>
        </div>
        {% endif %}
{% include 'footer.html' %}

The changes here are minimal, just adjusting the names of objects to what the generic view uses. Now, if you refresh the browser, you should be able to see your blog posts, only now they’re being handled by Django’s ListView generic view. But they’re in the wrong order, so go into blogengine/models.py and add this code to the Post model:

blogengine/models.py
1
2
class Meta:
    ordering = ["-pub_date"]

This defines the order the Post objects should be in the model, rather than the view. If you now refresh the browser, they should be in the right order.

A ListView is only one of the generic views available in Django. There are others that are useful under other circumstances, but they’re beyond the scope of this tutorial, so I suggest that if you’re interested, you take the time to learn more about them on your own. They can save you a lot of time and effort if used well.

Sadly, that brings this series of tutorials to an end. I hope you’ve enjoyed learning about Django, and I hope you’ll be inspired to build something cool with it! As always, the code is available on GitHub, so feel free to download it, use it as the basis for your own projects, or whatever else you’d like to do with it.

Yet Another Tutorial for Building a Blog Using Python and Django - Part 4

Welcome back! In this tutorial we’ll continue extending our Django-powered blogging engine. We’ll add the capability to assign blog posts to categories, and comment on posts. We’ll also generate an RSS feed for our blog posts.

Categories are somewhat tougher to implement than most of what we’ve done beforehand. One category can be assigned to many blog posts, and many categories can be assigned to one blog post, so this relationship is described as a “many to many relationship” when drawing up the database structure. What it means is that you can’t directly map categories onto posts and vice versa - you have to create an intermediate database table for the relationship between posts and categories.

Here’s what your models.py should look like:

blogengine/models.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
from django.db import models
from django.contrib.auth.models import User

# Create your models here. 
class Category(models.Model):
    title = models.CharField(max_length=200)
    slug = models.SlugField(max_length=40, unique=True)
    description = models.TextField()

    class Meta:
        verbose_name_plural = "Categories"

    def __unicode__(self):
        return self.title

    def get_absolute_url(self):
        return "/categories/%s/" % self.slug

class Post(models.Model):
    title = models.CharField(max_length=200)
    pub_date = models.DateTimeField()
    text = models.TextField()
    slug = models.SlugField(max_length=40, unique=True)
    author = models.ForeignKey(User)
    categories = models.ManyToManyField(Category, blank=True, null=True, through='CategoryToPost')

    def __unicode__(self):
        return self.title

    def get_absolute_url(self):
        return "/%s/%s/%s/" % (self.pub_date.year, self.pub_date.month, self.slug)

class CategoryToPost(models.Model):
    post = models.ForeignKey(Post)
    category = models.ForeignKey(Category)

We’re adding quite a bit of new code here. First of all we’re defining a new model called Category. Each category has a title, a description, and a slug (so we can have a dedicated page for each category). As usual, we define methods for unicode and get_absolute_url, but also note the class Meta. Here we’re defining some metadata for the class (ie, data about the data). The only thing we do here is essentially telling the admin interface that the plural of Category is not “Categorys” but “Categories”.

Then, in Post we add an additional field called Category, which we define as a ManyToManyField. Note the parameters passed through - we’re saying here that a post need not be assigned a category, and that CategoryToPost should be used as an intermediate table to link posts to categories.

Finally, we define the aforementioned CategoryToPost model, which has two fields, post and category. Both of these are foreign keys, mapping to a blog post and a category respectively. By creating entries in this table, a link can be created between a post and a category.

With our model changed, it’s time to update our admin.py as well:

blogengine/admin.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import models
from django.contrib import admin
from django.contrib.auth.models import User

class CategoryAdmin(admin.ModelAdmin):
    prepopulated_fields = {"slug": ("title",)}

class CategoryToPostInline(admin.TabularInline):
    model = models.CategoryToPost
    extra = 1

class PostAdmin(admin.ModelAdmin):
    prepopulated_fields = {"slug": ("title",)}
    exclude = ('author',)
    inlines = [CategoryToPostInline]

    def save_model(self, request, obj, form, change):
        obj.author = request.user
        obj.save()

admin.site.register(models.Post, PostAdmin)
admin.site.register(models.Category, CategoryAdmin)

Here we define a new class called CategoryAdmin, which details how we’re changing the admin interface for Category from the defaults generated from the fields provided. The only change we make here is that we prepopulate the slug field from the title, much like we did with blog posts.

Next, we define an inline for the relationships between categories and post, called CategoryToPostInline. This is a new concept - essentially it means that the category to post relationships can be defined in another model’s admin interface. We define the model this applies to, and that by default we will only add one additional field for adding categories when writing or editing a post (though users can add as many as they wish, or none). Note that the model this is based on is admin.TabularInline - this represents a tabular layout. If you prefer, you can use an alternative layout by using StackedInline instead.

Then, in PostAdmin we add our newly declared CategoryToPostInline to the PostAdmin class as an inline. Finally, at the bottom we register Category with the admin interface, so we can create and manage categories easily.

With that done, it’s time to edit our views.py:

blogengine/views.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# Create your views here.
from django.shortcuts import render_to_response
from django.core.paginator import Paginator, EmptyPage
from blogengine.models import Post, Category

def getPosts(request, selected_page=1):
    # Get all blog posts
    posts = Post.objects.all().order_by('-pub_date')

    # Add pagination
    pages = Paginator(posts, 5)

    # Get the specified page
    try:
        returned_page = pages.page(selected_page)
    except EmptyPage:
        returned_page = pages.page(pages.num_pages)

    # Display all the posts
    return render_to_response('posts.html', { 'posts':returned_page.object_list, 'page':returned_page})

def getPost(request, postSlug):
    # Get specified post
    post = Post.objects.filter(slug=postSlug)

    # Display specified post
    return render_to_response('single.html', { 'posts':post})

def getCategory(request, categorySlug, selected_page=1):
    # Get specified category
    posts = Post.objects.all().order_by('-pub_date')
    category_posts = []
    for post in posts:
        if post.categories.filter(slug=categorySlug):
            category_posts.append(post)

    # Add pagination
    pages = Paginator(category_posts, 5)

    # Get the category
    category = Category.objects.filter(slug=categorySlug)[0]

    # Get the specified page
    try:
        returned_page = pages.page(selected_page)
    except EmptyPage:
        returned_page = pages.page(pages.num_pages)

    # Display all the posts
    return render_to_response('category.html', { 'posts': returned_page.object_list, 'page': returned_page, 'category': category})

Here we import the Category model as well as the Post model. Then, the only additional change we need to make is to add a brand new getCategory view function. Note that this is quite similar to the getPosts function - we set up pagination in the same way, and rather than get all the posts, we get just those in the specified category. Also note that we’re using the template category.html rather than posts.html here, and we pass through category as well as posts and page when we return the render_to_response.

The next change we need to make is adding category.html. Go into your template directory and save the code below as category.html:

templates/category.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{% include 'header.html' %}
        <h1>Posts for {{ category.title }}</h1>
        {% if posts %}
        {% for post in posts %}
        <h1><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h1>
        {{ post.text }}
        {% endfor %}
        <br />
        {% if page.has_previous %}
        <a href="/{{ page.previous_page_number }}/">Previous Page</a>
        {% endif %}
        {% if page.has_next %}
        <a href="/{{ page.next_page_number }}/">Next Page</a>
        {% endif %}
        {% else %}
        <p>No posts matched</p>
        {% endif %}
{% include 'footer.html' %}

With our template in place, the last step is to add an appropriate URLconf. Edit urls.py to look like this:

DjangoBlog/urls.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from django.conf.urls.defaults import patterns, include, url

# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
    # Examples:
    # url(r'^$', 'blog.views.home', name='home'),
    # url(r'^blog/', include('blog.foo.urls')),

    # Uncomment the admin/doc line below to enable admin documentation:
    # url(r'^admin/doc/', include('django.contrib.admindocs.urls')),

    # Uncomment the next line to enable the admin:
    url(r'^admin/', include(admin.site.urls)),

    # Home page
    url(r'^$', 'blogengine.views.getPosts'),
    url(r'^(?P<selected_page>\d+)/?$', 'blogengine.views.getPosts'),

    # Blog posts
    url(r'^\d{4}/\d{1,2}/(?P[-a-zA-Z0-9]+)/?$', 'blogengine.views.getPost'),

    # Categories
    url(r'^categories/(?P<categorySlug>\w+)/?$', 'blogengine.views.getCategory'),
    url(r'^categories/(?P<categorySlug>\w+)/(?P<selected_page>\d+)/?$', 'blogengine.views.getCategory'),

    # Flat pages
    url(r'', include('django.contrib.flatpages.urls')),
)

Now, if you run python manage.py syncdb again, the category system should be up and running.

The next step is to add the facility to handle comments. Again, Django has its own application built in for handling comments, so go into setings.py and enter the following under INSTALLED_APPS:

DjangoBlog/settings.py
1
 'django.contrib.comments',

Then run python manage.py syncdb again to generate the appropriate database tables. You’ll also need to amend urls.py to provide a dedicated URL for comments:

DjangoBlog/urls.py
1
2
# Comments
url(r'^comments/', include('django.contrib.comments.urls')),

Place this before the URLconf for the flat pages.

Comments can be attached to any type of content, but we only want to attach them to blog posts, and they should only be visible in the single post template. But first of all, let’s add a comment count to posts in posts.html and category.html. Replace posts.html with this:

templates/posts.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{% include 'header.html' %}
        {% load comments %}
        {% if posts %}
        {% for post in posts %}
        <h1><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h1>
        {{ post.text }}
        {% get_comment_count for post as comment_count %}
        <h3>Comments: {{ comment_count }}</h3>
        {% endfor %}
        <br />
        {% if page.has_previous %}
        <a href="/{{ page.previous_page_number }}/">Previous Page</a>
        {% endif %}
        {% if page.has_next %}
        <a href="/{{ page.next_page_number }}/">Next Page</a>
        {% endif %}
        {% else %}
        <p>No posts matched</p>
        {% endif %}
{% include 'footer.html' %}

And replace category.html with this:

templates/category.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{% include 'header.html' %}
        {% load comments %}
        <h1>Posts for {{ category.title }}</h1>
        {% if posts %}
        {% for post in posts %}
        <h1><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h1>
        {{ post.text }}
        {% get_comment_count for post as comment_count %}
        <h3>Comments: {{ comment_count }}</h3>
        {% endfor %}
        <br />
        {% if page.has_previous %}
        <a href="/{{ page.previous_page_number }}/">Previous Page</a>
        {% endif %}
        {% if page.has_next %}
        <a href="/{{ page.next_page_number }}/">Next Page</a>
        {% endif %}
        {% else %}
        <p>No posts matched</p>
        {% endif %}
{% include 'footer.html' %}

The only significant changes here are that at the top we load comments, and underneath the post text we get the comment count for each post as the variable comment_count, then we display it underneath.

Now, we want to go further with our single post template. As well as a comment count, we want to add the actual comments themselves. Finally, we need a form for adding comments - in theory you can use the admin interface for doing this, but it’s very unlikely you’d want to do so. Open up single.html and edit it to look like this:

templates/single.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{% include 'header.html' %}
        {% load comments %}
        {% for post in posts %}
        <h1><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h1>
        <h3>{{ post.pub_date }}</h3>
        {{ post.text }}
        <h3>By {{ post.author.first_name }} {{ post.author.last_name }}</h3>
        <h3>Categories: {% for category in post.categories.all %} {{ category.title }} {% endfor %}</h3>
        {% get_comment_count for post as comment_count %}
        <h3>Comments: {{ comment_count }}</h3>
        <ol>
        {% get_comment_list for post as comments %}
        {% for comment in comments %}
        <li>{{ comment }}</li>
        {% endfor %}
        </ol>
        {% render_comment_form for post %}
        {% endfor %}
        <br />
        {% if page.has_previous %}
        <a href="/{{ page.previous_page_number }}/">Previous Page</a>
        {% endif %}
        {% if page.has_next %}
        <a href="/{{ page.next_page_number }}/">Next Page</a>
        {% endif %}
{% include 'footer.html' %}

This includes the same changes as the other two templates, so we load comments and display the comment count. Afterwards, we get the comment list for this post as comments, and then loop through the comments, showing them in an ordered list. Afterwards, we then use render_comment_form to show the default comment form for this post. If you’d prefer to create your own comment form, you can use get_comment_form instead to get a form object you can use in the template.

You’ll also need to make some minor changes to the view to get the form working. Save single.html and open blogengine/views.py and add the following line of code to your import statements:

blogengine/views.py
1
from django.template import RequestContext

Then, amend the final line of the getPost function as follows:

blogengine/views.py
1
return render_to_response('single.html', { 'posts':post}, context_instance=RequestContext(request))

The reason this needs to be changed is that the comment form includes the {% csrf_token %} tag, which requires information from the request object, and in order to do so rather than the default context, you need to pass through a RequestContext object instead, but don’t worry too much about the details.

If you now ensure the development server is running and visit a blog post, you should now see that you can post comments. If you want to enhance this very basic comment form, take a look at the excellent documentation on the Django website. Alternatively, there are a number of third-party comment services, such as Disqus and IntenseDebate that can handle comments for you and just require you to paste a snippet of code into whatever template you want to enable comments on, and these may be more convenient.

Finally for this lesson, as promised, we’ll implement our RSS feed. Again, there’s an application bundled with Django that will do this - the syndication framework. Open settings.py and paste the following line in at the bottom of your INSTALLED_APPS:

DjangoBlog/settings.py
1
 'django.contrib.syndication',

Save the file and run python manage.py syncdb to add the appropriate tables to your database. Then, we need to add a URLconf for the RSS feed. We’ll allow a consistent naming scheme for RSS feeds, so this will be /feeds/posts, and if you wanted to you could add /feeds/comments, for instance. Add this to you urls.py, before the url for flat pages:

DjangoBlog/urls.py
1
2
# RSS feeds
url(r'^feeds/posts/$', PostsFeed()),

We’ll also need to tell urls.py where to find PostsFeed(). In this case, we’re going to put it in the view, so add this import line near the top:

DjangoBlog/urls.py
1
from blogengine.views import PostsFeed

Now open blogengine/views.py and add the following line to the import statements at the top:

blogengine/views.py
1
from django.contrib.syndication.views import Feed

Then add the following class declaration to the bottom:

blogengine/views.py
1
2
3
4
5
6
7
8
9
10
11
12
13
class PostsFeed(Feed):
    title = "My Django Blog posts"
    link = "feeds/posts/"
    description = "Posts from My Django Blog"

    def items(self):
        return Post.objects.order_by('-pub_date')[:5]

    def item_title(self, item):
        return item.title

    def item_description(self, item):
        return item.text

This is pretty simple. We import the Feed class from thew views provided by the syndication framework, then we base PostsFeed on Feed. We set the title, the link for the feed, and a description for the feed. Then we get the last 5 Post objects in reverse chronological order, and we define each item’s title as the post title. and each item’s description as the text of the post. From here’ it’s pretty easy to see how you could create feeds based on comments, or pretty much any other object that might exist in the database.

And with that done, our blogging engine is pretty-much feature-complete. We have blog posts with comments, categories, an RSS feed, and flat pages, but the look and feel of the site definitely needs some attention. Next time, we’ll make our blogging engine look a little nicer. Once again, the code is available on GitHub in case you find that more convenient.

Yet Another Tutorial for Building a Blog Using Python and Django - Part 3

Welcome back! In this installment, we’ll make some changes to our URL structure for blog posts, we’ll add support for multiple authors and static pages, and we’ll add some more templates.

First of all, our URL structure. The existing structure works fine, but it would be better if we included a representation of the date of publication. If you’re familiar with WordPress, you’ll know it offers several different URL forms, one of which is the post name alone as we’re using here, and another of which is the year, month and name. We’ll use the latter of these URL schemes with our blogging engine.

This seems like a good opportunity to introduce the interactive Python shell that comes with Django. Make sure you have a few dummy posts set up, then in the project directory (DjangoBlog/, not the top-level one but the one inside that), enter the following command:

1
python manage.py shell

This will start up an interactive Python shell which you can use to interact with your Post objects. Now, the first step is to import your Post model:

1
>>> from blogengine.models import Post

We now have access to our Post objects – let’s take a look at them:

1
2
>>> Post.objects.all()
[<Post: My first blog post>, <Post: My second blog post>, <Post: My third post>, <Post: My fourth post>, <Post: My fifth post>, <Post: My sixth post>]

You may have completely different post objects, or a different number of them, but that’s fine. Remember we set __unicode__(self) to return self.title? Here we see that each blog post is represented by its title. Now let’s get one of our Post objects:

1
2
3
>>> p = Post.objects.get(pk=1)
>>> p
<Post: My first blog post>

In the first line above, we get the Post object with the primary key of 1, and store a reference to it as p. We then demonstrate that it is, indeed, one of our blog posts by outputting its title.

If you’re not familiar with relational database theory, a primary key is a value in a database table that refers uniquely to one entry in the table, so that if you refer to an entry by its primary key, you can be sure you’re getting the correct value. By default, Django models generate a field called id in addition to the ones you define, which is set as the primary key, and this is set to auto-increment, so for instance, every time you add an additional blog post, it gets the next number as its id. Here, we just want to get access to a single blog post object, so we just enter 1 as the primary key in order to get the earliest blog post.

Next, we get the publication date:

1
2
>>> p.pub_date
datetime.datetime(2012, 3, 19, 12, 11, 10)

This returns a datetime.datetime object. If you look at the documentation for Python’s datetime module, you’ll notice that it has attributes called day, month and year. Here’s how we can use these to get the information we want:

1
2
3
4
5
6
7
8
>>> p.pub_date.month
3
>>> p.pub_date.day
19
>>> p.pub_date.month
3
>>> p.pub_date.year
2012

It’s that simple – we just refer to the attribute we want to retrieve. So, it should now be pretty easy to understand how we can get the date for each blog post.

Exit your Python shell with Ctrl-D and head back into the blogengine/ folder. Then open models.py in your text editor and add the following method to the bottom of your Post class:

blogengine/models.py
1
2
def get_absolute_url(self):
    return "/%s/%s/%s/" % (self.pub_date.year, self.pub_date.month, self.slug)

Now, you haven’t seen get_absolute_url before. Every time you create a model in Django, you should really create a get_absolute_url method for it. In essence, it defines a single, canonical URL for that object, whether it’s a blog post, a user, or what have you. By creating one method that defines the structure for the URL for this type of object and referring to it elsewhere, we only need to change it in one place if we want to make any changes to how we determine the URL for that type of object.

What we do here is we define the URL as being /year/month/slug/. If you want, you can quite easily make it include the day as well like this:

blogengine/models.py
1
2
def get_absolute_url(self):
    return "/%s/%s/%s/%s/" % (self.pub_date.year, self.pub_date.month, self.pub_date.day, self.slug)

With our model updated, let’s change our URLconf accordingly. Return to the inner DjangoBlog/ directory and open up urls.py, then amend the lines for the blog posts as follows:

DjangoBlog/urls.py
1
2
# Blog posts
url(r'^\d{4}/\d{1,2}/(?P<postSlug>[-a-zA-Z0-9]+)/?$', 'blogengine.views.getPost'),

What we’ve changed here is that we’ve told urls.py to expect blog posts that look like 4 digits, then a forward slash, then one or two digits, then another forward slash, then a slug that can include hyphens, upper or lower case letters, and numbers.

With that done, we just need to update our template. Edit templates/posts.html to look like this:

templates/posts.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<html>
    <head>
        <title>My Django Blog</title>
    </head>
    <body>
        {% for post in posts %}
        <h1><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h1>
        <h3>{{ post.pub_date }}</h3>
        {{ post.text }}
        {% endfor %}
        <br />
        {% if page.has_previous %}
        <a href="/{{ page.previous_page_number }}/">Previous Page</a>
        {% endif %}
        {% if page.has_next %}
        <a href="/{{ page.next_page_number }}/">Next Page</a>
        {% endif %}
    </body>
</html>

Literally all we do is replace post.slug with post.get_absolute_url and remove the leading forward slash. If you then run python manage.py syncdb, restart the development server and go clicking around your posts, you should be able to see that our new URL system is now up and running.

With that done, the next step is to add support for multiple authors. Now, you might think that we’re going to have to create a new model for users, but that’s not so – Django ships with a number of useful models already, and we’re going to use one of them here.

Now, first of all, we need to amend our Post model to include the author’s details. Edit your blogengine/models.py to look like this:

blogengine/models.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from django.db import models
from django.contrib.auth.models import User

# Create your models here.
class Post(models.Model):
    title = models.CharField(max_length=200)
    pub_date = models.DateTimeField()
    text = models.TextField()
    slug = models.SlugField(max_length=40, unique=True)
    author = models.ForeignKey(User)

    def __unicode__(self):
        return self.title

    def get_absolute_url(self):
        return "%s/%s/%s/" % (self.pub_date.year, self.pub_date.month, self.slug)

There are two significant changes here. First, we import User from django.contrib.auth.models. User is a model provided by the auth model, and we’re going to use it here to represent the author of a given post. Then in the class definition of Post, we add a new field called author.

Note here that author is a foreign key field, and is passed a User object. Again for those unfamiliar with relational databases, a foreign key is a field in a database table that is also a primary key in another database table. Here we’re declaring that the author is one of the entries in the User table.

As well as this, we need to make some changes to the admin interface. By default, when we make a field in a model a foreign key, the admin interface will show a dropdown list of all of the instances of that object (so here, it would be a list of all the users on the system). But we don’t want that. We want the author to automatically be set as the current user, and for there to be no way to override this.

Open up admin.py and change it to look like this:

blogengine/admin.py
1
2
3
4
5
6
7
8
9
10
11
12
13
import models
from django.contrib import admin
from django.contrib.auth.models import User

class PostAdmin(admin.ModelAdmin):
    prepopulated_fields = {"slug": ("title",)}
    exclude = ('author',)

    def save_model(self, request, obj, form, change):
        obj.author = request.user
        obj.save()

admin.site.register(models.Post, PostAdmin)

The changes made here are simple. We import the User model, and in the PostAdmin class definition we exclude the author field – this means that we don’t show this field at all.

Note the addition of the save_model method. In Django it’s easy to create a new object using your models:

1
2
3
4
5
6
7
>>> from blogengine.models import Post
>>> p = Post()
>>> p
<Post: >
>>> p.title="My new blog post"
>>> p
<Post: My new blog post>

However, the new object won’t actually be stored in the database properly until you call the save() method. Here, you would need to enter p.save() (Note this won’t actually work unless you enter all the fields manually). What we’re doing in admin.py is overriding the default save() method to set the author to the name of the user who wrote the post.

Now run python manage.py syncdb again. Note that as you’ve changed the Post model, your existing posts will be lacking the required author field, and so you may need to add these again manually – this will be a numeric ID mapping to a user id. If you only have one user set up, you should just be able to set this to 1 by using an UPDATE SQL query.

If you now make sure the development server is running and log into the administrative interface, the first page you see should have a section marked “Auth”, with two items underneath named Groups and Users.

Now, cast your mind back to when you activated the admin interface and synced the database. If you recall, at this time you were asked to create a superuser account in order to log into the admin interface. This was actually provided by the django.contrib.auth application, one of the applications that are shipped with Django and are active by default. This contains the User and Group models.

If you’re familiar with Linux or Unix, the Auth application will feel very familiar. The account you created at the start was a superuser account, much like the root account on a Unix system, with unlimited privileges. Other users can be created, and given permissions on an individual basis. You can also create groups and add users to those groups, and then set the privileges for those groups en masse. For instance, in a large collaborative blog with many authors, you may have one group for people who contribute articles who can create new posts, editors who can edit existing posts and so on. Similarly, if you had a working comments system, you could easily set up a moderators group who can delete comments, and add people to that group.

Let’s create another user account so we have more than one. From the main admin page, click on the link for Add next to Users. You’ll be taken to a screen that prompts you for a username and password for the new user. Fill these in (there are two password fields for confirmation purposes) as you wish – here I’m setting the new user as bob. On the next screen you can add some additional details for the new user account, such as first name, last name and email address – do this so you have some information to work with.

Lower down you’ll see a dialogue for entering the permissions. You can make the new user a superuser so that they have permission to do anything, you can say whether or not they are staff (they need to be staff to use the admin interface, so check that), and whether they are active (making it easy to deactivate a user account without the need to delete it). Below you’ll see another dialogue showing the available permissions and allowing you to allocate them to that user. Further down, you’ll see a dialogue for changing the start date and last login date for the user, and finally a dialogue for adding new groups and adding the user to existing groups.

Save the user details once you’re done, then go into your superuser account and add a first and last name so we have some data to work with for that as well. Note that just as with a root account on a Unix box, it’s not a great idea to use a superuser account for everyday work (you should create an account that has the minimum privileges you need and use that), but we’ll stick with it for now just for learning purposes – don’t forget if you should roll out a public facing Django-powered site in future, though!

With that done, we now have some data to work with to identify the author of a given post. Let’s fire up the interactive shell again with python manage.py shell:

1
2
3
4
5
6
7
8
9
10
11
12
>>> from blogengine.models import Post
>>> Post.objects.all()
[<Post: My first post>]
>>> p = Post.objects.get()
>>> p
<Post: My first post>
>>> p.author
<User: root>
>>> p.author.first_name
u'Matthew'
>>> p.author.last_name
u'Daly

Here, we can see that it’s easy to get the author’s details from the post. We define p as a reference to the single Post object,then we get the author, which in this case is called root. As I’ve defined a first and last name, we can get those too with p.author.first_name and p.author.last_name, which are strings containing the first and last name respectively. Note the ‘u’ before the string – this just indicates that the string is Unicode.

So from here, it’s pretty easy to display the author’s name in each post. Go into templates/posts.html and add the following line where you want your author details to appear:

templates/posts.html
1
    <h3>By {{ post.author.first_name }} {{ post.author.last_name }}</h3>

Now, as long as you’ve added a first name and last name to that author’s details, if you visit http://127.0.0.1:8000, you should see the appropriate details.

Our next step is to add the facility to create flat pages, somewhat like the Pages functionality in WordPress. Again, Django comes with an application that will handle this, called flatpages, but it’s not enabled by default. Go into DjangoBlog/settings.py and at the bottom of INSTALLED_APPS, add the following:

DjangoBlog/settings.py
1
'django.contrib.flatpages',

Then run python manage.py syncdb again to add the appropriate tables to your database. Now, we need to add a flat page. Go back to the main page of the admin interface, and you should see that you now have the facility to add flat pages. Click on the Add link for flat pages, and give your page a URL, a title, and some text (here I’m giving it a URL of /about/ and a title of About), add it to a site at the bottom (this will say example.com, but don’t worry about that, it’s to do with the Sites application, which we’re not looking at right now) then save it. You should now have a FlatPage object available.

Let’s take a look at the tables created for flatpages using the sqlall command:

1
python manage.py sqlall flatpages
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
BEGIN;
CREATE TABLE "django_flatpage_sites" (
    "id" integer NOT NULL PRIMARY KEY,
    "flatpage_id" integer NOT NULL,
    "site_id" integer NOT NULL REFERENCES "django_site" ("id"),
    UNIQUE ("flatpage_id", "site_id")
)
;
CREATE TABLE "django_flatpage" (
    "id" integer NOT NULL PRIMARY KEY,
    "url" varchar(100) NOT NULL,
    "title" varchar(200) NOT NULL,
    "content" text NOT NULL,
    "enable_comments" bool NOT NULL,
    "template_name" varchar(70) NOT NULL,
    "registration_required" bool NOT NULL
)
;
CREATE INDEX "django_flatpage_a4b49ab" ON "django_flatpage" ("url");
COMMIT;

So we can see that django_flatpage, which contains the details about the actual flat pages, has the fields id, url, title, content, enable_comments, template_name, and registration_required. Some of these options are under the advanced options in the flat page interface, so you may have missed them. Now fire up python manage.py shell again:

1
2
3
4
>>> from django.contrib.flatpages.models import FlatPage
>>> f = FlatPage.objects.get()
>>> f
<FlatPage: /about/ -- About>

Here we have one FlatPage object only (note that get() should only be used if you will only get one result back), which is represented by a string that includes the URL and title.

1
2
>>> f.content
u'This is my about page.'

We can easily access any of the fields in the flat page. Now, we need to define some URLs for our flat pages. Exit the Python shell and open urls.py, then insert the following rule underneath the one for blog posts:

DjangoBlog/urls.py
1
2
# Flat pages
url(r'', include('django.contrib.flatpages.urls')),

Note that this must be the last rule in your urls.py, because it will match anything. Now, you can try and load /about/, or whatever page you’ve created, but you’ll get an error stating that the template does not exist, so we need to create that. Go into your template directory, and create a directory inside that called flatpages. Then create a new file in there called default.html, and add the following code to it:

templates/flatpages/default.html
1
2
3
4
5
6
7
8
9
<html>
    <head>
        <title>My Django Blog</title>
    </head>
    <body>
        <h1>{{ flatpage.title }}</h1>
        {{ flatpage.content }}
    </body>
</html>

Now, make sure you have the development server running and try to load http://127.0.0.1:8000/about/, or whatever your flat page URL is, and you should see your flat page’s title and content.

One final task for this lesson – we’re going to refactor our templates a little so that as little code as possible is duplicated and if we want to change anything we need to only do so in one place. Go to your template directory and edit posts.html to look like this:

templates/posts.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{% include 'header.html' %}
        {% for post in posts %}
        <h1><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h1>
        <h3>{{ post.pub_date }}</h3>
        {{ post.text }}
        <h3>By {{ post.author.first_name }} {{ post.author.last_name }}</h3>
        {% endfor %}
        <br />
        {% if page.has_previous %}
        <a href="/{{ page.previous_page_number }}/">Previous Page</a>
        {% endif %}
        {% if page.has_next %}
        <a href="/{{ page.next_page_number }}/">Next Page</a>
        {% endif %}
{% include 'footer.html' %}

Here we’re taking the header and footer of the page out and replacing them with code that includes another file there instead. Next, we need to create those files in the same directory. Here’s header.html:

templates/header.html
1
2
3
4
5
<html>
    <head>
        <title>My Django Blog</title>
    </head>
    <body>

And here’s footer.html:

templates/footer.html
1
2
</body>
</html>

None of this is terribly complex – we’re just moving the code into another file so that other templates can use the same files. Now save a copy of posts.html as single.html – we’re going to create a template for a single blog post. Edit the original posts.html to look like this:

templates/posts.html
1
2
3
4
5
6
7
8
9
10
11
12
13
{% include 'header.html' %}
        {% for post in posts %}
        <h1><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h1>
        {{ post.text }}
        {% endfor %}
        <br />
        {% if page.has_previous %}
        <a href="/{{ page.previous_page_number }}/">Previous Page</a>
        {% endif %}
        {% if page.has_next %}
        <a href="/{{ page.next_page_number }}/">Next Page</a>
        {% endif %}
{% include 'footer.html' %}

We’re just removing the date and author details from the template that shows multiple posts. Our existing single.html file can remain as it is for now, since that still has all the additional information we want to include in an individual post.

While we’re here, let’s update our flat pages to use the same header and footer. Go into the flatpages directory and change default.html to look like this:

templates/flatpages/default.html
1
2
3
4
{% include 'header.html' %}
        <h1>{{ flatpage.title }}</h1>
        {{ flatpage.content }}
{% include 'footer.html' %} 

Note that the path to the template files is not relative to flatpages/default.html, but relative to the root of the template directory.

The last thing to do is to amend the view for our blog to use the correct templates. Go into blogengine/views.py and change the getPost (NOT getPosts) function to pass the single.html template to render_to_response, instead of the posts.html template:

blogengine/views.py
1
2
# Display specified post
return render_to_response('single.html', { 'posts':post})

You should now notice that the single posts and multiple posts are using different templates.

Hope you’ve enjoyed this lesson, and I’ll do another one as soon as I can. The code is available on GitHub if you prefer to get it that way.

Yet Another Tutorial for Building a Blog Using Python and Django – Part 2

In the first part of this tutorial, we got the core elements of our blogging application working - we set up our model for posts, and a view, template and URL configuration to view the index. Next we’ll start extending this very basic functionality - we’ll add a view for individual posts as well, and we’ll allow for each post to have a separate URL.

First, we need to set up some pagination for the home page. At this point, it’s worth taking the time to look at how we want our URL to look. Here, we’ll work on the basis that by default, the home page will show the first five blog posts, and if someone wants to see later posts, they need to append a number to the end. Here’s the URL for the second page assuming it’s at example.com:

1
http://www.example.com/2/

So, we need two separate rules for the URLs. We need one for a URL with no number at the end, and one for a URL with a number at the end, and an optional forward slash. Open up urls.py and edit it so the Home page section looks like this:

DjangoBlog/urls.py
1
2
3
# Home page
url(r'^$', 'blogengine.views.getPosts'),
url(r'^(?P<selected_page>\d+)/?$', 'blogengine.views.getPosts'),

Note that I’ve edited the first rule to include ^$ as the regular expression. ^ denotes the start of a regex, and $ denotes the end, so this represents a URL with nothing added after the domain name, such as http://www.example.com. We’ve also changed getRecentPosts to getPosts, as that’s now a more descriptive name.

The second line will match if there is a digit (denoted by the \d+ section) and will pass that digit through to the getPosts function as selected_page. With that done, we now need to make the necessary changes in the view, so move into the blogengine directory and amend views.py to look like this:

blogengine/views.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Create your views here.
from django.shortcuts import render_to_response
from django.core.paginator import Paginator
from blogengine.models import Post

def getPosts(request, selected_page=1):
    # Get all blog posts
    posts = Post.objects.all().order_by('-pub_date')

    # Add pagination
    pages = Paginator(posts, 5)
    returned_page = pages.page(selected_page)

    # Display all the posts
    return render_to_response('posts.html', {'posts':returned_page.object_list})

Again, note the change in function name from getRecentPosts to getPosts. Now, let’s work through the rest of the code. You’ll notice the following line near the top:

blogengine/views.py
1
from django.core.paginator import Paginator

This imports the Paginator class, which is very useful for creating pagination. Then, you’ll notice the following line:

blogengine/views.py
1
def getPosts(request, selected_page=1):

If you know much about Python, you’ll know that you can specify a default value for a parameter passed to a function or method. Here, what we’re doing is setting the default value of selected_page to 1, so if someone visits http://www.example.com, for which the URLconf doesn’t specify a number, this defaults to 1. If they visit http://www.example.com/2 instead, the default value for selected_page will be overriden to 2.

Then you’ll note that we’ve refactored the lines that fetched the posts and sorted them into one line, and called that posts. After that we define pages as a Paginator object, and passed it the values of posts and 5. The first parameter is what we want to divide between pages, and the second is how many instances of this we should allow on an individual page. Here we’re passing through all of the posts, and allowing 5 posts per page. We then define returned_page as the page from pages that matches the number submitted in the selected_page variable. Finally we pass a list of all the objects that make up returned_page through to the template as posts.

So, we now have basic pagination in place. Next, we’ll add the capability to display individual posts.

Now, we could just be lazy and have each post referred to by the numerical ID that’s automatically added by Django to the database, but why would we want to do that? We want a nice, human and search engine friendly URL that gives some idea what the blog post is about. Django is structured in such a way that nice, friendly URLs without cruft are very easy to create, and it actually has a special type of field in the models called a slug field that’s ideal for creating URLs.

So first of all, go into blogengine/models.py and edit it to look like this:

blogengine/models.py
1
2
3
4
5
6
7
8
9
10
11
from django.db import models

# Create your models here.
class Post(models.Model):
    title = models.CharField(max_length=200)
    pub_date = models.DateTimeField()
    text = models.TextField()
    slug = models.SlugField(max_length=40, unique=True)

    def __unicode__(self):
        return self.title

The only change is the addition of the slug field. Like any other field, you’ll be able to edit the slug field using the admin interface. But, why should you have to? Existing blogging solutions like WordPress will suggest a URL for a blog post, so that’s what we want to do as well. Open blogengine/admin.py and edit it to look like this:

blogengine/admin.py
1
2
3
4
5
6
7
import models
from django.contrib import admin

class PostAdmin(admin.ModelAdmin):
    prepopulated_fields = {"slug": ("title",)}

admin.site.register(models.Post, PostAdmin)

If you know a little about object-oriented programming in Python, you should be able to grasp what’s going on here. We’re creating PostAdmin, which inherits from ModelAdmin, and using the title to prepopulate the slug field. We then register this as before, but using PostAdmin rather than the default ModelAdmin.

A fairly typical slug will be based on your title, but will strip out whitespace and other characters between the words and replace them with hyphens, and convert the result to lowercase, so a title like “My new bike” will become my-new-bike.

Also, note that in models.py, we pass the parameter unique=True for the slug. This indicates that the slug must be unique, so we can’t have the same URL applied to two different posts.

With our model and admin amended, it’s now time to create a view to deal with displaying an individual post. Add the following function to blogengine/views.py:

blogengine/views.py
1
2
3
4
5
6
def getPost(request, postSlug):
    # Get specified post
    post = Post.objects.filter(slug=postSlug)

    # Display specified post
    return render_to_response('posts.html', { 'posts':post})

This function receives the request object and a slug for the post. It then gets the specific post with that slug, and returns it. For now we’ll just use the existing posts.html template, but we’ll want to add a new template for single posts at some point.

With that done, the next step is to add a URLconf to handle blog posts. Open urls.py and add the following code after the lines for the home page:

DjangoBlog/urls.py
1
2
# Blog posts
url(r'^(?P<postSlug>[-a-zA-Z0-9]+)/?$', 'blogengine.views.getPost'),

So, now we have a dedicated URL for each post. But how do we get there? We need to create a link from the home page to each individual blog post. Open up your posts.html template and edit it to look like this:

templates/posts.html
1
2
3
4
5
6
7
8
9
10
11
12
<html>
    <head>
        <title>My Django Blog</title>
    </head>
    <body>
        {% for post in posts %}
        <h1><a href="/{{ post.slug }}">{{ post.title }}</a></h1>
        <h3>{{ post.pub_date }}</h3>
        {{ post.text }}
        {% endfor %}
    </body>
</html>

Now, if you run python manage.py syncdb, the changes to your database schema will be made automatically. However, if you already have some test posts in the database, these won’t have a slug and that could cause problems. So you can either add slugs to the existing posts manually using an UPDATE SQL command, or if you’re using something like PHPMyAdmin you can use that to add slugs for these posts. Or if they’re just test posts and you don’t care about them in the slightest, just delete your database and start again from scratch.

With that done, if you then run python manage.py runserver, and then visit http://127.0.0.1:8000, you should see your home page. If you have at least one blog post set up, you should see those posts on the home page, and the title should be a hyperlink to that post. If you have more than 5 posts, you should be able to go to http://127.0.0.1:8000/2 and see the next 5 posts.

But wait! What if you don’t have more posts? You want some code in place to handle what happens if you try to go to http://127.0.0.1:8000/2 and it isn’t there. You also want to dynamically generate links for older and newer posts so that users can click back as far as they need to.

First of all, let’s put something in place to catch nonexistent pages. Open blogengine/views.py and edit the getPosts function to look like this:

blogengine/views.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# Create your views here.
from django.shortcuts import render_to_response
from django.core.paginator import Paginator, EmptyPage
from blogengine.models import Post

def getPosts(request, selected_page=1):
    # Get all blog posts
    posts = Post.objects.all().order_by('-pub_date')

    # Add pagination
    pages = Paginator(posts, 5)

    # Get the specified page
    try:
        returned_page = pages.page(selected_page)
    except EmptyPage:
        returned_page = pages.page(pages.num_pages)

    # Display all the posts
    return render_to_response('posts.html', { 'posts':returned_page.object_list})

def getPost(request, postSlug):
    # Get specified post
    post = Post.objects.filter(slug=postSlug)

    # Display specified post
    return render_to_response('posts.html', { 'posts':post})

The only differences here are that EmptyPage is imported, and we add error checking to returned_page so that if it throws an EmptyPage exception (meaning that the given page doesn’t exist), then it defaults to returning the highest numbered page. The value of pages.num_pages is the number of pages in total, so you use this to get the last numbered page. If you prefer, you can change it to default to the first page by replacing pages.num_pages with 1.

With this done, the next step is to create links for the next and previous pages. Fortunately Django makes this really easy. First, you have to pass through the returned_page object in views.py, like this:

blogengine/views.py
1
2
# Display all the posts
return render_to_response('posts.html', { 'posts':returned_page.object_list, 'page':returned_page})

Here in addition to the existing posts object, we now pass through returned_page as page. Now, amend your posts.html template as follows:

templates/posts.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<html>
    <head>
        <title>My Django Blog</title>
    </head>
    <body>
        {% for post in posts %}
        <h1><a href="/{{ post.slug }}">{{ post.title }}</a></h1>
        <h3>{{ post.pub_date }}</h3>
        {{ post.text }}
        {% endfor %}
        <br />
        {% if page.has_previous %}
        <a href="/{{ page.previous_page_number }}/">Previous Page</a>
        {% endif %}
        {% if page.has_next %}
        <a href="/{{ page.next_page_number }}/">Next Page</a>
        {% endif %}
    </body>
</html>

Here, if the given page has a previous page, we display a link to it, and if it has a next page, we display a link to that too. page.has_previous and page.has_next return True or False, and page.previous_page_number and page.next_page_number display a number for the appropriate page, so it’s easy to use them to link to the appropriate page.

And that will do for now! We’ve gotten quite a lot done this time, and we actually have something that, although it’s still missing many of the more sophisticated features of blogging platforms such as WordPress, is fundamentally usable as a blog as long as you either don’t want comment functionality or are prepared to use a third-party system such as Disqus. Feel free to congratulate yourself with a beverage of your choice, and we’ll carry on later.