How to Write Django Template Tags
posted on January 22nd, 2009 by Greg Allard in Greg's Posts on Code SpatterTemplate tags can be useful for making your applications more reusable by other projects. For this example I will be adding to the
books project that I started in a previous post. Also, I’ve bundled the
example files into a google code project.
Start off by creating a folder called templatetags in your app directory and create two files in it. The first one named __init__.py and the second book_tags.py. There’s 3 things that we need to accomplish with our template tags. The first is to create a tag that will output the url for the action of the form. For example, {% get_book_form_url foo_object %}Next we need to get the form and assign it to a template variable that can be specified by the template variable. For example, {% book_form as bar_var %}. And the third template tag will get the books for an object and place in a template variable. For example, {% books_for_object foo_object as bar_var %}.
from django.template import Library, Node, TemplateSyntaxError from django.template import Variable, resolve_variable from django.utils.translation import ugettext as _ from django.contrib.contenttypes.models import ContentType from django.core.urlresolvers import reverse from books.models import Book register = Library() def get_contenttype_kwargs(content_object): """ Gets the basic kwargs necessary for form submission url """ kwargs = {'content_type_id': ContentType.objects.get_for_model(content_object).id, 'object_id': getattr(content_object, 'pk', getattr(content_object, 'id')), } return kwargs def get_book_form_url(content_object): """ prints url for form action """ kwargs = get_contenttype_kwargs(content_object) return reverse('new_book', kwargs=kwargs) class BooksForObjectsNode(Node): """ Get the books and add to the context """ def __init__(self, obj, context_var): self.obj = Variable(obj) self.context_var = context_var def render(self, context): content_type = ContentType.objects.get_for_model( self.obj.resolve(context)) # create the template var by adding to context context[self.context_var] = \ Book.objects.filter( # find all books for object content_type__pk = content_type.id, object_id = self.obj.resolve(context).id ) return '' def books_for_object(parser, token): """ Retrieves a list of books for given object {% books_for_object foo_object as book_list %} """ try: bits = token.split_contents() except ValueError: raise TemplateSyntaxError( _('tag requires exactly two arguments') if len(bits) != 4: raise TemplateSyntaxError( _('tag requires exactly three arguments') if bits[2] != 'as': raise TemplateSyntaxError( _("second argument to tag must be 'as'") return BooksForObjectsNode(bits[1], bits[3]) def book_form(parser, token): """ Adds a form to the context as given variable {% book_form as form %} """ # take steps to ensure template var was formatted properly try: bits = token.split_contents() except ValueError: raise TemplateSyntaxError( _('tag requires exactly two arguments') if bits[1] != 'as': raise TemplateSyntaxError( _("second argument to tag must be 'as'") if len(bits) != 3: raise TemplateSyntaxError( _('tag requires exactly two arguments') # get the form return BookFormNode(bits[2]) class BookFormNode(Node): """ Get the form and add it to the context """ def __init__(self, context_name): self.context_name = context_name def render(self, context): from books.forms import NewBookForm form = NewBookForm() # create the template var by adding to context context[self.context_name] = form return '' # register these tags for use in template files register.tag('books_for_object', books_for_object) register.tag('book_form', book_form) register.simple_tag(get_book_form_url)
Add this to your template
To start adding books to an object, add this code to your template and change my_awesome_object_here to the template variable name of your object.
<h2>Books</h2> {% load book_tags %} {% books_for_object my_awesome_object_here as books %} {% for book in books %} <a href="{{ book.get_absolute_url }}">{{ book }}</a> - {{ book.description }} {% endfor %} <h2>Add a book</h2> <form action="{% get_book_form_url my_awesome_object_here %}" method="post"> {% book_form as form %} {{ form }} <input type="submit" value="Go" /> </form>
You can get the template tags source code and the code from
the previous post at the
google code project page or by doing
svn co http://django-books.googlecode.com/svn/trunk books
in a directory on the python path.
Related posts:
- How to Write Reusable Apps for Pinax and Django Pinax is a collection of reusable django apps that…
- Django RequestContext Example Browsing other peoples’ code is a great way to learn…
- Quick Thumbnails in Django I normally like to write code myself instead of installing…
How to Write Reusable Apps for Pinax and Django
posted on January 15th, 2009 by Greg Allard in Greg's Posts on Code Spatter
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.
Related posts:
- How to Write Django Template Tags Template tags can be useful for making your applications more…
- How to Add Locations to Python Path for Reusable Django Apps In my previous post I talk about reusable apps, but…
- How to Display Realtime Traffic Analytics Users of Presskit’n have been asking for traffic statistics on…
Django RequestContext Example
posted on December 22nd, 2008 by Greg Allard in Greg's Posts on Code SpatterBrowsing other peoples’ code is a great way to learn new things about a language or framework. I never made it to the Django docs about Contexts, but the
Pinax developers apparently did and I got a chance to learn this from them. This is a few sections of their code and how they use RequestContext in their apps.
If you are looking at the source of some of their views you might see how they are using it. Here is what it looks like in friends_app.friends
return render_to_response(template_name, { "join_request_form": join_request_form, "invites_received": invites_received, "invites_sent": invites_sent, "joins_sent": joins_sent, }, context_instance=RequestContext(request))
So what extactly does context_instance=RequestContext(request) do? I took a look at
the django documentation to find out more. And that led me to check the settings file and I found that there were quite a few things listed in TEMPLATE_CONTEXT_PROCESSORS.
TEMPLATE_CONTEXT_PROCESSORS = ( "django.core.context_processors.auth", "django.core.context_processors.debug", "django.core.context_processors.i18n", "django.core.context_processors.media", "django.core.context_processors.request", "notification.context_processors.notification", "announcements.context_processors.site_wide_announcements", "account.context_processors.openid", "account.context_processors.account", "misc.context_processors.contact_email", "misc.context_processors.site_name", "messages.context_processors.inbox", "friends_app.context_processors.invitations", "misc.context_processors.combined_inbox_count", )
I opened up friends_app.context_processors to see a bit more and it looked like this
from friends.models import FriendshipInvitation def invitations(request): if request.user.is_authenticated(): return { 'invitations_count': FriendshipInvitation.objects.filter( to_user=request.user, status="2").count(), } else: return {}
This means that every view that has context_instance=RequestContext(request) in it will call the above function since it is listed in settings.py and it will provide the template variable, {{ invitations_count }}.
Using RequestContext makes it easy to have the common template variables available on every page and I will have to start using it more in my apps. So make sure you have from django.shortcuts import render_to_response and from django.template import RequestContext in your file and add your context processor to the settings file and you should be ready to add template vars to your contexts.
Related posts:
-
How to Write Reusable Apps for Pinax and Django
Pinax is - How to Write Django Template Tags Template t
- Quick Thumbnails in Django I normally