Intro to Django — Part 1

Django 1.3 edition

Steven F. Lott

slott56@gmail.com

http://slott-softwarearchitect.blogspot.com/

Topics

Install, Philosophy, Code Kata

Basic Model, Database Implementation, Navigation

Next Parts: View, Presentation, and more

Conclusion

Install

Assume Python 2.6

sudo easy_install-2.6 django

That was fun.

Check your PATH environment variable to be sure you have access to the Django script: django-admin.py.

Bigger Install

For scalability and security, you'll usually do this.

Philosophy

Plus handy middleware and settings

Principles

"Convention over Configuration"

"Don't Repeat Yourself"

What About?

Javascript and CSS?

Static media?

Code Kata

Simple Inventory of ingredients, recipes and locations.

What?

Use Case

Model

model.png

Getting Started

  1. Create the "Site".
  2. Create an "App".
  3. Configure the settings.
  4. Define model and tests.
  5. Sync the DB.

What's a "site"?

For simple applications it could be the entire Apache server.

Or. Multple sites on a single server (virtual host configuration).

Or. Site spread across multiple servers (highly scalable).

This is all Apache and mod_wsgi configuration.

What's an "app"?

The Django unit of reuse.

App = Model + View is the minimum.

Separate site-specific templates.

App = Model + View + Template is typical.

App = Model + Forms + Admin + View + Template + Tests

All neatly separated to permit reuse and customization

Let's start a project

Handy getting started utility.

django-admin.py startproject tutorial

Creates a tutorial directory with some boilerplate files.

Add an app to a project

Often, we'll have many resuable apps within a larger web site.

cd tutorial
./manage.py startapp inventory

Creates the inventory directory with some boilerplate files.

Settings

Many, many settings. For now, we'll look at a few

Model Code 1

http://docs.djangoproject.com/en/1.3/ref/models/fields/

from django.db import models

class Ingredient( models.Model ):
    name = models.CharField( max_length=64 )
    safety = models.CharField( max_length=64 )

Class with attributes.

Model Code 2

UNITS = ( ("ea.","Each"), ("lb.","Pounds"), ("qt.","Quarts") )

class OnHand( models.Model ):
    ingredient = models.ForeignKey( Ingredient )
    count = models.FloatField( )
    units = models.CharField( max_length=64,
        choices=UNITS )

Class with foreign key reference to another class.

Also, use of choices to populate drop-downs in admin forms.

Build the DB

./manage.py syncdb

And...

That's it

Play Time

./manage.py syncdb
./manage.py shell

Now you have interactive acccess to DB via Django ORM.

Example

>>> from inventory.models import Ingredient
>>> Ingredient.objects.all()
[]
>>> Ingredient.objects.create( name="lime" )
<Ingredient: Ingredient object>
>>> Ingredient.objects.create( name="coconut" )
<Ingredient: Ingredient object>
>>> Ingredient.objects.all()
[<Ingredient: Ingredient object>, <Ingredient: Ingredient object>]

Hmm... <Ingredient: Ingredient object> Ugly.

Fix the Model

Add __unicode__ methods to make model instances show something useful

class Ingredient( models.Model ):
    name = models.CharField( max_length=64 )
    perishable = models.BooleanField()
    def __unicode__( self ):
        return self.name

How's This?

>>> from inventory.models import Ingredient
>>> Ingredient.objects.all()
[<Ingredient: lime>, <Ingredient: coconut>]
>>> [ i.name for i in Ingredient.objects.all() ]
[u'lime', u'coconut']

We can live with that.

Test the Model

from django.utils import unittest
class TestOnHand( unittest.TestCase ):
    def setUp( self ):
        self.gin, _ = Ingredient.objects.get_or_create( name="gin", perishable=False )
        self.ginger_ale, _ = Ingredient.objects.get_or_create( name="ginger ale", perishable=False )
        self.lime, _ = Ingredient.objects.get_or_create( name="lime", perishable=True )
        self.cooler, _ = Location.objects.get_or_create( name="galley cooler", side="port" )
        self.bar, _ = Location.objects.get_or_create( name="saloon", side="starboard" )
        OnHand.objects.get_or_create( ingredient=self.gin, location=self.bar, count=.75, units="l" )
        OnHand.objects.get_or_create( ingredient=self.ginger_ale, location=self.cooler, count=6, units="ea." )
        OnHand.objects.get_or_create( ingredient=self.lime, location=self.cooler, count=2, units="ea." )
    def test_should_inventory( self ):
        self.assertEqual( 3, OnHand.objects.count() )

Unittest

./manage.py test inventory
Creating test database for alias 'default'...
....
----------------------------------------------------------------------
Ran 4 tests in 0.039s

OK
Destroying test database for alias 'default'...

Database Implementation

The ORM does many, many things for us.

Database Migration

The ORM cannot, however, handle schema migration.

When you make a schema change, Django can't (and won't) modify an existing table.

How It Works

Django ORM added a bunch of navigation methods to our classes.

OnHand had ingredient which will fetch from the DB.

Ingredient, it turns out, has an attribute onhand_set.

>>> from tutorial.inventory.models import Ingredient
>>> Ingredient.objects.get( name='lime' )
<Ingredient: lime>
>>> lime= _
>>> lime.onhand_set.all()
[<OnHand: 3.0 ea. lime port Cooler>]

How Much Code I Wrote

Zero.

What To Do

  1. Populate objects manually.
  2. Play interactively.
  3. Use manage.py dumpdata to preserve test fixtures.
  1. Write formal unit tests.

View

Several pieces to view functions.

All those interlocking pieces and parts

Presentation

Several pieces to presentation.

Next Parts

View

Presentation, Dynamic Content

Admin Pages (Batteries Included)

Conclusion

We're not even close to done with the Django feature set.