Django 1.5 in a nutshell I

In 2011, I wrote a quick tutorial about Django 1.3. I was going to demonstrate how to write a blog in just one hour in a live code session. This time I’m in a mentoring program teaching Python and Django to a colleague so I decided to update that tutorial. Django is now in version 1.5 and you can find what is new in the release notes of the framework. Let’s code!

0. Prerequisites

I’m assuming we are working on Linux with Python 2.7, database sqlite3 and the pip software for managing Python package. First step is to install Django and south from pip by typing in a terminal as administrator:

# pip install django

Or, if you have Django already installed…

# pip install --upgrade django

Then south:

# pip install south

Check the version of Django in a Python console writing:

import south
import django
django.get_version()

1. Starting a Django project

Now, the first step will be to create the project with django-admin.py utility (sometime it is available without the .py extension) so please, type in a console (not as admin, just your regular user):

$ django-admin.py startproject djangoblog

The command creates the folder djangoblog as a container for your Django project:

djangoblog/
├── djangoblog
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── manage.py

One of the main differences from Django 1.4 and Django 1.3 is the change in project structure. The outer folder is irrelevant, you can rename it whenever you want. It is only a convenient place to put your custom applications. A project is nothing more than a set of Django applications working together. Normally the main application of your site is not very reusable but the rest of them could be installed separately and reused for other projects.

Starting a Django project is as easy as this. You can start a test server typing:

$ cd djangoblog
$ chmod +x manage.py
$ ./manage.py runserver

Check the output to navigate to the URL with the test server (normally http://127.0.0.1:8000/) Stop the server by pressing Ctrl+C

But let’s edit the settings.py for our needs. We first apply some useful tricks I learned along time to make our applications more flexible and improving re-usability.

Enter folder djangoblog, choose your favorite code editor and open settings.py,  add these lines at the beginning:

import os
PROJECT_PATH = os.path.dirname(__file__)

This variable is useful to allow relative paths in our settings file.

Now let’s configure DATABASES variable to use SQLite3. Look for DATABASES variable and make it look like:

DATABASES = {
  'default': {
    'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
    'NAME': os.path.join(PROJECT_PATH, 'db/database.db'), # Or path to database file if using sqlite3.
    # The following settings are not used with sqlite3:
    'USER': '',
    'PASSWORD': '',
    'HOST': '', # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP.
    'PORT': '', # Set to empty string for default.
  }
}

We should modify following variables as well:

TIME_ZONE = 'Europe/Madrid'

LANGUAGE_CODE = 'en-eu'

STATIC_ROOT = os.path.join(PROJECT_PATH, 'static_files')

INSTALLED_APPS = (
  'django.contrib.auth',
  'django.contrib.contenttypes',
  'django.contrib.sessions',
  'django.contrib.sites',
  'django.contrib.messages',
  'django.contrib.staticfiles',
  # Uncomment the next line to enable the admin:
  'django.contrib.admin',
  # Uncomment the next line to enable admin documentation:
  'django.contrib.admindocs',
  'djangoblog',
  'south'
)

Note you are adding the own djangoblog application to the list of installed applications. This is because it is convenient to find only-application-related content inside the djangoblog folder. For instance, customized templates or static files so by declaring djangoblog as an installed application we are letting Django know there is relevant content inside djangoblog application.

Now, inside djangoblog folder, create db, static and templates sub directories:

$ mkdir db static templates

To automatically enable administration portal lets touch urls.py file and remove the commentaries in the lines related with the admin tool:

from django.conf.urls 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'^$', 'djangoblog.views.home', name='home'),
  # url(r'^djangoblog/', include('djangoblog.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)),
)

2. Synchronizing the database

Any Django project contains some default installed applications. Sometimes applications need their custom databases. We are going to create these databases. Do:

$ ./manage.py syncdb

Answer yes to the first questions and enter the admin username, e-mail and password to end configuring your Django site.

Do you remember we have installed an application called south and add it to the installed applications of our project? This is a schema evolution tool to assist us, developers, to evolve our databases. Using south we are not able to sync applications inside the project folder in a normal way. We need to aware south we are creating, deleting or changing the application model but this management is not very difficult and we will see it soon.

3. Testing the project

Just start the test server in the same way we did before:

$ ./manage.py runserver

If you want to customize where the project is started, use:

$ ./manage.py runserver IP:PORT

If you want to expose the server through your public IP, use:

$ ./manage.py runserver 0.0.0.0:PORT

The server shows a 404 error because there is no view associated to the default address but you can access /admin/ URL (normally http://127.0.0.1:8000/admin/) and enter username and password you provided in the previous step to get access to the administrator portal. Get used to it by modifying your user and filling name and surname.

4. Adding a new application

It’s time to create our first application. We will use it to manage blog posts and it will be named (in a rapture of originality and ingenious) posts. So we go to the container folder and type:

$ ./manage.py startapp posts

The commando will create a folder called posts with three files:

  • models.py will keep the application data model
  • tests.py will keep tests to verify application correctness
  • views.py will keep application’s views

So the complete directory structure looks like:

djangoblog/
├── djangoblog
│   ├── db
│   │   └── database.db
│   ├── __init__.py
│   ├── settings.py
│   ├── static
│   ├── static_files
│   ├── templates
│   ├── urls.py
│   └── wsgi.py
├── manage.py
└── posts
 ├── __init__.py
 ├── models.py
 ├── tests.py
 └── views.py

Now we must let know our project that application posts exist and is available for use. Modify settings.py to add posts to the list of installed applications before south:

INSTALLED_APPS = (
  'django.contrib.auth',
  'django.contrib.contenttypes',
  'django.contrib.sessions',
  'django.contrib.sites',
  'django.contrib.messages',
  'django.contrib.staticfiles',
  # Uncomment the next line to enable the admin:
  'django.contrib.admin',
  # Uncomment the next line to enable admin documentation:
  'django.contrib.admindocs',
  'djangoblog',
  'posts',
  'south'
)

¡It is very important south is always the last installed application!

5. The posts application data model

One of the distinctive characteristics of Django is that you first model the solution and write it into models.py file and then lets the Django ORM to map it to a database automatically. We are going to model a blog post as follows, edit posts/models.py and write:

# -*- encoding: utf-8 -*-
from django.db import models
from django.contrib import auth

class Post(models.Model):

  title = models.CharField(max_length=255)

  machine_name = models.SlugField(max_length=255, primary_key=True)

  content = models.TextField(blank=True)

  publication_date = models.DateTimeField(auto_now_add=True)

  def __unicode__(self):
    return self.title

  def excerpt(self):
    return self.content[:300] + u'…'

  class Meta:
    ordering = [u'-publication_date']

A model is no more than a regular class inheriting from models.Model base class able to be serialized by the Django ORM. Post class defines a title as a field of 255 characters, a primary key machine_name with the tipical_slug_format where only american alphanumeric characters, underscores and hyphens are allowed; the content of the post will be in the content field that is a text field which accepts the empty string as a valid value and the publication_date, with a default value being the moment when a new post is saved.

Furthermore we provide the excerpt() method and the __unicode__() magic method. The excerpt() method returns the first 300 characters of the post followed by the ellipsis character. Note the ellipsis symbol is only one character. As we are using unicode characters in the source file we need to specify the encode of the file with (see line 1):

# -*- encoding: utf-8 -*-

All strings in Django are unicode strings so my advice is to force all your strings to be unicode as well. To do this, prefix your string literal with a u as in the example:

u'An unicode string'

Internal class Meta allows the developer to set some metadata related with the model. In this case we establish the default order to be by publication date from earliest to latest (descending order).

6. Synchronizing database: the first snapshot

Now we have a new application declared, all that remains to get it working is to map models to the database. Without south application, this can be achieved by issuing the following command (don’t do it, you have south installed):

$ ./manage.py posts syncdb

But fortunately south is installed. South acts like a sort of version control software for DB schemes registering changes (snapshots) in the models, then applying them. To take the initial snapshot, type:

$ ./manage.py schemamigration posts --initial

The command for later changes is the same without the –initial parameter. Now you have the snapshot for the current state of posts application, apply it by writting:

$ ./manage.py migrate posts

7. Adding the post model to the administrator site

Before creating new posts, we need register Posts model into the administrator site. To do this, create an admin.py file inside posts folder and add these lines:

from posts.models import Post
from django.contrib import admin

admin.site.register(Post)

8. Creating posts

Now you have registered Posts model, an automatic panel to manage Posts instances is automatically added to the admin site. Try it by running the server and going to /admin/ URL:

$ ./manage.py runserver

You can create a new post. Don’t forget machine name and observe how Django transform class members’ names into humanized names for the fields in the form replacing underscores by spaces, lowering model names and adding an ‘s’ when used in plural. You can make errors (i.e. adding spaces o special characters to the machine name field) to see how validation works. Admin application from Django is really powerfull and we will return to it in a while. Lets create 4 – 6 moderate length posts.

9. Django views

In Django, views and templates conform the basis for user / application interaction. The procedure is as follows:

  1. The Django applications receive an request for an URL.
  2. The URL is compared with a pattern inside urls.py which determines which view will be used.
  3. The view processes the request, generates a dictionary called context and pass it to some template.
  4. The template is rendered by the Django template interpreter and filled with values in the context dictionary if proceeds.
  5. The rendered template is sent as the http response.

With class-based views introduced in Django 1.3 and the provided framework, to implement views is something extremely easy 🙂

Edit views.py inside the post folder and add the following code to create a list of posts:

# Create your views here.
from django.views.generic import ListView
from posts.models import Post

class PostList(ListView):
    template_name = 'postlist.html'
    model = Post

ListView instances pass a context object called object_list to the template specified in template_name.

Now we need to associate this view with an URL, we will allow two ways of accessing the list of posts:

  • /posts/
  • / (as a shortcut)

Modify urls.py inside djangoblog folder to bind these URL to the former view:

from posts.views import PostList
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'^$', 'djangoblog.views.home', name='home'),
  # url(r'^djangoblog/', include('djangoblog.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), name='adminpage'),

  url(r'^$', PostList.as_view(), name=u'mainpage'),
  url(r'^posts/', PostList.as_view(), name=u'postlist'),
)

Please, pay attention to the import in line 1 and name parameters in lines 19 and 20.

10. Django templates

When designing user interfaces, webpages, some elements in the webpage are fixed. Usually main navigation bar, header and footer are not altered despite the section you are in the site. This introduces the idea of having HTML templates with placeholders for variable content and this is how Django templates language works.

It is important to note Django templates are only text templates, they are not coupled to any technology and they ignore if the render process is producing HTML, JavaScript or whatever.

So, let’s write a template using Django template language on a bootstrap-powered HTML in order to make our blog looking good. Download bootstrap and decompress it in the folder static you create on step 1:

djangoblog/
├── djangoblog
│   ├── db
│   ├── static
│   │   ├── css
│   │   ├── img
│   │   └── js
│   ├── static_files
│   └── templates
└── posts
    └── migrations

Bootstrap is no more than some CSS and JS acting like a reduced but flexible webpage framework. We are writing some HTML 5 using CSS from bootstrap so in djangoblog/templates folder add a new file called postlist.html and paste this content:

{% load staticfiles %}
<!DOCTYPE html>
<html>
  <head>
    <title>My Django blog | {% block title %} Default title {% endblock %}</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- Bootstrap -->
    <link href="{% static "css/bootstrap.min.css" %}" rel="stylesheet" media="screen">
    <style>body { padding-top: 60px; }</style>
  </head>
  <body>
    <div class="navbar navbar-inverse navbar-fixed-top">
      <div class="navbar-inner">
        <div class="container">
          <a class="brand" href="#">My Django blog</a>
        </div>
      </div>
    </div>
    <div class="container">
      {% block content %}
      <h1>Default title</h1>
      <p>Default content</p>
      {% endblock %}
    </div>
    <script src="http://code.jquery.com/jquery.js"></script>
    <script src="{% static "js/bootstrap.min.js" %}"></script>
  </body>
</html>

Template instructions are called tags in Django slang and they have the following form: ‘{% <tag_name> [parameters] %}’. When they are blocks, the starting tag follow the same schema described but the block is ended by ‘{% end<tag_name> %}’. A tag can take parameters separated by commas. Some tags are available by default but other require to load special modules. That is exactly what the tag load does (see line 1). Tag block (lines 5 and 20) is a content placeholder. It takes a parameter, the name of the block, and this can be used in other templates to be referred and replaced by new content. Tag static, part of the module loaded in line 1; it takes a parameter and return the URL for the static content suffixed by the parameter.

Before running the server, you need to collect all the static content and put inside static_files folder you created in step 1. But do not despair, this is done automatically by Django. Just run the following command, answering yes:

$ ./manage.py collectstatic

Now you can run the server as usual. Then go to the root URL (normally http://127.0.0.1:8000/) and check the result. Compare the previous template’s source code with the HTML source in the browser (keyboard shortcut Ctrl+U in Chrome and Firefox).

11. A useful view

We named former view as postlist.html for testing purposes but you probably noticed it is not listing any post. It looks more like the base page with the header and content sections so rename it to base.html:

$ mv djangoblog/templates/postlist.html djangoblog/templates/base.html

Now create a new postlist.html document and add the following content:

{% extends "base.html" %}

{% block title %} Index {% endblock %}

{% block content %}

  <h1>Index</h1>

  {% for post in object_list %}
    <article class="post">
      <header>
        <h2>{{ post.title }}</h2>
      </header>
      <section>
        {{ post.excerpt }}
      </section>
      <aside>
        <p class="label label-info">
          Published on
          <time>{{ post.publication_date|date:"r" }}</time>
        </p>
        <!-- Here will be commentaries -->
      </aside>
    </article>
  {% endfor %}

{% endblock %}

Now we are extending a template because the first line is saying so. Tag extends take as parameter the template to be extended. Extending a template means replacing its blocks by the blocks in the current template. Look at lines 3 and 5 and see how we refer to the same block names as those in file base.html. Now the content is distinct and will replace blocks with the same name on the base template.

Do you remember the view from step 9? It was passing a context object called object_list to the template. You can iterate on lists by using the tag for … in as in line 9. The text inside the block will be executed once per iteration.

Inside Django templates, dictionaries and objects are read-only and they are accessed in a normalized form by using ‘dot.notation’ so getting the title of the post is:

post.title

Furthermore, you can call object’s methods as if they were attributes (see line 15) if they don’t take parameters:

post.excerpt

To print the content of a variable inside the template you must use ‘{{ <variable_name> }}’.

If you want to transform content before printing it, it is possible to pass the content of a variable through a pipe by using pipe notation:

{{ <variable_name>|<filter_name>[:<parameter>] }}

Filters are Django special functions that transform content. In line 20 we use filter date to format the publication date.

So now you can run the server again to see living results. Enjoy!

Conclusions

Now I finish to write the updated tutorial, it seems to me a lot bigger than the original and I realize I need to update the Spanish version as well because there are significant changes from Django 1.3 to Django 1.5. Content increasing is logical, the Spanish version I did was for a living code session meanwhile this update is focused on the mentoring program (say “Hi!” Tom). I’m not restricted to an hour so I extend the explanations trying to be clearer and I added bootstrap which I think is an original point and help people to feel more satisfied with the results.

Stay tuned for the second part of the tutorial!

6 comentarios en “Django 1.5 in a nutshell I

  1. Tom says Hi! 🙂

    The tutorial is very easy to follow, and I think it would be hard to shorten it that much, it would have to be a step by step “do this do that, copy this, paste that” – which in turn would be even less appealing since it wouldn’t be teaching you anything.

    I liked the ration of code snippets to text. I’ve found an error though. The tutorial doesn’t rune since django is searching for templates and statics in the default django folders (inside django/contrib. To make it work I had to add:

    os.path.join(PROJECT_PATH, ‘static’), to STATICFILES_DIRS and
    os.path.join(PROJECT_PATH, ‘templates’), to TEMPLATE_DIRS

    in the settings.py file.

    1. Although, this is possible, what happened here is that probably you forgot to include “djangoblog” as an installed app:

      INSTALLED_APPS = (
      ‘django.contrib.auth’,
      ‘django.contrib.contenttypes’,
      ‘django.contrib.sessions’,
      ‘django.contrib.sites’,
      ‘django.contrib.messages’,
      ‘django.contrib.staticfiles’,
      # Uncomment the next line to enable the admin:
      ‘django.contrib.admin’,
      # Uncomment the next line to enable admin documentation:
      ‘django.contrib.admindocs’,
      ‘djangoblog’, # <– Very important!
      'south'
      )

      This way we can pack the static and templates files with the "main application" with minimum code.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s