I've been teaching myself Django by writing a web app that tracks reading series in cities: Readsr. A reading series is a kind of recurring event. It defines a time, location, and recurrence rule such as first Monday of the month. Writing a list view to display upcoming readings was easy, but I also wanted to create a calendar view, similar to what Google Calendar provides. That took a little more work.
Existing Solutions
First I searched for existing Django calendar solutions. I found several. Swingtime and django-agenda seemed very well thought out and comprehensive, but were also perhaps overkill for what I needed. django-gencal doesn't appear to be maintained and I had trouble understanding the documentation (though you may have better results, as I am slow).
A Way Forward
I found that Python's calendar module has a built template called HTMLCalendar, which sounded promising. Then I found a coupleexamples of people inheriting from HTMLCalendar to add data to the calendar display. This sounded right on, so I adapted this code for my reading events.
Problem: Presentation and Content Mixed
I noticed a problem in the code I was adapting. The view was producing HTML. That seemed to violate separation of content and presentation. Shouldn't the HTML be generated in the template? And since the templating language itself isn't powerful enough to generate an HTML table from a list of objects, that meant I needed to write my own template tag. Yikes.
Writing My First Template Tag
Django's documentation made it easy, though. A template tag consists of a few parts. Below is the code; it goes into a subdirectory of the django app called "templatetags."
The first part follows directly from the Django docs: get a register object.
Then I define a function to parse the template tag arguments and return the node (the HTML code from which the page is eventually build). The template syntax is defined here.
Then I define the node itself, which is made thread-safe by storing and retrieving the variables passed through the template tag from a context (again, this is straight from the Django docs).
Then I inherit from HTMLCalendar and redefine the format methods to add the particular reading event data. You could adapt this class to any kind of event that has an associated date/time by changing the groupby lambda function to use whatever field your event object uses to store its date and time (my reading object simply calls it "date_and_time").
Finally, I register this template tag so it is available to templates.
# Register the template tag so it is available to templates register.tag("reading_calendar", do_reading_calendar)
Then, here's the view (and a couple helper functions) that gets called with the arguments from the URL, including the year, month, and series to display events for.
""" Return the name of the month, given the number. """ return date(1900, month_number, 1).strftime("%B") defthis_month(request): """ Show calendar of readings this month. """ today = datetime.now() return calendar(request, today.year, today.month) defcalendar(request, year, month, series_id=None): """ Show calendar of readings for a given month of a given year. ``series_id`` The reading series to show. None shows all reading series. """ my_year = int(year) my_month = int(month) my_calendar_from_month = datetime(my_year, my_month, 1) my_calendar_to_month = datetime(my_year, my_month, monthrange(my_year, my_month)[1])
my_reading_events = Reading.objects.filter(date_and_time__gte=my_calendar_from_month).filter(date_and_time__lte=my_calendar_to_month) if series_id: my_reading_events = my_reading_events.filter(series=series_id)
And finally, here's the template where we load the template tag and employ it, passing the year, month, and list from the view (you would also want to write some control elements that use previous_year, previous_month, etc. to allow the user to change what the calendar displays, but because I want to wrap this up I'll forgo writing that out):
Use template tag
1 2 3 4 5 6 7 8
{% load reading_tags %}
<div id="calendar"> {% reading_calendar year month reading_list %} </div>
Hopefully that makes sense. Enjoy!
You can also see this code (made generic for any kind of event) on django snippets.