510 lines
19 KiB
Plaintext
510 lines
19 KiB
Plaintext
=====================================
|
|
Writing your first Django app, part 1
|
|
=====================================
|
|
|
|
By Adrian Holovaty <holovaty@gmail.com>
|
|
|
|
Let's learn by example.
|
|
|
|
Throughout this tutorial, we'll walk you through the creation of a simple Web
|
|
poll application.
|
|
|
|
It'll consist of two parts:
|
|
|
|
* A public site that lets people vote in polls and view poll results.
|
|
* An admin site that lets you add, change and delete polls behind the scenes.
|
|
|
|
We'll assume you have `Django installed`_ already.
|
|
|
|
.. _`Django installed`: http://www.djangoproject.com/documentation/install/
|
|
|
|
Initial setup
|
|
=============
|
|
|
|
If this is your first time using Django, you'll have to take care of some
|
|
initial setup.
|
|
|
|
Run the command ``django-admin.py startproject myproject``. That'll create a
|
|
``myproject`` directory in your current directory.
|
|
|
|
(``django-admin.py`` should be on your system path if you installed Django via
|
|
its setup.py utility. If it's not on your path, you can find it in
|
|
``site-packages/django/bin``; consider symlinking to it from some place
|
|
on your path, such as /usr/local/bin.)
|
|
|
|
.. admonition:: Where should this code live?
|
|
|
|
If your background is in PHP, you're probably used to putting code under the
|
|
Web server's document root (in a place such as ``/var/www``). With Django,
|
|
you don't do that. It's not a good idea to put any of this Python code within
|
|
your Web server's document root, because it risks the possibility that
|
|
people may be able to view your code over the Web. That's not good for
|
|
security.
|
|
|
|
Put your code in some directory **outside** of the document root, such as
|
|
``/home/mycode``.
|
|
|
|
A project is a collection of settings for an instance of Django -- including
|
|
database configuration, Django-specific options and application-specific
|
|
settings. Let's look at what ``startproject`` created::
|
|
|
|
myproject/
|
|
__init__.py
|
|
manage.py
|
|
settings.py
|
|
urls.py
|
|
|
|
These files are:
|
|
|
|
* ``manage.py``: A command-line utility that lets you interact with this
|
|
Django project in various ways.
|
|
* ``settings.py``: Settings/configuration for this Django project.
|
|
* ``urls.py``: The URL declarations for this Django project; a "table of
|
|
contents" of your Django-powered site.
|
|
|
|
The development server
|
|
----------------------
|
|
|
|
Change into the ``myproject`` directory, if you haven't already, and run the
|
|
command ``python manage.py runserver``. You'll see the following output on the
|
|
command line::
|
|
|
|
Validating models...
|
|
0 errors found.
|
|
|
|
Starting server on port 8000 with settings module 'myproject.settings'.
|
|
Go to http://127.0.0.1:8000/ for Django.
|
|
Quit the server with CONTROL-C (Unix) or CTRL-BREAK (Windows).
|
|
|
|
You've started the Django development server, a lightweight, pure-Python Web
|
|
server that builds on the BaseHTTPServer included in Python's standard library.
|
|
We've included this with Django so you can develop things rapidly, without
|
|
having to deal with configuring Apache until you're ready for production.
|
|
|
|
DON'T use this server in anything resembling a production environment. It's
|
|
intended only for use while developing.
|
|
|
|
.. admonition:: Changing the port
|
|
|
|
By default, the ``runserver`` command starts the development server on port
|
|
8000. If you want to change the server's port, pass it as a command-line
|
|
argument::
|
|
|
|
python manage.py runserver 8080
|
|
|
|
Now that the server's running, visit http://127.0.0.1:8000/ with your Web
|
|
browser. You'll see a "Welcome to Django" page, in pleasant, light-blue pastel.
|
|
It worked!
|
|
|
|
Database setup
|
|
--------------
|
|
|
|
Now, edit ``settings.py``. It's a normal Python module with module-level
|
|
variables representing Django settings. Change these settings to match your
|
|
database's connection parameters:
|
|
|
|
* ``DATABASE_ENGINE`` -- Either 'postgresql', 'mysql' or 'sqlite3'.
|
|
More coming soon.
|
|
* ``DATABASE_NAME`` -- The name of your database, or the full (absolute)
|
|
path to the database file if you're using SQLite.
|
|
* ``DATABASE_USER`` -- Your database username (not used for SQLite).
|
|
* ``DATABASE_PASSWORD`` -- Your database password (not used for SQLite).
|
|
* ``DATABASE_HOST`` -- The host your database is on. Leave this as an
|
|
empty string if your database server is on the same physical machine
|
|
(not used for SQLite).
|
|
|
|
.. admonition:: Note
|
|
|
|
Make sure you've created a database within PostgreSQL or MySQL by this
|
|
point. Do that with "``CREATE DATABASE database_name;``" within your
|
|
database's interactive prompt.
|
|
|
|
Run the following command to initialize your database with Django's core
|
|
database tables::
|
|
|
|
python manage.py init
|
|
|
|
If you don't see any errors, it worked.
|
|
|
|
If you're interested, run the command-line client for your database and type
|
|
``\dt`` (PostgreSQL), ``SHOW TABLES;`` (MySQL), or ``.schema`` (SQLite) to
|
|
display the tables Django created.
|
|
|
|
.. admonition:: About those database tables
|
|
|
|
The tables created by ``manage.py init`` are for sessions, authentication
|
|
and other features Django provides. The next release of Django will have
|
|
a "lite" version of the ``init`` command that won't install any database
|
|
tables if you don't want them.
|
|
|
|
Creating models
|
|
===============
|
|
|
|
Now that your environment -- a "project" -- is set up, you're set to start
|
|
doing work. (You won't have to take care of that boring administrative stuff
|
|
again.)
|
|
|
|
Each application you write in Django consists of a Python package, somewhere
|
|
on your `Python path`_, that follows a certain convention. Django comes with a
|
|
utility that automatically generates the basic directory structure of an app,
|
|
so you can focus on writing code rather than creating directories.
|
|
|
|
.. admonition:: Projects vs. apps
|
|
|
|
What's the difference between a project and an app? An app is a Web
|
|
application that does something -- e.g., a weblog system, a database of
|
|
public records or a simple poll app. A project is a collection of
|
|
configuration and apps for a particular Web site. A project can contain
|
|
multiple apps. An app can be in multiple projects.
|
|
|
|
In this tutorial, we'll create our poll app in the ``myproject`` directory,
|
|
for simplicity. As a consequence, the app will be coupled to the project --
|
|
that is, Python code within the poll app will refer to ``myproject.polls``.
|
|
Later in this tutorial, we'll discuss decoupling your apps for distribution.
|
|
|
|
To create your app, make sure you're in the ``myproject`` directory and type
|
|
this command::
|
|
|
|
python manage.py startapp polls
|
|
|
|
That'll create a directory ``polls``, which is laid out like this::
|
|
|
|
polls/
|
|
__init__.py
|
|
models/
|
|
__init__.py
|
|
polls.py
|
|
views.py
|
|
|
|
This directory structure will house the poll application.
|
|
|
|
The first step in writing a database Web app in Django is to define your models
|
|
-- essentially, your database layout, with additional metadata.
|
|
|
|
.. admonition:: Philosophy
|
|
|
|
A model is the single, definitive source of data about your
|
|
data. It contains the essential fields and behaviors of the data you're
|
|
storing. Django follows the `DRY Principle`_. The goal is to define your
|
|
data model in one place and automatically derive things from it.
|
|
|
|
In our simple poll app, we'll create two models: polls and choices. A poll has
|
|
a question and a publication date. A choice has two fields: the text of the
|
|
choice and a vote tally. Each choice is associated with a poll.
|
|
|
|
These concepts are represented by simple Python classes. Edit the
|
|
``polls/models/polls.py`` file so it looks like this::
|
|
|
|
from django.core import meta
|
|
|
|
class Poll(meta.Model):
|
|
question = meta.CharField(maxlength=200)
|
|
pub_date = meta.DateTimeField('date published')
|
|
|
|
class Choice(meta.Model):
|
|
poll = meta.ForeignKey(Poll)
|
|
choice = meta.CharField(maxlength=200)
|
|
votes = meta.IntegerField()
|
|
|
|
The code is straightforward. Each model is represented by a class that
|
|
subclasses ``django.core.meta.Model``. Each model has a number of class
|
|
variables, each of which represents a database field in the model.
|
|
|
|
Each field is represented by an instance of a ``meta.*Field`` class -- e.g.,
|
|
``meta.CharField`` for character fields and ``meta.DateTimeField`` for
|
|
datetimes. This tells Django what type of data each field holds.
|
|
|
|
The name of each ``meta.*Field`` instance (e.g. ``question`` or ``pub_date`` )
|
|
is the field's name, in machine-friendly format. You'll use this value in your
|
|
Python code, and your database will use it as the column name.
|
|
|
|
You can use an optional first positional argument to a ``Field`` to designate a
|
|
human-readable name. That's used in a couple of introspective parts of Django,
|
|
and it doubles as documentation. If this field isn't provided, Django will use
|
|
the machine-readable name. In this example, we've only defined a human-readable
|
|
name for ``Poll.pub_date``. For all other fields in this model, the field's
|
|
machine-readable name will suffice as its human-readable name.
|
|
|
|
Some ``meta.*Field`` classes have required elements. ``meta.CharField``, for
|
|
example, requires that you give it a ``maxlength``. That's used not only in the
|
|
database schema, but in validation, as we'll soon see.
|
|
|
|
Finally, note a relationship is defined, using ``meta.ForeignKey``. That tells
|
|
Django each Choice is related to a single Poll. Django supports all the common
|
|
database relationships: many-to-ones, many-to-manys and one-to-ones.
|
|
|
|
.. _`Python path`: http://docs.python.org/tut/node8.html#SECTION008110000000000000000
|
|
.. _DRY Principle: http://c2.com/cgi/wiki?DontRepeatYourself
|
|
|
|
Activating models
|
|
=================
|
|
|
|
That small bit of model code gives Django a lot of information. With it, Django
|
|
is able to:
|
|
|
|
* Create a database schema (``CREATE TABLE`` statements) for this app.
|
|
* Create a Python database-access API for accessing Poll and Choice objects.
|
|
|
|
But first we need to tell our project that the ``polls`` app is installed.
|
|
|
|
.. admonition:: Philosophy
|
|
|
|
Django apps are "pluggable": You can use an app in multiple projects, and
|
|
you can distribute apps, because they don't have to be tied to a given
|
|
Django installation.
|
|
|
|
Edit the ``settings.py`` file again, and change the ``INSTALLED_APPS`` setting
|
|
to include the string ``'myproject.polls'``. So it'll look like this::
|
|
|
|
INSTALLED_APPS = (
|
|
'myproject.polls',
|
|
)
|
|
|
|
(Don't forget the trailing comma, because of Python's rule about single-value
|
|
tuples: Without a trailing comma, Python wouldn't know this was a tuple.)
|
|
|
|
Now Django knows ``myproject`` includes the ``polls`` app. Let's run another command::
|
|
|
|
python manage.py sql polls
|
|
|
|
You should see the following (the CREATE TABLE SQL statements for the polls app)::
|
|
|
|
BEGIN;
|
|
CREATE TABLE "polls_polls" (
|
|
"id" serial NOT NULL PRIMARY KEY,
|
|
"question" varchar(200) NOT NULL,
|
|
"pub_date" timestamp with time zone NOT NULL
|
|
);
|
|
CREATE TABLE "polls_choices" (
|
|
"id" serial NOT NULL PRIMARY KEY,
|
|
"poll_id" integer NOT NULL REFERENCES "polls_polls" ("id"),
|
|
"choice" varchar(200) NOT NULL,
|
|
"votes" integer NOT NULL
|
|
);
|
|
COMMIT;
|
|
|
|
Note the following:
|
|
|
|
* Table names are automatically generated by combining the name of the app
|
|
(``polls``) with a plural version of the object name (polls and choices).
|
|
(You can override this behavior.)
|
|
|
|
* Primary keys (IDs) are added automatically. (You can override this, too.)
|
|
|
|
* Django appends ``"_id"`` to the foreign key field name, by convention.
|
|
Yes, you can override this, as well.
|
|
|
|
* The foreign key relationship is made explicit by a ``REFERENCES`` statement.
|
|
|
|
* It's tailored to the database you're using, so database-specific field
|
|
types such as ``auto_increment`` (MySQL), ``serial`` (PostgreSQL), or
|
|
``integer primary key`` (SQLite) are handled for you automatically. Same
|
|
goes for quoting of field names -- e.g., using double quotes or single
|
|
quotes. The author of this tutorial runs PostgreSQL, so the example
|
|
output is inPostgreSQL syntax.
|
|
|
|
If you're interested, also run the following commands:
|
|
|
|
* ``python manage.py sqlinitialdata polls`` -- Outputs the initial-data
|
|
inserts required for Django's admin framework.
|
|
|
|
* ``python manage.py sqlclear polls`` -- Outputs the necessary ``DROP
|
|
TABLE`` statements for this app, according to which tables already exist
|
|
in your database (if any).
|
|
|
|
* ``python manage.py sqlindexes polls`` -- Outputs the ``CREATE INDEX``
|
|
statements for this app.
|
|
|
|
* ``python manage.py sqlall polls`` -- A combination of 'sql' and
|
|
'sqlinitialdata'.
|
|
|
|
Looking at the output of those commands can help you understand what's actually
|
|
happening under the hood.
|
|
|
|
Now, run this command to create the database tables for the polls app
|
|
automatically::
|
|
|
|
python manage.py install polls
|
|
|
|
Behind the scenes, all that command does is take the output of
|
|
``python manage.py sqlall polls`` and execute it in the database pointed-to by
|
|
your Django settings file.
|
|
|
|
Read the `django-admin.py documentation`_ for full information on what the
|
|
``manage.py`` utility can do.
|
|
|
|
.. _django-admin.py documentation: http://www.djangoproject.com/documentation/django_admin/
|
|
|
|
Playing with the API
|
|
====================
|
|
|
|
Now, let's hop into the interactive Python shell and play around with the free
|
|
API 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`` sets
|
|
up the project's environment for you. "Setting up the environment" involves two
|
|
things:
|
|
|
|
* Putting ``myproject`` on ``sys.path``. For flexibility, several pieces of
|
|
Django refer to projects in Python dotted-path notation (e.g.
|
|
``'myproject.polls.models'``). In order for this to work, the
|
|
``myproject`` package has to be on ``sys.path``.
|
|
|
|
We've already seen one example of this: the ``INSTALLED_APPS`` setting is
|
|
a list of packages in dotted-path notation.
|
|
|
|
* Setting the ``DJANGO_SETTINGS_MODULE`` environment variable, which gives
|
|
Django the path to your ``settings.py`` file.
|
|
|
|
.. admonition:: Bypassing manage.py
|
|
|
|
If you'd rather not use ``manage.py``, no problem. Just make sure
|
|
``myproject`` is at the root level on the Python path (i.e.,
|
|
``import myproject`` works) and set the ``DJANGO_SETTINGS_MODULE``
|
|
environment variable to ``myproject.settings``.
|
|
|
|
For more information on all of this, see the `django-admin.py documentation`_.
|
|
|
|
Once you're in the shell, explore the database API::
|
|
|
|
# Modules are dynamically created within django.models.
|
|
# Their names are plural versions of the model class names.
|
|
>>> from django.models.polls import polls, choices
|
|
|
|
# No polls are in the system yet.
|
|
>>> polls.get_list()
|
|
[]
|
|
|
|
# Create a new Poll.
|
|
>>> from datetime import datetime
|
|
>>> p = polls.Poll(question="What's up?", pub_date=datetime.now())
|
|
|
|
# Save the object into the database. You have to call save() explicitly.
|
|
>>> p.save()
|
|
|
|
# Now it has an ID. Note that this might say "1L" instead of "1", depending
|
|
# on which database you're using. That's no biggie; it just means your
|
|
# database backend prefers to return integers as Python long integer
|
|
# objects.
|
|
>>> p.id
|
|
1
|
|
|
|
# Access database columns via Python attributes.
|
|
>>> p.question
|
|
"What's up?"
|
|
>>> p.pub_date
|
|
datetime.datetime(2005, 7, 15, 12, 00, 53)
|
|
|
|
# Change values by changing the attributes, then calling save().
|
|
>>> p.pub_date = datetime(2005, 4, 1, 0, 0)
|
|
>>> p.save()
|
|
|
|
# get_list() displays all the polls in the database.
|
|
>>> polls.get_list()
|
|
[<Poll object>]
|
|
|
|
Wait a minute. ``<Poll object>`` is, utterly, an unhelpful representation of
|
|
this object. Let's fix that by editing the polls model
|
|
(in the ``polls/models/polls.py`` file) and adding a ``__repr__()`` method to
|
|
both ``Poll`` and ``Choice``::
|
|
|
|
class Poll(meta.Model):
|
|
# ...
|
|
def __repr__(self):
|
|
return self.question
|
|
|
|
class Choice(meta.Model):
|
|
# ...
|
|
def __repr__(self):
|
|
return self.choice
|
|
|
|
It's important to add ``__repr__()`` methods to your models, not only for your
|
|
own sanity when dealing with the interactive prompt, but also because objects'
|
|
representations are used throughout Django's automatically-generated admin.
|
|
|
|
Note these are normal Python methods. Let's add a custom method, just for
|
|
demonstration::
|
|
|
|
class Poll(meta.Model):
|
|
# ...
|
|
def was_published_today(self):
|
|
return self.pub_date.date() == datetime.date.today()
|
|
|
|
Note ``import datetime`` wasn't necessary. Each model method has access to
|
|
a handful of commonly-used variables for convenience, including the
|
|
``datetime`` module from the Python standard library.
|
|
|
|
Let's jump back into the Python interactive shell by running
|
|
``python manage.py shell`` again::
|
|
|
|
>>> from django.models.polls import polls, choices
|
|
# Make sure our __repr__() addition worked.
|
|
>>> polls.get_list()
|
|
[What's up?]
|
|
|
|
# Django provides a rich database lookup API that's entirely driven by
|
|
# keyword arguments.
|
|
>>> polls.get_object(id__exact=1)
|
|
What's up?
|
|
>>> polls.get_object(question__startswith='What')
|
|
What's up?
|
|
>>> polls.get_object(pub_date__year=2005)
|
|
What's up?
|
|
>>> polls.get_object(id__exact=2)
|
|
Traceback (most recent call last):
|
|
...
|
|
PollDoesNotExist: Poll does not exist for {'id__exact': 2}
|
|
>>> polls.get_list(question__startswith='What')
|
|
[What's up?]
|
|
|
|
# Lookup by a primary key is the most common case, so Django provides a
|
|
# shortcut for primary-key exact lookups.
|
|
# The following is identical to polls.get_object(id__exact=1).
|
|
>>> polls.get_object(pk=1)
|
|
What's up?
|
|
|
|
# Make sure our custom method worked.
|
|
>>> p = polls.get_object(pk=1)
|
|
>>> p.was_published_today()
|
|
False
|
|
|
|
# Give the Poll a couple of Choices. Each one of these method calls does an
|
|
# INSERT statement behind the scenes and returns the new Choice object.
|
|
>>> p = polls.get_object(pk=1)
|
|
>>> p.add_choice(choice='Not much', votes=0)
|
|
Not much
|
|
>>> p.add_choice(choice='The sky', votes=0)
|
|
The sky
|
|
>>> c = p.add_choice(choice='Just hacking again', votes=0)
|
|
|
|
# Choice objects have API access to their related Poll objects.
|
|
>>> c.get_poll()
|
|
What's up?
|
|
|
|
# And vice versa: Poll objects get access to Choice objects.
|
|
>>> p.get_choice_list()
|
|
[Not much, The sky, Just hacking again]
|
|
>>> p.get_choice_count()
|
|
3
|
|
|
|
# The API automatically follows relationships as far as you need.
|
|
# Use double underscores to separate relationships.
|
|
# This works as many levels deep as you want. There's no limit.
|
|
# Find all Choices for any poll whose pub_date is in 2005.
|
|
>>> choices.get_list(poll__pub_date__year=2005)
|
|
[Not much, The sky, Just hacking again]
|
|
|
|
# Let's delete one of the choices. Use delete() for that.
|
|
>>> c = p.get_choice(choice__startswith='Just hacking')
|
|
>>> c.delete()
|
|
|
|
For full details on the database API, see our `Database API reference`_.
|
|
|
|
When you're comfortable with the API, read `part 2 of this tutorial`_ to get
|
|
Django's automatic admin working.
|
|
|
|
.. _Database API reference: http://www.djangoproject.com/documentation/db_api/
|
|
.. _part 2 of this tutorial: http://www.djangoproject.com/documentation/tutorial2/
|