Monday, March 29, 2010

Roll Your Own Template Tags

Django URLs and Regular Expressions

So I'm at this point where I need to start figuring out how admins and users interact with activity and commitment objects. These two objects are things that users can participate in for points in the competition. The main difference between the two is that a user needs to request the points for an activity for an admin, while points are awarded for commitments after some period of time (like a week). Since these two objects (actually 3, but I haven't gotten around to the third item yet) are fairly similar, I use the item type as a parameter to some of my Django URLs.

from django.conf.urls.defaults import *

urlpatterns = patterns('',
url(r'^add_(?P<item_type>activity|commitment)/(?P<item_id>\d+)/$',
'activities.views.add_participation', name='add_participation'),
url(r'^remove_(?P<item_type>activity|commitment)/(?P<item_id>\d+)/$',
'activities.views.remove_participation', name='remove_participation'),
url(r'^request_(?P<item_type>activity|commitment)_points/(?P<item_id>\d+)/$',
'activities.views.request_points', name='request_points'),
)

So my plan has been to have one method that takes the item type as a parameter and then decide what to do after that. The use of regular expressions in the URLs is something I've found to be very powerful in Django and I think it helps keep my code DRY.

Creating Template Tags

I found the need to do similar things with my templates. Commitments and activities have add/remove links, but only activities have the request points link (In the url patterns above, commitments will work. That will probably be removed for the next release). So what I would like is a simple template that creates the appropriate links. However, I didn't see a way to include a parameter to an include tag. After doing a bit of digging, I figured that the only way to accomplish what I want was to create my own template tags.

from django import template
from django.core.exceptions import ObjectDoesNotExist
from django.contrib.auth.models import User

from activities.models import Activity, Commitment, ActivityMember, CommitmentMember

register = template.Library()

def render_user_tools(user, item):
"""Renders the form used to add/remove activities and to request points."""

if not isinstance(user, User):
try:
user = User.objects.get(username=user)
except User.DoesNotExist():
return ""

if isinstance(item, Commitment):
return __generate_commitment_form(user, item)
elif isinstance(item, Activity):
return __generate_activity_form(user, item)
else:
return "";

register.simple_tag(render_user_tools)

# Private methods for constructing the form.
def __generate_commitment_form(user, item):
# Check that the user is involved with this item.
return_string = ""
try:
# Exception thrown if user cannot be found.
item_join = CommitmentMember.objects.get(user=user, commitment=item)

return_string += '<form action="/activities/remove_{0}/{1.id}'
return_string += '/" method="post" style="display:inline"><a href="#"'
return_string += 'onclick="parentNode.submit()">Remove</a></form>'

except ObjectDoesNotExist:
return_string += '<form action="/activities/add_{0}/{1.id}'
return_string += '/" method="post" style="display:inline">'
return_string += '<a href="#" onclick="parentNode.submit()">Add</a></form>'

# return_string is a format string with places to insert the item type and item.
return return_string.format("commitment", item)

def __generate_activity_form(user, item):
# Check that the user is involved with this item.
return_string = ""
try:
# Exception thrown if user cannot be found.
item_join = ActivityMember.objects.get(user=user, activity=item)
if item_join.approval_status == u"unapproved":
return_string += '<form action="/activities/request_{0}_points/{1.id}'
return_string += '/" method="post" style="display:inline"><a href="#"'
return_string += 'onclick="parentNode.submit()">Request Points</a></form>&nbsp'
elif item_join.approval_status == u"pending":
return_string += "<span class=\"pending_activity\">Pending approval</span>&nbsp"

return_string += '<form action="/activities/remove_{0}/{1.id}'
return_string += '/" method="post" style="display:inline"><a href="#"'
return_string += 'onclick="parentNode.submit()">Remove</a></form>'

except ObjectDoesNotExist:
return_string += '<form action="/activities/add_{0}/{1.id}'
return_string += '/" method="post" style="display:inline">'
return_string += '<a href="#" onclick="parentNode.submit()">Add</a></form>'

# return_string is a format string with places to insert the item type and item.
return return_string.format("activity", item)

It's yet another interesting feature of Django that will also prevent me from duplicating code (although the strings in the two private methods are fairly similar).

No comments:

Post a Comment