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. This post will describe an example of adding your own functionality to Pinax. It will also serve as an example of writing a reusable app since every individual app currently in Pinax can be used separately. For your convenience, I’ve bundled the example files into a Google Code project.
In this example, I will 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. Books will include a title, description, and tags (requires django-tagging). In another post I’ve shown how to create template tags, which make it easy to show the list of books and a form to add to the list. There is a lot more that could be done with this app, but I’m going to keep this example 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', 'tribes', 'created')
views.py
In this file we create a view to show information about 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 the following 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 Code project by executing the following command in a directory on the Python path:
svn co http://django-books.googlecode.com/svn/trunk books