Intro to Django — Part 2

Django 1.3 edition

Steven F. Lott

slott56@gmail.com

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

Topics

Code Kata

Review Model

View, Dynamic Content

Next Parts: Presentation, Admin Pages, etc.

Code Kata

Simple Inventory of ingredients, recipes and locations.

What?

Use Case

Model

model.png

Model Code

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 )

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 )

View

Several standard pieces to a handling a request.

Common Aspects:

All those interlocking pieces and parts... we'll do this in steps.

urls(request) → view function

Much fancy URL parsing available to locate the view function.

A urls.py module creates a well-known global variable.

Any code can create the urlpatterns variable. Keep it simple.

Should be structured.

Top urls.py

Can enable the built-in admin. We'll get to that.

For now we'll add two parsing rules.

url(r'^$', 'tutorial.inventory.views.home', name='home'),
url(r'^inventory/', include('tutorial.inventory.urls') ),

The empty path (r'^$') will use the home function in views module.

The r'^inventory/' includes another urls module.

Inventory urls.py

from django.conf.urls.defaults import patterns, include, url

urlpatterns = patterns('tutorial.inventory.views',
    url(r'^$', 'home' ),
    #url(r'^ingredient/$', 'ingredient' ),
    #url(r'^location/$', 'location' ),
    #url(r'^onhand/$', 'on_hand' ),
)

Top-level used include(); Using parsing already done in parent.

Cool URIs don't change

view( request ) → response

The Django ORM to get data.

We have all of Python to process data.

HTTP Response object

Response

Inventory views.py

from django.http import HttpResponse

def home( request ):
    return HttpResponse( "Hello from {0}".format(
        request.META['PATH_INFO']),
    content_type='text/plain' )

Always write something like this first just to help debug your Apache and mod_wsgi configuration.

Demonstrate

Handy developer server.

./manage.py runserver

Runs a Django service on http://127.0.0.1:8000/

HTTP Request → url pattern match → view function → HTTP Response.

Confusion

Some folks get to the ./manage.py runserver command and think they're done.

False.

You need Apache (and a cache of some kind) to handle real requests.

Why? User's Browser is slow.

Handling the (1) download drag and (2) secure port 80 is Apache's job.

Unit Testing

Django Unit Test framework has a special self.client.

This can be used to send GET and POST requests to the web server.

Numerous assertions available to check responses

Tip

Provide good CSS class and ID to help unit testing.

<span class="query">Lime</span>

Can make unit testing much simpler.

Testing a View Function: tests.py

from django.test import TestCase
from tutorial.inventory.models import Ingredient, Location, OnHand

class Test_Ingredient_View( TestCase ):
    fixtures = [ "sample_data.json" ]
    def test_should_get_all( self ):
        response= self.client.get( "/inventory/ingredient/" )
        self.assertContains( response, "<td>lime</td>", status_code=200, )
        self.assertTemplateUsed( response, "ingredients.html" )
    def test_should_get_specific( self ):
        response= self.client.get( "/inventory/ingredient/lime/" )
        self.assertContains( response, "<span class="query">Lime</span>",
            status_code=200, )
        self.assertContains( response, "<td>lime</td>", )
        self.assertTemplateUsed( response, "ingredients.html" )

TDD Sidebar

Tests are so easy to write.

Write them first.

The /inventory/ingredient/name/ is going to break when someone uses "rum/dark" and "rum/light" as ingredients.

We should

Dynamic Content

Adding features to parse the URL.

  1. More rules in urls.py
  2. More functions in views.py
  3. Also, this may lead to additional templates

Dynamic urls.py

from django.conf.urls.defaults import patterns, include, url

urlpatterns = patterns('tutorial.inventory.views',
    url(r'^$', 'home' ),
    url(r'^ingredient/(?P<name>.*?)/$', 'ingredient' ),
    url(r'^ingredient/$', 'ingredient' ),
    #url(r'^location/$', 'location' ),
    #url(r'^onhand/$', 'on_hand' ),
)

Collect part of the path in name; provide it to the view function.

Dynamic views.py

from django.shortcuts import render
from tutorial.inventory.models import Ingredient

def ingredient( request, name=None ):
    if name:
        objects = Ingredient.objects.filter(
            name__icontains=name )
    else:
        objects = Ingredient.objects.all()
    return render( request, "ingredients.html",
        {'objects':objects, 'name':name}
        )

Filter

The objects attribute of a Model class has a number of functions.

And there's a LOT more capability.

GET and POST requests

The request.GET has the parsed data from after the ?.

The request.POST has parsed parameters in the content.

The request.FILES has file-like objects for any attached files.

Plus the user, cookies, and session information.

Presentation

Three parts:

Conclusion

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