At first, we need to create a directory
templatetags
containing an empty __init__.py
file in the climate_change
directory.
mkdir -p climate_change/templatetags
touch climate_change/templatetags/__init__.py
I will call the template library
weather
. So I have to create a file weather.py
in climate_change/templatetags
and define and register the template tag in that file. The template tag get_current_weather
should display the current weather for a chosen location. To define what location you choose, you could refer to id
, name
or location_id
, but none of them is appropriate for this reason. id
and location_id
are not remember-able and not informative enough, whereas the name might be changed to translate the city to another language or to add some more specifics and this change would detach the template tag from the location. For those reasons, it is best to create a new field sysname
for the location model which would have a unique non-changeable value as a textual humanized identifier for templates.But wait! It's such a pain to add new fields and modify database schema... Not, if you are using south! Let's quickly install it and then put it under INSTALLED_APPS.
easy_install south
#...
INSTALLED_APPS = (
# django core
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.sites",
"django.contrib.admin",
# third-party
"south",
# project-specific
"climate_change",
)
#...
Note that south was installed in the virtual environment not spoiling the global python namespace. Now we will syncronize the database to create
south_history
table, create the initial migration for climate_change
and apply it.
# create the missing database table from south app
python manage.py syncdb
# create initial migration for climate_change app which will be used by new projects
python manage.py startmigration climate_change --initial
# fake this migration for this project
python manage.py migrate climate_change --fake
Now we can finally add the new
sysname
field, create migration for it and apply it.
#...
class Location(models.Model):
sysname = models.SlugField(
_("system name"),
max_length=200,
unique=True,
blank=True,
default="",
help_text=_("Do not change this value"),
)
name = models.CharField(_("name"), max_length=200)
location_id = models.CharField(
_("location ID"),
max_length=20,
help_text=_("Location IDs can be retrieved from URLs of weather "
"at specific cities at Yahoo! Weather, e.g. GMXX0008 from "
"http://weather.yahoo.com/forecast/GMXX0008.html"),
)
#...
# create a new migration called "add_sysname"
python manage.py startmigration climate_change add_sysname --auto
# apply it to the database
python manage.py migrate climate_change
I had to set the default value to empty string because otherwise south throws exception when I use sqlite3. Anyway, after running those commands, I started the built in webserver again and added the
sysname
"berlin" to the record of Berlin's location.We can get back to
weather.py
and add the template tag there
# -*- coding: UTF-8 -*-
from django.db import models
from django import template
from django.template import loader
register = template.Library()
### TAGS ###
def do_get_current_weather(parser, token):
"""
Returns the latest known weather information.
Usage::
{% get_current_weather in <location_sysname> [using <template_path>] [as <var_name>] %}
Examples::
{% get_current_weather in "berlin" using "climate_change/custom_weather.html" %}
{% get_current_weather in "london" as current_weather %}
var sCurrentWeather = "{{ current_weather|escapejs }}";
"""
bits = token.split_contents()
tag_name = bits.pop(0)
template_path = ""
var_name = ""
location_sysname = ""
try:
while bits:
first_word = bits.pop(0)
second_word = bits.pop(0)
if first_word == "in":
location_sysname = second_word
elif first_word == "using":
template_path = second_word
elif first_word == "as":
var_name = second_word
except ValueError:
raise template.TemplateSyntaxError, "get_current_weather tag requires a following syntax: {% get_current_weather [using <template_path>] [as <var_name>] %}"
return CurrentWeatherNode(tag_name, location_sysname, template_path, var_name)
class CurrentWeatherNode(template.Node):
def __init__(self, tag_name, location_sysname, template_path, var_name):
self.tag_name = tag_name
self.location_sysname = location_sysname
self.template_path = template_path
self.var_name = var_name
def render(self, context):
location_sysname = template.resolve_variable(
self.location_sysname,
context,
)
template_path = ""
if self.template_path:
template_path = template.resolve_variable(
self.template_path,
context,
)
context.push()
WeatherLog = models.get_model("climate_change", "WeatherLog")
logs = WeatherLog.objects.filter(
location__sysname=location_sysname,
).order_by("-timestamp")
if logs:
context['weather'] = logs[0]
output = loader.render_to_string(
[template_path, "climate_change/current_weather.html"],
context,
)
context.pop()
if self.var_name:
context[self.var_name] = output
return ""
else:
return output
register.tag("get_current_weather", do_get_current_weather)
### FILTERS ###
# none at the moment
As you might see from the code, the template tag is using a template which can be redefined by the template designer. We still need the default template itself, so I will create a directory
templates/climate_change
and a file current_weather.html
with this content:
{% load i18n %}
<div class="current-wheather">
<h3>{{ weather.location.name }}</h3>
<dl>
<dt>{% trans "Temperature" %}:</dt>
<dd>{{ weather.temperature }}° C</dd>
<dt>{% trans "Humidity" %}:</dt>
<dd>{{ weather.humidity }} %</dd>
<dt>{% trans "Wind speed" %}:</dt>
<dd>{{ weather.wind_speed }} km/h</dd>
<dt>{% trans "Visibility" %}:</dt>
<dd>{{ weather.visibility }} km</dd>
</dl>
</div>
How can I test the template tag? I will need a new page which will include it. So I will add a rule in
urls.py
to redirect root url to index.html
which will extend from base.html
.So the
base.html
looks like this:
{% block doctype %}>!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">{% endblock %}
{% load i18n %}
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<title>{% block title %}simple document{% endblock %}</title>
{% block extra_head %}{% endblock %}
</head>
<body>
<div id="header">{% block header %}{% endblock %}</div>
<div id="content">{% block content %}{% endblock %}</div>
<div id="footer">{% block footer %}{% endblock %}</div>
</body>
</html>
The
index.html
looks like this:
{% extends "base.html" %}
{% load i18n weather %}
{% block content %}
{% get_current_weather in "berlin" %}
{% endblock %}
And also we'll need an extension in the
urls.py
:
from django.conf.urls.defaults import *
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns("",
(
r"^$",
"django.views.generic.simple.direct_to_template",
{'template': "index.html"},
),
(r"^admin/", include(admin.site.urls)),
)
When I run the development server and go to
http://127.0.0.1:8000/
, I see this:It's time for the graphs! The end of this tutorial will be published here tomorrow.
No comments:
Post a Comment