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.