Django for Designers: Difference between revisions

From OpenHatch wiki
Content added Content deleted
Line 572: Line 572:
Django automatically looks for a folder named 'static' inside any of your Django apps. You can also put static files in other places, such as inside /mysite/ directly. To do that, you would need to add the absolute path of the folder to STATICFILES_DIRS in settings.py.
Django automatically looks for a folder named 'static' inside any of your Django apps. You can also put static files in other places, such as inside /mysite/ directly. To do that, you would need to add the absolute path of the folder to STATICFILES_DIRS in settings.py.


We'll just leave our static files inside bookmarks/, for the sake of simplicity.
For this tutorial we'll just leave our static files inside bookmarks/, for the sake of simplicity.


TODO: see how the old HTML looks here
TODO: see how the old HTML looks here
Line 664: Line 664:
{% endblock %}
{% endblock %}
</source>
</source>

Now if we reload our site at http://localhost:8000, we can see that the prewritten static files have modified it some, and it looks significantly nicer than it did before!


=== Part 3: Models, our database, and making it visible ===
=== Part 3: Models, our database, and making it visible ===

Revision as of 07:18, 5 March 2013

Introduction

In this tutorial, we will explain topics and provide commands for you to run on your own computer. You will leave with a working social bookmarking web app!

This is a tutorial on web programming, so we will go beyond just Django and discuss third-party Django apps and other real-world web development tools. We'll also be emphasizing areas of Django that particularly affect designers, such as static files, template inheritance, and AJAX.

Things you should know already

  • HTML familiarity
  • Basic Python proficiency
  • Basic or better Javascript proficiency
  • Pre-requisites that we will help with

We expect you to have git, Python, and a few other elements ready on your laptop before the tutorial. We will publish a laptop setup guide that steps you through:

  • Installing Python, git, pip, virtualenv, and a reasonable text editor
  • Setting up your env with Django, South, and django-debug-toolbar
  • Basic command line knowledge (cd, ls, etc)
  • Basic git knowledge
  • Setting up your git repo for the tutorial

Things you do not need to know already:

  • Django :)
  • What an ORM is
  • Anything database related

Laptop setup

See our /Laptop setup guide.

Curriculum

Part 1: Starting our project

This tutorial goes through the process of building a simple social bookmarking application, where users can save various URLs and tag those bookmarks to make them easier to find later. Our bookmarks application will be built on top of Django, a full-featured web framework written in Python.

Activate your env

The first step is to open your terminal window, navigate to the folder where you plan to store your app code (where you set up your virtualenv for this tutorial earlier) and activate your env:

$ source env/bin/activate

You'll know it worked because afterwards, you'll see

(env) $

at the front of all your terminal lines in that terminal window!

Set up your git repo

If you're following this tutorial at PyCon, in your terminal, run:

$ git clone git://github.com/aldeka/pycon-django-tutorial.git

This will download a few files that we'll be using in this tutorial.

Additionally, this sets us up to use a program called git to save our progress and sync up later, in case any of you get stuck. If you're not familiar with git, git is a version control system that tracks changes you make to files within a git repository (the folder you just cloned). You make commits in git, which are like save points in video games -- the commit messages tell you what changes are included in this commit. If later on you make some changes and delete something or mess something up, you can always go back to a previous commit and try again.

Git also has "branches"--parallel timelines where you can go off from the main branch (called "master") and try something out. It's often a good idea to start a new branch when you're writing a new feature. You can make as many commits as you want inside the branch, and it won't affect the main master timeline. You can make as many branches as you want, and branches of branches. You can even share branches with others (though we won't be doing that today). Git's branching system (and its non-centralized architecture generally) make it easy to try out new ideas in code without having to ask permission of everyone else who might be working on the same code as you. Then, when your new feature is done, you can then merge (or rebase--the exact process would take a much longer explanation) the changes back into the master timeline.

We'll be using the master branch (the default name for the main branch of code, which you're in right now) for syncing up throughout the class in case you get lost. You'll need to make your own branches for playing with the code on your computer, so that it doesn't interfere w. re-syncing later.

To make your first branch, first enter the pycon-django-tutorial directory:

$ cd pycon-django-tutorial

Then enter:

$ git branch my-branch-1

Congratulations! You're now in a branch named my-branch-1!

You can see what branch you are in (and what branches are in your repository) at any time by typing:

$ git branch

Start your project

Let's create your first Django project, which we'll call "myproject".

$ django-admin.py startproject myproject

You'll see we made a folder called "myproject", with some files in it. Let's check them out!

$ cd myproject/
$ ls
    manage.py
    myproject
$ ls myproject/
    __init__.py  
    settings.py
    urls.py
    wsgi.py

These files are:

  • manage.py: A command-line utility that lets you interact with this Django project in various ways.
  • myproject/: Django auto-creates an folder within your project with the same name as your project that has a number of useful files in it. (This is a recent change; before Django 1.4, Django just put everything in the project folder instead of siloing it.)
  • myproject/__init__.py: An empty file that tells Python that this directory should be considered a Python module. Because of the __init__.py file, you can use import to import myproject.
  • myproject/settings.py: Settings/configuration for this Django project.
  • myproject/urls.py: The URL declarations for this Django project; a “table of contents” of your Django-powered site.

Django comes with some local server software included. The Django development server is not hardy enough for actual production use (please, please don't use it for sites other people are supposed to see!) but it makes it easy to see the changes you make to your web app as you build it. Let's make sure our project works by verifying that the dev server will start:

$ python manage.py runserver

You should see some output in your terminal that looks like:

Validating models...
0 errors found.
 
Django version 1.2, using settings 'myproject.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

Visit http://localhost:8000 in your web browser, and you’ll see a “Welcome to Django” page, in pleasant, light-blue pastel. It worked! :)

Observe the logging that happens in the terminal where your server is running:

[24/Mar/2011 11:50:18] "GET / HTTP/1.1" 200 2057

which has the format:

DATE  METHOD URL  PROTOCOL  RESPONSE_CODE  CONTENTSIZE

Navigate to http://127.0.0.1:8000/some/url/. What changes in the terminal log?

Exit the server by returning to the terminal window where the development server is running and pressing CONTROL-C on your keyboard.

Version control

Before we do anything else, let’s commit our work in git.

As you recall, git lets you create checkpoints over the course of the time a program is being developed. Commits are those checkpoints. Programmers often have to go back into the history of a program to change things--whether it's to diagnose a bug or redesign how a feature works. Programmers also have to have an easy way of being able to edit the same application at the same time, and to share their edits with each other. Thus, besides saving their work the normal way, programmers commit their code using version control software.

Each commit contains the diff--the "difference", all of the changes you made to your code base -- between that commit and the commit before it. Different branches in git share the commits made prior to the branching-off point, then each have their own commit history.

To make a commit, first type

git status

into your terminal. This will let you know what changes git has noticed in your code and which of those changes, if any, are staged and ready to be committed.

$ git status
# On branch my-branch-1
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#   myproject/
nothing added to commit but untracked files present (use "git add" to track)

"Untracked files" means that git has noticed some new files inside its repository folder, but you haven't told git explicitly that you want it to "listen" for, and track, changes in those files.

  • Add the folder:
    git add manage.py
    
    . What does git status say now?
  • git commit to commit those files. Adding the -m flag lets you say what the 'message' for the commit is in the same line:
git commit -m "Initial commit of Django project for the PyCon 2013 Django tutorial"
  • Look at your changes with
    git log
    
    to see your history. Is your commit message there?

Huzzah! We committed our changes so far. Now let's make some more changes!

Set up your settings and database

Now that we have a the scaffolding for our project in place, we can get to work! First, it needs to be configured.

Open /myproject/settings.py in your editor. settings.py is a Python script that only contains variable definitions. Django looks at the values of these variables when it runs your project.

In settings.py, let's find DATABASES. The DATABASES variable is a dictionary (note the ‘{}’ characters) with one key: default.

DATABASES = {
'default': {
'ENGINE': 'django.db.backends', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
'NAME': '',                      # Or path to database file if using sqlite3.
'USER': '',                      # Not used with sqlite3.
'PASSWORD': '',                  # Not used with sqlite3.
'HOST': '',                      # Set to empty string for 127.0.0.1. Not used with sqlite3.
'PORT': '',                      # Set to empty string for default. Not used with sqlite3.
}
}

Notice that the value of default is itself another dictionary with information about the site’s default database.

Set your app to use a sqlite database, in the ENGINE attribute. Sqlite is great for development because is stores its data in one normal file on your system and therefore is really simple to move around with your app. It's not sturdy enough to use for a website in production, though.

'ENGINE': 'django.db.backends.sqlite3',

Set your app to use a file called 'database.db' to store information for this project.

'NAME': 'database.db',

Does database.db exist right now? (No, but that's okay. It'll get created automatically when it's needed.)

Run git status, then git add and commit your change:

git add settings.py
git commit -m "set up the database for Django to use"

Even though git knows to "listen" to settings.py now, it still won't add it to the 'staging area' for your commit unless you tell it to explicitly with git add.

Notice the INSTALLED_APPS setting towards the bottom of the settings.py. That variable (a python tuple... note the ‘()’ symbols) holds the names of all Django applications that are activated in this Django instance. Apps can be used in multiple projects, and you can package and distribute them for use by others in their projects. Some apps (as you can see) are installed by default!

Add South to our list of installed apps. (We'll need it later.)

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

TODO: Install django-debug-toolbar

Each of our applications makes use of at least one database table, so we need to create the tables in the database before we can use them. To do that, run the following command in your terminal window:

python manage.py syncdb

The syncdb command looks at the INSTALLED_APPS setting and creates any necessary database tables according to the database settings in your settings.py file. You’ll see a message for each database table it creates.

When prompted, you’ll get a prompt asking you if you’d like to create a superuser account for the authentication system. Say yes! Use 'super' as your password for now.

Does database.db exist right now? (Yes! Calling syncdb made Django realize it needed a sqlite database file, so it made one for us.)

Save and commit your work!

Projects vs. apps distinction

We’ve talked a little about Django apps and projects. You might be wondering what that nonsense is all about. Here are the things to know: An app is component of a website that does something. South is a Django app. So is our bookmarks app (or it will be, anyway). An app is:

  • single purpose - login, passwords, polls, forum, etc.
  • orthogonal to / independent of other apps - Bookmarks shouldn’t have to know the inside details of authentication, for example.

A project corresponds roughly to a ‘website’: it has a settings.py file, and it may have corresponding databases or other data stores that the apps interact with. As you can remember, Django automatically made a folder for us called myproject, which you can think of as the app that coordinates all the other apps and other parts of the project.

Most projects--those for more complex websites--should use multiple apps, one for each core piece of functionality. Ideally, you should reuse existing free, open source Django apps that other people have written for things like user accounts, image galleries, and other common use cases. [Django Packages http://www.djangopackages.com] is a good resource for finding such apps.

You can find prewritten Django apps for things like bookmarks and tagging, but we'll be writing them ourselves for the sake of learning. :)

Start your first app

In your terminal, write:

$ python manage.py startapp bookmarks

If you run

$ ls

you'll see that now there is a folder called bookmarks/ in your project!

Inside that folder, you'll see:

$ ls bookmarks/
bookmarks/
__init__.py
models.py
tests.py

Let's commit our new app.

$ git add bookmarks/
$ git commit -m "Made a bookmarks app"

Part 2: URLs, basic views, templates, static files

Note for tutorial attendees

At the end of each section of the tutorial, I'm going to push our progress so far to the tutorial git repository. If you're doing this tutorial in class, please switch back to the master branch, then pull my changes.

$ git checkout master
$ git pull origin master

This will make it so that everyone starts out each section with the same working code, so even if you get stuck or confused in one session, you can still keep up.

Remember that *you* shouldn't be putting your modifications inside the master branch, though! Instead, make a new branch for section 2 based off the master code you just downloaded, and let's get going!

$ git branch my-branch-2
$ git checkout my-branch-2

M-V-C separation concept

TODO: draw a picture

Model: how the data is organized. Controller: how the logic of the app works. View: How the app looks to the user.

Writing our URLs

The first step to writing your application is to design your URL structure. You do this by creating a Python module called a URLconf. URLconfs are how Django associates a given URL with given Python code.

When a user requests a Django-powered page, the system looks at the ROOT_URLCONF setting, which contains a string in Python dotted syntax. Django loads that module and looks for a module-level variable called urlpatterns, which is a sequence of Python tuples in the following format: (regular expression, Python callback function [, optional dictionary])

Django starts at the first regular expression and makes its way down the list, comparing the requested URL against each regular expression until it finds one that matches.

You might ask, “What’s a regular expression?” Regular expressions, or "regexes", are patterns for matching text. In this case, we’re matching the URLs people go to, and using regular expressions to match whole ‘groups’ of them at once. (If you’d like to learn more about regular expressions, read the [Dive into Python guide to regular expressions http://diveintopython.net/regular_expressions/index.html] sometime. Or you can look at this [xkcd http://xkcd.com/208/]. [Regexpal http://regexpal.com/] is also a super helpful tool for writing a regex.)

In addition to matching text, regular expressions can capture text. Capturing means to remember that part of the string, for later use. Regexes use parentheses () to wrap the parts they’re capturing.

For Django, when a regular expression matches the URL that a web surfer requests, Django extracts the captured values (if any) and passes them to a function of your choosing. This is the role of the callback function above. When a regular expression matches the url, Django calls the associated callback function with any captured parts as parameters. These callback functions typically live in one of your apps' views.py, so this will be much clearer after the next section.

When we ran

django-admin.py startproject myproject

to create our project way back when, Django created a default URLconf file called `urls.py` inside myproject/.

To write our app's URLs, edit the file myproject/urls.py so it looks like this:

urlpatterns = patterns('',
    (r'^$', 'bookmarks.views.index'),
    (r'^bookmarks/$', 'bookmarks.views.index'),
    (r'^tags/(\w+)/$', 'bookmarks.views.tag'),
)

Suppose a visitor to your site goes to http://localhost:8000/tags/awesome/.

  • which regex pattern is tripped?
  • what function is then called?
  • what arguments is that function called with?

Save urls.py. Start the dev server

$ python manage.py runserver

and try that url out! What happens?

Save and commit your work.

The idea that a URL doesn’t have to map onto a file or a folder, or some other sort of static resource, is quite powerful. The URL is just a way of giving instructions to some server, somewhere. Note that in this example, both / and /bookmarks/ go to the same place -- they both activate bookmarks.views.index!

(Rant: In Django, as in most modern frameworks, you have total control over the way your URLs look. People on the web won’t see cruft like .py or .php or even .html at the end of your URLs. There is no excuse for that kind of stuff in the modern era! (Though, putting .php on the end of all your Django URLs, while pointless, is kind of hilarious and super easy.))

Handling those URLs with some basic views

Start the development server:

python manage.py runserver

Fetch “http://localhost:8000/bookmarks/” in your browser. You should get a pleasantly-colored error page with the following message:

ViewDoesNotExist at /bookmarks/
 
Tried index in module bookmarks.views. Error was: 'module'
object has no attribute 'index'

What's going on? Recall this line:

(r'^bookmarks/$', 'bookmarks.views.index')

If we look in the bookmarks folder, we can see there's a views.py file... but if we look in that file, it's empty! So, our problem is that the URL parsing is going fine, but there is no one listening at the other end of the phone! This ViewDoesNotExist error happened because you haven’t written a function index() in the module bookmarks/views.py.

Well, I guess we should do that!

Let's write some views. Open bookmarks/views.py and put the following Python code in it:

from django.http import HttpResponse
 
def index(request):
    return HttpResponse("Hello, world. You're at the bookmarks index.")

This is a very simple view.

Save the views.py file, then go to http://localhost:8000/questions/ in your browser, and you should see that text.

Add a tag view by adding to the views.py file. This view is slightly different, because it takes an argument (which, remember, is passed in from whatever was captured by the regular expression in the URLconf):

def tag(request, tag_name):
    return HttpResponse("This is a tag: %s" % (tag_name,))

Save views.py.

Navigate to [1]. It’ll run the tag() method and display whatever tag name you provide in the URL.

Add a little html to the ‘question’ view. For example, wrap the question_id in strong tags and verify that the view is indeed bold!

return HttpResponse("This is a tag: <strong>%s</strong>" % (tag_name,))

Add and commit your code. Remember to write a good commit message that explains what changed. (For examples of how not to write git commits, see http://www.commitlogsfromlastnight.com/.)

Handling URLs with templates

These placeholder responses are okay, but you wouldn't want to make a real webpage in a Python string in the middle of a view function! Fortunately, Django comes with a built-in template renderer! Let's edit views.py:

from django.http import HttpResponse
from django.shortcuts import render_to_response

def index(request):
	context = {}
	return render_to_response('index.html', context)

render_to_response is a shortcut function that takes a relative path to a template file and a dictionary of contextual data and, instead of returning "Hello world" like before, returns a rendered web page.

Let's spin up our development web server and see our new view in action!

$ python manage.py runserver

http://localhost:8000/

TemplateDoesNotExist at /

Oops. We get an error because we haven't actually made our index.html template file yet. Let's do that.

First, we'll make a templates folder inside our bookmarks app:

$ mkdir bookmarks/templates

Then let's make a file called index.html inside our templates folder:

<!doctype html>
<html>
<head>
	<title>My bookmarking app</title>
</head>

<body>
	<div id="header">
		<h1>My bookmarking app</h1>
	</div>
	<div id="content">
		<h2>All bookmarks</h2>
		<ul>
			<li>Bookmark</li>
			<li>Another bookmark</li>
			<li>A third bookmark</li>
		</ul>
	</div>
	<div id="footer">
		Brought to you by PyCon US 2013
	</div>
</body>
</html>

Now if you go to http://localhost:8000, you should see the webpage we made in our template file!

Django templates 101

Let's rewrite the tags view to use a template as well.

def tag(request, tag_name):
	context = {
		'tag': tag_name,
	}
	return render_to_response('tag.html', context)

Our tag view gets fed in a variable we can use -- the name of the tag, so we add that to the context. That makes it available to the template to render.

Let's make our tag template:

<!doctype html>
<html>
<head>
    <title>My bookmarking app</title>
</head>

<body>
    <div id="header">
        <h1>My bookmarking app</h1>
    </div>
    <div id="content">
        <h2>Bookmarks tagged {{ tag }}</h2>
        <ul>
            <li>Bookmark</li>
            <li>Another bookmark</li>
            <li>A third bookmark</li>
        </ul>
    </div>
    <div id="footer">
        Brought to you by PyCon US 2013
    </div>
</body>
</html>

In Django's templating language, to put a variable into the template, put it inside double Template:Brackets. If you look at http://localhost:8000/tags/awesome/, you'll now see "awesome" (or whatever tag name you choose) in the space occupied by Template:Tag.

You'll notice--these two templates share an awful lot of code. Repeating yourself in this way is no fun, and a recipe for messy, inconsistent styling. Fortunately, Django templates support inheritance!

Let's make a base.html template. This template isn't rendered by any view, but all (or most) of our templates can inherit from it:

<!doctype html>
<html>
<head>
	<title>My bookmarking app</title>
</head>

<body>
	<div id="header">
		<h1>My bookmarking app</h1>
	</div>
	<div id="content">
		<h2>{% block subheader %}{% endblock %}</h2>
		{% block content %}
                Sample content -- you should never see this, unless an inheriting template fails to have any content block!
		{% endblock %}
	</div>
	<div id="footer">
		Brought to you by PyCon US 2013
	</div>
</body>
</html>

This file contains the generic html layout we want for our site. It also includes two {% block %} tags, which are places where templates that inherit from this template can insert their own special HTML. Here we have a {% block %} for the subheader, and a block for the page content.

Let's make index.html inherit from this template:

{% extends 'base.html' %}

{% block subheader %}All bookmarks{% endblock %}

{% block content %}
<ul>
	<li>Bookmark</li>
	<li>Another bookmark</li>
	<li>A third bookmark</li>
</ul>
{% endblock %}

And tag.html:

{% extends 'base.html' %}

{% block subheader %}Bookmarks tagged {{ tag }}{% endblock %}

{% block content %}
<ul>
    <li>Bookmark</li>
    <li>Another bookmark</li>
    <li>A third bookmark</li>
</ul>
{% endblock %}

The {% extends %} tag tells the template renderer which template this template file inherits from. Each block tag then specifies the content that ought to go in each block.

There are lots of built-in template tags and filters; we'll see a few more later. The full docs on them [lives here https://docs.djangoproject.com/en/dev/ref/templates/builtins/].

As a rule, in Django, things in {% %} control the "logic" of the template, while things in {{ }} actually stick data into the template in particular places.

Static files

Now we know how to create HTML within Django's templating language. For a good web application, though, you will probably want to embed stylesheets, scripts, images, and other files. How can we include static files such as these?

TODO: ls and copy static folder into mysite.bookmarks

Django automatically looks for a folder named 'static' inside any of your Django apps. You can also put static files in other places, such as inside /mysite/ directly. To do that, you would need to add the absolute path of the folder to STATICFILES_DIRS in settings.py.

For this tutorial we'll just leave our static files inside bookmarks/, for the sake of simplicity.

TODO: see how the old HTML looks here

Now let's add some more structure to our HTML templates to make them easier to style. First, base.html:

<!doctype html>
<html>
<head>
    <title>My bookmarking app</title>
    <link href='http://fonts.googleapis.com/css?family=Source+Sans+Pro:200,400,700,900' rel='stylesheet' type='text/css'>
    <link rel="stylesheet" href="/static/css/style.css" type="text/css" media="screen" charset="utf-8">
</head>

<body>
    <div id="container">
        <div id="header">
            <div id="new-bookmark-widget">
                <form>
                <h3>New bookmark</h3>
                <p><label>URL</label> <input></p>
                <p><label>Title</label> <input></p>
                <p><label>Tags (comma-separated)</label> <input></p>
                <p><button>Submit</button>
                </form>
            </div>
            <h1><a href="/">My bookmarking app</a></h1>
        </div>
        <div id="content">
            <h2>{% block subheader %}{% endblock %}</h2>
            {% block content %}
            Sample content -- you should never see this, unless an inheriting template fails to have any content block!
            {% endblock %}
        </div>
        <div id="footer">
            Brought to you by PyCon US 2013
        </div>
    </div>
</body>
</html>

And also index.html:

{% block content %}
<ul class="bookmarks">
	<li>
        <a class="bookmark-link" href="">Bookmark</a>
        <div class="metadata"><span class="author">Posted by Jane Smith</span> | <span class="timestamp">2012-2-29</span> | <span class="tags"><a href="">funny</a> <a href="">haha</a></span></div>
    </li>
	<li>
        <a class="bookmark-link" href="">Another bookmark</a>
        <div class="metadata"><span class="author">Posted by Jeanne Doe</span> | <span class="timestamp">2012-2-28</span></div>
    </li>
	<li>
        <a class="bookmark-link" href="">A third bookmark</a>
        <div class="metadata"><span class="author">Posted by Jayne Cobb</span> | <span class="timestamp">2012-2-28</span> | <span class="tags"><a href="">funny</a></span></div>
    </li>
    <li>
        <a class="bookmark-link" href="">La la la bookmark</a>
        <div class="metadata"><span class="author">Posted by Jeanne Doe</span> | <span class="timestamp">2012-2-28</span> | <span class="tags"><a href="">blah</a> <a href="">school</a></span></div>
    </li>
    <li>
        <a class="bookmark-link" href="">Rhubarb rhubarb bookmark</a>
        <div class="metadata"><span class="author">Posted by Jeanne Doe</span> | <span class="timestamp">2012-2-27</span></div>
    </li>
</ul>
{% endblock %}

and tag.html:

{% block content %}
<ul class="bookmarks">
    <li>
        <a class="bookmark-link" href="">Bookmark</a>
        <div class="metadata"><span class="author">Posted by Jane Smith</span> | <span class="timestamp">2012-2-29</span> | <span class="tags"><a href="">funny</a> <a href="">haha</a></span></div>
    </li>
    <li>
        <a class="bookmark-link" href="">Another bookmark</a>
        <div class="metadata"><span class="author">Posted by Jeanne Doe</span> | <span class="timestamp">2012-2-28</span></div>
    </li>
    <li>
        <a class="bookmark-link" href="">A third bookmark</a>
        <div class="metadata"><span class="author">Posted by Jayne Cobb</span> | <span class="timestamp">2012-2-28</span> | <span class="tags"><a href="">funny</a></span></div>
    </li>
</ul>
{% endblock %}

Now if we reload our site at http://localhost:8000, we can see that the prewritten static files have modified it some, and it looks significantly nicer than it did before!

Part 3: Models, our database, and making it visible

Introduction to databases and the ORM, or: finally, something we couldn't've done with plain HTML/CSS/JS!

Okay, being able to design your URLs however you want, without them having to correspond to your actual file structure, is pretty neat. But besides that, we haven't done anything yet that you couldn't do with just HTML, CSS, and JavaScript. Time to fix that.

TODO ORM = Object Relational Mapper/Mapping. Translating Python classes into representations in a database on your server for you.

TODO (models.py --> ORM --> DB chart)

Creating a basic model

Let's make it so we can store some real live data in our application! In our simple bookmarking app, we’ll create two models: Bookmarks and Tags. What information do we need to store about these objects?

A bookmark has:

  • A URL
  • A title (optionally)
  • A timestamp for when the bookmark was made

A tag has:

  • The tag slug/name

A tag also needs to know which bookmarks it applies to.

How do we represent this information in a Django model?

Open up bookmarks/models.py in your editor.

from django.db import models


class Bookmark(models.Model):
    url = models.URLField()
    timestamp = models.DateTimeField(auto_now_add=True)
    title = models.CharField(max_length=200, blank=True, null=True)

What's going on here?

  • We've made a Bookmark class that inherits from Django's model class.
  • We gave it a url, which is a URLField (a field that expects a string that parses as a valid URL).
  • We gave it a timestamp, which is a DateTimeField (a field that expects a Python datetime object). auto_now_add means that the field will automatically set itself to the time when the model is created.
  • Finally, we gave it a title, which is a CharField (a field that expects a string of some length or less, in this case less than 200 characters). "blank=True" and "null=True" mean that this field is allowed to be blank--filling it in is optional.

Making fields optional is a pretty common thing to want to do. To make it easier, you might rewrite your models file to have this shortcut:

from django.db import models

optional = dict(blank=True, null=True)


class Bookmark(models.Model):
    url = models.URLField()
    timestamp = models.DateTimeField(auto_now_add=True)
    title = models.CharField(max_length=200, **optional)

Now let's add a tag model! In the same file, below the Bookmark model:

class Tag(models.Model):
    bookmark = models.ManyToManyField(Bookmark)
    slug = models.CharField(max_length=50)

The tag slug is stored in another CharField, which we've seen before. But what's this ManyToManyField? Well, it's one type of field for denoting a relationship between two models.

In relational databases, there's two basic types of relationships. In a ForeignKey relationship, one model relates to one and only one other model. You could imagine this like a Car model and a Wheel model. Each Wheel instance belongs to one and only one Car, so it would have a ForeignKey field for its car. The car, of course, can have multiple wheels.

With a ManyToManyField, on the other hand, the relationship isn't exclusive for either of the models involved. For instance, you could imagine having a Pizza model and a Topping model. Each Pizza can have multiple Toppings, and each Topping can be on multiple Pizzas.

(There are other variants of these fields, but this is the basic concept. See https://docs.djangoproject.com/en/dev/ref/models/fields/#module-django.db.models.fields.related for more details for how these relationship fields work in Django.)

In this case, each Bookmark can have multiple Tags, and each Tag can apply to multiple Bookmarks. So we use a ManyToManyField to store that information.

Creating database tables for your app (carefully, with South)

When you first create your models, you might not always know exactly what fields your models will need in advance. Maybe someday your bookmarks app will have preview thumbnail images! Then you would want to add another field to the model to store that information. Maybe someday you'll decide that tracking all the timestamps is silly, and want to delete that field.

Unfortunately, Django (and most database-using software) can’t figure out how to handle model changes very well on its own. Fortunately, a Django app called South that we installed earlier can handle these database changes--called ‘migrations’--for us.

Now that we’ve made our first version of our models file, let’s set up our bookmarks app to work with South so that we can make migrations with it in the future!

On the command line, write:

$ python manage.py schemamigration bookmarks --initial

As you can see, that’s created a migrations directory for us, and automatically made a new migration file inside it.

$ ls bookmarks/migrations/
0001_initial.py  __init__.py

All we need to do now is apply our new migration:

$ python manage.py migrate bookmarks

Great! Now our database file knows about bookmarks and its new models, and if we need to change our models, South is set up to handle those changes. We’ll come back to South later.

IMPORTANT: You can't migrate an app if it's already been synced in the database using

python manage.py syncdb

. But you do need to run syncdb at least once before you use south (since south itself uses syncdb to give itself space in your database). That's why it's super important that when you run syncdb, south should be listed under INSTALLED_APPS, but none of your own apps should be, and after you add your app to INSTALLED_APPS, you must not run syncdb again until after you've already set up migrations with that app.

Add and commit all your work, including the migrations folder that South generated for you.

Add some bookmarks via the command line

Now, let’s hop into the interactive Python shell and play around with the free API ("Application programming interface" -- APIs aren't just data doodads that hip web startups provide for you, they're an important concept in software architecture.) that Django gives you. To invoke the Python shell, use this command:

$ python manage.py shell

We’re using this instead of simply typing “python”, because manage.py's shell sets up the project’s environment for you. “Setting up the environment” involves two things:

  • Making sure bookmarks (and any other apps you might have) are on the right path to be imported.
  • Setting the DJANGO_SETTINGS_MODULE environment variable, which gives Django the path to your settings.py file.

Once you’re in the shell, let's explore the database API. Let's import the model classes we just wrote:

>>> from bookmarks.models import Bookmark, Tag

List all the current Bookmarks:

>>> Question.objects.all()
[]

How many bookmarks is this? It's an empty list, so zero!

Let's add a bookmark:

>>> b = Bookmark(url="http://www.bringinthecats.com/", title="Funny link")

We could specify the value for timestamp as well here, but we don't have to since when we wrote its model definition, we specified that timestamp has a default value (of when the bookmark is created). We did specify a title for the bookmark, even though we didn't have to since it's an optional field.

Try getting the ID number of our new bookmark 'b' by typing:

q.id

What happens?

Save your bookmark to the database. In Django, you have to call save() explicitly.

>>> b.save()

Get the id of the Bookmark instance. Because it’s been saved, it has an ID in the database now! Even though we didn't specify one in our models.py, every saved model instance automatically has an id field.

>>> q.id
1

Access the database columns (Fields, in Django parlance) as Python attributes:

>>> b.title
"Funny link"
>>> b.timestamp
datetime.datetime(2011, 12, 1, 3, 3, 55, 841929)

We can change the bookmark title by changing its title attribute, then calling save().

>>> b.title = "WHEEEE"
>>> b.save()
>>> b.pub_date
"WHEEEE"

If we now ask Django to show a list of all the Bookmark objects available, we can see it's no longer an empty list!

>>> Bookmark.objects.all()
[<Bookmark: Bookmark object>]

Fix the hideous default model representation

Wait a minute! <Bookmark: Bookmark object> is an utterly unhelpful representation of this object. Let’s fix that by editing the Bookmark model. Use your text editor to open the bookmarks/models.py file and adding a __unicode__() method to both Bookmark and Tag:

class Bookmark(models.Model):
    url = models.URLField()
    timestamp = models.DateTimeField(auto_now_add=True)
    title = models.CharField(max_length=200, **optional)

	def __unicode__(self):
		return self.url


class Tag(models.Model):
	bookmark = models.ManyToManyField(Bookmark)
	slug = models.CharField(max_length=50)

	def __unicode__(self):
		return self.slug

Let's see our new shiny __unicode__ methods in action. Start a new Python interactive shell by running:

python manage.py shell
>>> from bookmark.models import Bookmark, Tag
>>> Bookmark.objects.all()
[<Bookmark: http://www.bringinthecats.com/>]

Adding more data via the shell

>>> b = Bookmark(url="http://www.google.com")
>>> b.save()
>>> b = Bookmark(url="https://us.pycon.org/2013/", title="PyCon US website")
>>> b.save()
>>> b = Bookmark(url="https://www.djangoproject.com")
>>> b.save()

We've created a bunch of bookmarks, but no tags! Let's change that.

>>> b = Bookmark.objects.all()[0]
>>> b
<Bookmark: http://www.bringinthecats.com/>
>>> b.tag_set.create(slug="cats")
<Tag: cats>
>>> b.tag_set.create(slug="music")
<Tag: music>
>>> b.tag_set.create(slug="funny")
<Tag: funny>
>>> b.save()

Slicing and dicing Django data

There are many methods for searching across your Django models.

We can filter our bookmarks, for instance for ones with a particular URL:

>>> foo = Bookmark.objects.filter(url="http://www.bringinthecats.com/")
>>> foo
[<Bookmark: http://www.bringinthecats.com/>]

Or for bookmarks which have titles:

>>> titled_bookmarks = Bookmark.objects.filter(title__isnull=False)
>>> titled_bookmarks
[<Bookmark: http://www.bringinthecats.com/>, <Bookmark: https://us.pycon.org/2013/">]

If you try to use filter to search for a question that does not exist, filter will give you the empty list.

>>> Bookmark.objects.filter(title="Who framed Roger Rabbit?")
[]

The get method, on the other hand, returns exactly one hit. If it finds zero matches, or more than one match, it will raise an exception.

>>> Bookmark.objects.get(id=1)
<Bookmark: http://www.bringinthecats.com/>
>>> Bookmark.objects.get(id=4)
Traceback (most recent call last):
    ...
DoesNotExist: Bookmark matching query does not exist.

More information on making queries with Django's ORM can be found in the Django docs at https://docs.djangoproject.com/en/dev/topics/db/queries/#retrieving-objects.

Part 3.5: Changing our mind and adding users

D'oh! You know what every social bookmarking app has, that ours doesn't have? Users!

I don't mean like the number of people using it--I mean a way to store different users' accounts and keep track of who owns which bookmarks. So let's change our app to include this feature.

Lucky for us, Django comes with an app for user accounts and authentication from the get-go! In fact, it's already installed. If you look back at your settings.py file, you'll see that in INSTALLED_APPS, there is an entry for 'django.contrib.auth'.

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',
    'south',
    'debug_toolbar',
    'bookmarks'
)

That's our authentication app!

Let's open the Django shell and play with this app a bit.

$ python manage.py shell
>>> from django.contrib.auth.models import User
>>> User.objects.all()
[<User: karen>]

Whaaaaat?? There's already a User here. How can that be?

You might recall making a 'superuser' account when you first set up your Django project. That superuser was, in fact, created using Django's built-in auth app.

What is our user account's id number?

>>> me = User.objects.all()[0]
>>> me.id
1

Neato!

Add user field to bookmark

Now we need to create a relationship between the built-in User model and our Bookmark model.

First, in our models.py file, we need to import django.contrib.auth's built-in User model so that we can refer to it in our models.

from django.db import models
from django.contrib.auth.models import User

Now we need to think--what kind of relationship do users and bookmarks have?

Well, a user can have multiple bookmarks. But (right now, anyway) a bookmark should only have one user. So that means that we should use a ForeignKey field to add the user to our Bookmark model.

class Bookmark(models.Model):
    author = models.ForeignKey(User)
    title = models.CharField(max_length=200, **optional)
    url = models.URLField()
    timestamp = models.DateTimeField(auto_now_add=True)

    def __unicode__(self):
        return self.url

While we're at it, let's update the __unicode__ method too to let us know to whom a bookmark belongs to.

class Bookmark(models.Model):
    author = models.ForeignKey(User)
    title = models.CharField(max_length=200, **optional)
    url = models.URLField()
    timestamp = models.DateTimeField(auto_now_add=True)

    def __unicode__(self):
        return "%s by %s" % (self.url, self.author.username)

Make a migration in South

Now that we've added a field to our model, we are going to need to create a database migration. South can help us do this!

To create our migration, run

$ python manage.py schemamigration bookmarks --auto

Note that we're now using --auto instead of --initial (which we used back when we first wrote our models and set up our app to use South).

Eep! Before it will make our migration file, South wants some information from us:

 ? The field 'Bookmark.author' does not have a default specified, yet is NOT NULL.
 ? Since you are adding this field, you MUST specify a default
 ? value to use for existing rows. Would you like to:
 ?  1. Quit now, and add a default to the field in models.py
 ?  2. Specify a one-off value to use for existing columns now
 ? Please select a choice: 2

We could in theory modify our models to specify a default value for author, or make it optional. But neither of those sound like good options--we *want* future bookmarks to be forced to have an author! So we'll choose 2, to set up a default value for our new author field just for the purposes of this migration.

 ? Please enter Python code for your one-off default value.
 ? The datetime module is available, so you can do e.g. datetime.date.today()
 >>>

We then need to come up with a default value. Well, right now there's only one User in our system who the sample bookmarks we'd entered so far could belong to--our superuser account, which (if you don't remember) had an ID number of 1.

So let's enter 1 for our default.

 ? Please enter Python code for your one-off default value.
 ? The datetime module is available, so you can do e.g. datetime.date.today()
 >>> 1
 + Added field author on bookmarks.Bookmark
Created 0002_auto__add_field_bookmark_author.py. You can now apply this migration with: ./manage.py migrate bookmarks

Remember, the first step creates the migration, but doesn't run it. So let's do what South says and run a command to apply our migration!

$ python manage.py migrate bookmarks
Running migrations for bookmarks:
 - Migrating forwards to 0002_auto__add_field_bookmark_author.
 > bookmarks:0002_auto__add_field_bookmark_author
 - Loading initial data for bookmarks.
Installed 0 object(s) from 0 fixture(s)

Modify views and templates to use model data

Let's edit our views and templates so they use real bookmark data from our database!

First, let's add a bit more fake data to our database.

TODO: fake bookmark population script

Then, we'll need to have our view import the bookmark model and send the data to the view.

from django.shortcuts import render_to_response
from bookmarks.models import Bookmark


def index(request):
    bookmarks = Bookmark.objects.all()
    context = {
        'bookmarks': bookmarks
    }
    return render_to_response('index.html', context)

Wait! We don't actually want to load every bookmark in our database when we go to the front page. If we have lots of bookmarks, that will get slow and unwieldy quickly.

Instead, let's show the 10 most recent bookmarks:

def index(request):
    bookmarks = Bookmark.objects.all().order_by('-timestamp')[:10]
    context = {
        'bookmarks': bookmarks
    }
    return render_to_response('index.html', context)

We also want our tag view to show real bookmarks. We only want to show bookmarks that have been tagged with the given tag. So we can write:

from django.shortcuts import render_to_response
from bookmarks.models import Bookmark, Tag


[...]

def tag(request, tag_name):
    tag = Tag.objects.get(slug=tag_name)
    bookmarks = tag.bookmarks.all()
    context = {
        'tag': tag,
        'bookmarks': bookmarks,
    }
    return render_to_response('tag.html', context)

TODO: get_object_or_404

(do something runserver related here?)

Part 4: CRUD with HTML and AJAX

CRUD with Django forms

CRUD with JSON and asynchronous Javascript

(sync up here)

Part 5: Style time

Time to make our CSS and/or JS more awesome!

Part 6: Sharing with others

Surfing to classmates' "runserver" instances

Deployment on Heroku

Sharing on Github

  • Rename origin to upstream
  • Create a fork
  • Do a push

Brief discussion of other deployment options

Part 7: Exercises for the reader

Writing your own tests

Write your own styles / JS frontend behavior

Handling user-uploaded media

Accessing a Django-powered API (Tastypie)

Regular expressions

Text patterns can be encoded as regular expressions. You've already seen them in urls.py. We won't go into great depth here, but we will recommend some resources so you can understand them well, visualize them, and know how to write Python code that takes advantage of them.

First, get to know how they work. To do that, I recommend visualizing them.

Visualizing

To start with that, open up http://www.regexper.com/ and enter in one of the regular expressions we used. For example, enter this:

^$

Then, click the Display button (or hit ENTER on your keyboard). Contrast that with these examples:

^bookmarks\/$

(This is similar to the URL for the bookmarks view. We had to escape the slash character (/) by putting a backslash (\) before it because the regexper.com tool uses JavaScript-esque regular expressions rather than Python regular expressions. Let's ignore that for now.)

You'll see that it matches the exact string of "bookmarks/"; it also constrains things so that the line ends after the word "bookmarks/". If you remove that "$" character, the string "bookmarks/ahoy" would also match.

Executing regular expressions

You can execute regular expressions in a few ways.

First, there is the Python API for that. You can see it in action here:

You can also use http://regexpal.com/ to execute regular expressions

More reading and resources
  • http://regexpal.com/ has a great Quick Reference that you can find by clicking on those words in the top right of the web page.


Learn about relational databases

Django uses a relational database to store your data. In this section, we will explain the very basics of relational databases, give you pointers to more information, and show you how to explore your data.

Within the tutorial, we have used Django's ORM to access our data. That has obscured how the data is actually stored... so now it is time to take a quick look.

SQL is a query language for creating, reading, and modifying databases. SQL queries look something like:

SELECT title FROM bookmarks

You can get access to a SQL shell from within Django by running:

python manage.py dbshell

and you can now execute the above query.

These databases are considered relational because the normal way to use them involves using references between tables to avoid repetition of data. In our tutorial, for example, a Bookmark pointed to a User. This is not the only way to build the app; we could have embedded all of the user's information within each Bookmark. That would have a major downside: the various copies of the information could get out of date.

This tutorial used sqlite, a very popular relational database, but there are plethora of others, including:

  • MySQL
  • Postgres
  • Oracle
  • Microsoft SQL Server

Django can be used with any of these. sqlite is convenient because it requires no setup step; however, popular hosting services typically use MySQL or Postgres. sqlite has one major downside -- it cannot support multiple changes to the database made while each other are being made. That limitation is completely OK for an app running on your laptop.

Even though Django's ORM looks just like Python, it does complex work behind the scenes. This work deserves some respect! One common source of slowness in web applications is executing too many SQL queries.

Exploring your app's data

The simplest way to explore your app's data visually is by finding the database.db file and opening it in a graphical browser. On Linux, Mac, or Windows, install Firefox, and then install the https://addons.mozilla.org/en-us/firefox/addon/sqlite-manager/ add-on.

From there, you can open your database and browse around.

Try to run the above SQL query against your database within SQLite Manager.

Other tools
  • Use http://sqlzoo.net/ to learn more about SQL by trying interactively to write queries that solve problems.