Intro to Django — Part 3

Django 1.3 edition

Steven F. Lott

slott56@gmail.com

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

Topics

Code Kata

Review: Model, View

Presentation

Admin Pages (Batteries Included)

Admin Customization

Access and Authorization

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...

Presentation

Three parts:

Part IV. Stay Tuned.

Other Stuff

Your .CSS, .JS and .PNG's are just static files.

They're referenced in your HTML.

They reside on your Apache server.

They're downloaded by Apache outside Django.

The STATIC_URL setting must match your Apache configuration.

Admin Pages

Batteries Included

Basic CRUD (Create, Retrieve, Update, Delete) pages are available.

By default.

No code.

Just enable them.

Enabling the Admin Interface

  1. Uncomment a setting in settings.py to include the app. Do a ./manage.py syncdb to upgrade the database.
  2. Create an admin user.
  3. Create admin.py modules to define and register ModelAdmin subclasses. Customize the forms, admin, templates, &c.
  4. Uncomment the admin path in urls.py.

1. Admin Settings

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',
    'tutorial.inventory',
)

2. Admin Authentication

./manage.py createsuperuser

Need an "admin" user for access to admin pages.

You can set the is_staff property to enable admin for users.

This is very coarse-grained. We'll look at fine-tuning it.

3. admin.py Bindings

from django.contrib import admin
from tutorial.inventory.models import Ingredient, Location, OnHand

class IngredientAdmin(admin.ModelAdmin):
    # Customizations here.
    pass
admin.site.register(Ingredient, IngredientAdmin)

An admin.py module for each app.

Register relevant classes from the app's models.py.

4. urls.py Admin Path

# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
    # Uncomment the next line to enable the admin:
    url(r'^admin/', include(admin.site.urls)),
)

Autodiscover searches each app for its admin.py module.

URL Path is /admin/app/model. You can change "admin".

What It Looks Like

  1. Run the demo server.

    manage.py runserver
    
  2. Browse the /admin path.

Done.

Admin Customization

Start Here: http://docs.djangoproject.com/en/1.3/ref/contrib/admin/

The admin UI is highly customizable.

Highly.

Customize admin first. Write code reluctantly.

Start with essentials.

Common Customizations

Data Validation for user input.

Calculation and Derived Data for output.

List-Detail Views.

Customizing Look and Feel using Templates.

Multiple Admin Sites for role-specific admin.

Data Validation

Essential. Part of the problem domain.
The model has data types, and sizes, choices, relationships, cardinality, derived data (properties), uniqueness, etc.
Application. Part of this solution.

Define a Form and a ModelAdmin to add checking.

  • ranges of data
  • input format
  • optionality
  • state specific rules

forms.py

from django.forms import ModelForm
from django.core.exceptions import ValidationError
from tutorial.inventory.models import OnHand

class OnHandForm(ModelForm):
    class Meta:
        model = OnHand
    def clean_count(self):
        count= self.cleaned_data['count']
        if count < 0.0:
            raise ValidationError( "Positive or Zero counts" )
        return count

Define the ModelAdmin

from tutorial.inventory.forms import OnHandForm
class OnHandAdmin(admin.ModelAdmin):
    form= OnHandForm
admin.site.register(OnHand, OnHandAdmin)

All Validation is via Forms

Even data you're bulk-loading by reading a file.

  1. Create a Form instance from the raw data.
  2. If valid, use the Form's save() method to create the Model object.

100% consistency between web and non-web applications.

What It Looks Like

  1. Run the demo server.

    manage.py runserver
    
  2. Browse the /admin path.

Done.

Calculation and Derived Data

Define as much of this in the model as possible.

Sometimes "calculation" or "business rules" outside the model.

View functions focus on HTTP request-response.

Command-line applications will use Models and Forms and application modules.

Derived Data Example

class Shopping( models.Model ):
    ingredient = models.ForeignKey( Ingredient )
    count = models.FloatField( )
    price = models.DecimalField( max_digits=5, decimal_places=2)
    def __unicode__( self ):
        return u"{0.count!f} {0.ingredient!s}".format( self )
    @property
    def budget( self ):
        return self.count * self.price

recipe_check.py Module

from inventory.models import Recipe, Ingredient, OnHand

def recipe_check( recipe ):
    for needed in recipe.ingredient_set.all():
        if needed.ingredient.onhand.count < needed.count:
            s= Shopping( ingredient=needed.ingredient,
                count= needed.count-needed.ingredient.onhand.count
                )
            s.save()
    return Shopping.objects.all()

List-Detail Views

80% of the web is a list view (with filters and search) with links to detail views.

This is the way Django admin works.

And, there are generic list-detail view functions that you can use.

Write Code Reluctantly.

Part IV. Stay Tuned.

Customizing Look and Feel

Just template changes.

You can provide templates to the admin site objects.

Part IV.

Multiple Admin Sites

An admin.py module defines a suite of admin pages.

Bound to specific models. Using specific forms.

And any other customizations available in the ModelAdmin class.

The urls.py binds the admin to a top-level path (/admin/...).

Extending the admin.py Module

from django.contrib.admin.sites import AdminSite
from inventory.models import Ingredient, Location, OnHand
from inventory.admin import IngredientAdmin, LocationAdmin, OnHandAdmin

galley_admin= AdminSite( name='galley' )
galley_admin.register( Ingredient, IngredientAdmin )
galley_admin.register( OnHand, OnHandAdmin )

Using an admin.py Module

Update your urls.py:

from inventory.admin import galley_admin
from django.contrib import admin

urlpatterns = patterns('',
    (r'^admin/', include(admin.site.urls)), # Default
    (r'^galley/', include(galley_admin.urls)), # Customized
    #(r'^engineering/', include(location_admin.urls)),
)

Access and Authorization

The default behavior is that "staff" have access to the admin pages.

Most of the time, you have at least four distinct kinds of admin users.

Superuser

Step 1. Enable the auth application to include the necessary User and Group models.

Step 2. Run ./manage.py createsuperuser

Step 3. Enjoy.

If you drop your database and run ./manage.py syncdb this will ask if you want to create a superuser.

Security

A customized Admin site with just User and Profile tables and nothing more.

Not even group definition.

Data Admin(s)

End users with more privileges than other end users.

Various kinds of roles and responsibilities.

You can give them a customized admin site that contains just what they need.

Access Controls

  1. Group Permissions. Will the view function even work?

    Automatically supported by Django's admin.

  2. Navigation. Can the user see the link to it?

    Part IV.

Group Permission

  1. Each Model has permissions for add, change and delete.

  2. A Group has permissions. It's wise to base this on user roles.

  3. A User has personal permissions (avoid these).

    A User has Group permissions (use these).

The Django admin checks each request against User's permissions.

Define Group permissions based on user roles. Assign users to groups.

That was easy.

Conclusion

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