I’ve been developing a django website and while doing so learning some of the neat tricks that the framework has. One thing that I’ve found and love is making custom tags in the templating system and I decided to make a few html helpers similar to Code Igniter.
(I use this for a reference)
so after creating my django project which because I’m using virtualenv so in the environment:
python2.7 bin/django-admin.py startproject budgetproject
Then I started an app called util by running:
python2.7 ../manage.py startapp util
which is where I will be putting my helpers and of course enable that in the settings which do to my directory structure looks like this (budgetproject/settings.py):
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.admin',
'django.contrib.admindocs',
'budgetproject.app.homepage',
'budgetproject.app.accounts',
'budgetproject.util',
)
Awesome we’re set and ready to go with a directory structure of:
budgetproject
util
__init__.py
models.py
tests.py
views.py
__init__.py
templates
settings.py
urls.py
wsgi.py
Alright got the basics out of the way lets get to what we came for and thats to create a template tag. First we need to create the templatetags directory and create and empty file __init__.py to essentially make an empty python package:
budgetproject
util
templatetags
__init__.py
__init__.py
models.py
tests.py
views.py
...
now lets create our file with our tags I called mine html.py and in it I put:
from django import template
import os
TEMPLATE_DIR = 'html'
register = template.Library()
@register.inclusion_tag(os.path.join(TEMPLATE_DIR,'form_open.html'))
def form_open(action,method='post'):
return {'action': action,'method':method}
@register.inclusion_tag(os.path.join(TEMPLATE_DIR,'form_close.html'))
def form_close():
return {}
Now lets go over what each section means.
from django import template import os TEMPLATE_DIR = 'html' register = template.Library()
Every filter or template tag must have include template and have a variable called register that is a template.Library() type. I’m also using the os module to have relative paths and keep compatibility for different operating systems.
@register.inclusion_tag(os.path.join(TEMPLATE_DIR,'form_open.html'))
def form_open(action,method='post'):
return {'action': action,'method':method}
The register.inclusion_tag decorator is a simple way to create an inclusion tag which basically allows you to “display some data by rendering another template” so that is why you pass the decorator and the function it decorates returns a dictionary of the data your are going to display. In this case were displaying an opening form tag so we need an action and method among other things but those are good enough for now. The reason I did an inclusion tag here instead of a simple tag and return a string will become more apparent in a moment.
@register.inclusion_tag(os.path.join(TEMPLATE_DIR,'form_close.html'))
def form_close():
return {}
For the closing form tag I could of done a simple tag and returned a string, but I prefer to keep things consistent so I made it an inclusion tag and the included template is simply just the string.
Now lets look at those templates to see what gets included shall we? First the form_open.html template:
<form action="{{ action }}" method="{{ method }}">{% csrf_token %}
We have our form tag and the action and method variables we passed to it as well as the cross site request forgery token to prevent people from doing a cross site request hack. That is the reason I chose to make it an inclusion tag is so that I could put the csrf_token tag in the template that is included.
and the form_close.html:
</form>
again could of just done a simple tag decorator and return a string, but I wanted to keep some consistency.
Now lets take a look at our directory structure again:
budgetproject
util
templatetags
__init__.py
html.py
__init__.py
models.py
tests.py
views.py
...
templates
html
form_close.html
form_open.html
base.html
Awesome! except now how do we use this new found treasure? Well its actually quite simple and thats what I love about django and python after all “Simple is better than complex”. For us lets pretend we are going to use it in our base.html template. In there we would put:
{% load html %}
{# other html tags and blocks such as html, head, title, body, etc #}
{% form_open "test/url/thing" %}
{# form elements go here #}
{% form_close %}
{# other html tags and blocks such as html, head, title, body, etc #}
So fist and formost we must load our template tags by doing {% load html %} and because our util app is registered and has the templatetags directory with html.py our tags will get loaded. Then we can use our tags as we have defined i.e. {% form_open “test/url/” %} (method is optional and will default to “post”) and {% form_close %}.
Well there you have it, try it out and let me know how it works for you.