Thursday, June 10, 2010

Creating models in Django

Now that your environment -- a "project" -- is set up, you're set to start doing work.
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.

In this tutorial, we'll create our poll app in the mysite 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 mysite.polls. Later in this tutorial, we'll discuss decoupling your apps for distribution.
To create your app, make sure you're in the mysite 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.py
    tests.py
    views.py
This directory structure will house the poll application.

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.py file so it looks like this:
from django.db import models

class Poll(models.Model):
    question = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')

class Choice(models.Model):
    poll = models.ForeignKey(Poll)
    choice = models.CharField(max_length=200)
    votes = models.IntegerField
 

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.

Edit the settings.py file again, and change the INSTALLED_APPS setting to include the string 'mysite.polls'. So it'll look like this:
INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'mysite.polls'
)
Now Django knows mysite includes the polls app. Let's run another command:
python manage.py sql polls
You should see something similar to the following (the CREATE TABLE SQL statements for the polls app):
BEGIN;
CREATE TABLE "polls_poll" (
    "id" serial NOT NULL PRIMARY KEY,
    "question" varchar(200) NOT NULL,
    "pub_date" timestamp with time zone NOT NULL
);
CREATE TABLE "polls_choice" (
    "id" serial NOT NULL PRIMARY KEY,
    "poll_id" integer NOT NULL REFERENCES "polls_poll" ("id"),
    "choice" varchar(200) NOT NULL,
    "votes" integer NOT NULL
);
COMMIT;


If you're interested, 
also run the following commands:
Looking at the output of those commands can help you understand what's actually happening under the hood.
Now, run syncdb again to create those model tables in your database:
python manage.py syncdb
The syncdb command runs the sql from 'sqlall' on your database for all apps in INSTALLED_APPS that don't already exist in your database. This creates all the tables, initial data and indexes for any apps you have added to your project since the last time you ran syncdb. syncdb can be called as often as you like, and it will only ever create the tables that don't exist.
Read the django-admin.py documentation for full information on what the manage.py utility can do.

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 mysite on sys.path. For flexibility, several pieces of Django refer to projects in Python dotted-path notation (e.g. 'mysite.polls.models'). In order for this to work, the mysite 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.
    nce you're in the shell, explore the database API:
    >>> from mysite.polls.models import Poll, Choice  
    # Import the model classes we just wrote.
    
    # No polls are in the system yet.
    >>> Poll.objects.all()
    []
    
    # Create a new Poll.
    >>> import datetime
    >>> p=Poll(question="What's up?",pub_date=datetime.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(2007, 7, 15, 12, 00, 53)
    
    # Change values by changing the attributes, then calling save().
    >>> p.pub_date = datetime.datetime(2007, 4, 1, 0, 0)
    >>> p.save()
    
    # objects.all() displays all the polls in the database.
    >>> Poll.objects.all()
    [<Poll: Poll object>]
Wait a minute. <Poll: Poll object> is, utterly, an unhelpful representation of this object. Let’s fix that by editing the polls model (in the polls/models.py file) and adding a __str__() method to both Poll and Choice:
class Poll(models.Model):
    # ...
    def __str__(self):
        return self.question

class Choice(models.Model):
    # ...
    def __str__(self):
        return self.choice

It’s important to add __str__() 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:
import datetime
# ...
class Poll(models.Model):
    # ...
    def was_published_today(self):
        return self.pub_date.date() == datetime.date.today()

Note the addition of import datetime to reference Python’s standard datetime module.
Let’s jump back into the Python interactive shell by running python manage.py shell again:
>>> from mysite.polls.models import Poll, Choice

# Make sure our __str__() addition worked.
>>> Poll.objects.all()
[<Poll: What's up?>]

# Django provides a rich database lookup API that's entirely driven by
# keyword arguments.
>>> Poll.objects.filter(id=1)
[<Poll: What's up?>]
>>> Poll.objects.filter(question__startswith='What')
[<Poll: What's up?>]

# Get the poll whose year is 2005. Of course, if you're 
going through this
# tutorial in another year, change as appropriate.
>>> Poll.objects.get(pub_date__year=2005)
<Poll: What's up?>

>>> Poll.objects.get(id=2)
Traceback (most recent call last):
    ...
DoesNotExist: Poll matching query does not exist.

# 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 Poll.objects.get(id=1).
>>> Poll.objects.get(pk=1)
<Poll: What's up?>

# Make sure our custom method worked.
>>> p = Poll.objects.get(pk=1)
>>> p.was_published_today()
False

# Give the Poll a couple of Choices. The create call constructs a new
# choice object, does the INSERT statement, adds the choice to the set
# of available choices and returns the new Choice object.
>>> p = Poll.objects.get(pk=1)
>>> p.choice_set.create(choice='Not much', votes=0)
<Choice: Not much>
>>> p.choice_set.create(choice='The sky', votes=0)
<Choice: The sky>
>>> c = p.choice_set.create(choice='Just hacking again', votes=0)

# Choice objects have API access to their related Poll objects.
>>> c.poll
<Poll: What's up?>

# And vice versa: Poll objects get access to Choice objects.
>>> p.choice_set.all()
[<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]
>>> p.choice_set.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.
>>> Choice.objects.filter(poll__pub_date__year=2005)
[<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]

# Let's delete one of the choices. Use delete() for that.
>>> c = p.choice_set.filter(choice_startswith='Just hacking')
>>> c.delete()
 

No comments:

Post a Comment