Pinax is a collection of reusable django apps that brings together features that are common to many websites. It allows developers to focus on what makes their site unique. Here is an example of adding your own functionality to Pinax. It will also be an example of writing a reusable app since every individual app currently in Pinax can be used separately. Also, I’ve bundled the example files into a google code project.

My example will be to create a list of books and allow them to be tied to any object using Django’s ContentType framework. The books could be recommended reading for the members of a tribe (pinax group), a class, or anything in your project and will include title, description, and tags (requires django-tagging). In another post I’ve shown how to create template tags to make it easy to show the list of books and a form to add a book. Obviously, there is a lot more that could be done with this app, but I will leave it out of the example to keep it simple.

Starting the App

Create a folder in the apps directory or any place that is on the python path (ex. /path/to/pinax/projects/complete_project/apps/books/) and include these files:

  • __init__.py even though it might be empty, it is required
  • forms.py
  • models.py
  • urls.py
  • views.py

models.py

I will start with creating the model for the project. Below is all of the code I am placing in the file. I’ve added a lot of comments to explain everything that is happening.

#import all of the things we will be using
from django.db                          import models
from tagging.fields                     import TagField
# to help with translation of field names
from django.utils.translation  import ugettext_lazy as _
# to have a generic foreign key for any model
from django.contrib.contenttypes        import generic
# stores model info so this can be applied to any model
from django.contrib.contenttypes.models import ContentType
 
class Book(models.Model):
    """
    The details of a Book
    """
    # fields that describe this book
    name        = models.CharField(_('name'), max_length=48)
    description = models.TextField(_('description'))
 
    # to add to any model
    content_type   = models.ForeignKey(ContentType)
    object_id      = models.PositiveIntegerField()
    content_object = generic.GenericForeignKey('content_type',
        'object_id')
 
    # for the list of tags for this book
    tags        = TagField()
 
    # misc fields
    deleted     = models.BooleanField(default=0)
    created     = models.DateTimeField(auto_now_add=True)
    # so that {{book.get_absolute_url}} outputs the whole url
    @models.permalink
    def get_absolute_url(self):
        return ("book_details", [self.pk])
    # outputs name when printing this object as a string
    def __unicode__(self):
        return self.name

forms.py

Use Django’s ModelForm to create a form for our book model.

from django import forms
from books.models import Book
 
class NewBookForm(forms.ModelForm):
    class Meta:
        model = Book
        exclude = ('deleted', 'content_type',
            'object_id', 'created')

views.py

In this file we create a view to show the details of a book and a view to create a new book for an object.

from django.shortcuts import render_to_response
from django.shortcuts import get_object_or_404
from django.http import HttpResponseRedirect
from django.template import RequestContext
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.decorators import login_required
 
from tribes.models import Tribe
from books.models import Book
from django.contrib.contenttypes.models import ContentType
 
@login_required
def new(request, content_type_id, object_id,
            template_name="books/new.html"):
    """
    creates a new book
    """
    from books.forms import NewBookForm
 
    # if a new book was posted
    if request.method == 'POST':
        book_form = NewBookForm(request.POST)
        if book_form.is_valid():
            # create it
            book = book_form.save(commit=False)
            content_type        = \
                ContentType.objects.get(id=content_type_id)
            content_object      = \
                content_type.get_object_for_this_type(
                id=object_id)
            book.content_object = content_object
            book.save()
            request.user.message_set.create(
                message=
                _("Successfully created book '%s'")
                % book.name)
            # send to object page or book page
            try:
                return HttpResponseRedirect(
                    content_object.get_absolute_url()
                )
            except:
                return HttpResponseRedirect(reverse(
                    'book_details', args=(book.id,)))
        # if invalid, it gets displayed below
    else:
        book_form = NewBookForm()
 
    return render_to_response(template_name, {
        'book_form': book_form,
    }, context_instance=RequestContext(request))
 
@login_required
def details(request, book_id,
    template_name="books/details.html"):
    """
    displays details of a book
    """
    book = get_object_or_404(Book, id=book_id)
    return render_to_response(template_name, {
        'book': book,
    }, context_instance=RequestContext(request))

urls.py

To tie our views to some urls, add this to the urls.py file.

from django.conf.urls.defaults import *
from django.conf.urls.defaults import *
 
urlpatterns = patterns('',    
    # new book for object
    url(r'^new/(?P<content_type_id>\d+)/(?P<object_id>\d+)', 
        'books.views.new', name="new_book"),
    # display details of a book
    url(r'^details/(?P<book_id>\d+)$', 'books.views.details', 
        name="book_details"),
)

More Features

The rest of the application is described in the post titled: How to Write Django Template Tags. You can also check out all of the code from the google project by doing the following command:

svn co http://django-books.googlecode.com/svn/trunk books

in a directory on the python path.

I use webfaction to host a lot of my django projects. It has an easy setup that will get you developing quickly and a great community of talented programmers. There is also a quick setup for rails, wordpress, and a lot more.

Related posts:

  1. How to Display Realtime Traffic Analytics  Users of Presskit’n have been asking for traffic statistics on their press releases so I decided I would get them...
  2. How to Add Locations to Python Path for Reusable Django Apps  In my previous post I talk about reusable apps, but I don’t really explain it that much. If you have...
  3. How to Write Django Template Tags  Template tags can be useful for making your applications more reusable by other projects. For this example I will be...