Monthly archives on Django

Build your own collapsing monthly archive for your Django project, in a few simple steps.

Monthly archiving is a pretty cool feature offered by many blogs/sites. We wanted to add such a feature on the greek music portal, and specifically on the festivals page. We have created a simple solution that you can checkout on, and through the following article would like to share with the Django community. We would love to see the archive code on any Django powered blog/cms, and we have packed the example code on a small Django application that you can checkout on with the monthly archive for festivals on the right side


Our example model is a simple model, with a DateField called date. This can be adjusted to whatever DateField field your models might contain.

from django.db import models
import datetime

class Event(models.Model):
    "A very basic model for Events"
    name = models.CharField('A name for the event', max_length=200)
    place = models.CharField('Location where the event takes place',
    date = models.DateField('When the event takes place')

On the view that we want to create the monthly archives now. First we make a query asking for all the events. Then we create a dictionary with years as keys, and for values we create dictionaries with months and the events for these months. Since in Python we can't create a dictionary with sorted keys, we create a list with dictionaries of years:months/events and pass it to our template.

# Create your views here.

from django.template import Context, loader
from django.http import HttpResponse
import datetime
from models import Event
def events_index(request):
    '''a basic events listing view'''
    events = Event.objects.filter().order_by('-date')
    now =
    #create a dict with the years and months:events 
    event_dict = {}
    for i in range(events[0].date.year, events[len(events)-1].date.year-1, -1):
        event_dict[i] = {}
        for month in range(1,13):
            event_dict[i][month] = []
    for event in events:
    #this is necessary for the years to be sorted
    event_sorted_keys = list(reversed(sorted(event_dict.keys())))
    list_events = []
    for key in event_sorted_keys:
        adict = {key:event_dict[key]}
    t = loader.get_template('templates/event_index.html')
    c = Context({
       'now': now,'list_events':list_events,
    return HttpResponse(t.render(c))

Finally, we pass the list_events list to our template. In our example, we want to show a monthly archive of the events, where months expand once clicked and show the events that take place on that month, with details as the location. Note that the current year/month is expanded automatically, through the variable "now" that we pass to the template. With the use of jQuery and css, we can present the data passed by the view to our template, the way we want.



    <link rel="stylesheet" type="text/css" href="../static/main.css" />
    <script type="text/javascript" src="../static/jquery.min.js"></script>

    {% for event_year in list_events %}
        <ul class="year">{{event_year.keys.0}}
            {% for month, events in event_year.values.0.items %}
                <!--show only months with events! -->
                {% if events %} 
                    <li id="{{event_year.keys.0}}-{{month}}" class="month">
                        <div class="month-data">
                            <div class="collapsed">&nbsp;</div>
                            <div class="expanded" style="display:none;">&nbsp;</div>
                            <span class="name">{{|date:"F"}}</span>
                            <span class="counter"> ({{events|length}})</span>
                        <ul class="events" style="display:none;">
                        {% for event in events %}
                            <li class="event">
                            <a class="title" href="#">{{}}</a>
                            <div class="date">{{|date:"j M Y"}}</div>
                            <div class="place">{{}}</div> 
                        {% endfor %}
                {% endif %}
            {% endfor %}
    {% endfor %}

    // toggle month
    var toggleMonth = function(month) {
        $(month).find('.month-data div.collapsed').toggle();
        $(month).find('.month-data div.expanded').toggle(); 

    // expand the current month
    // intercept click to toggle month
    $('.month-data').click(function() {
        var month = $(this).parent();


You are now ready to populate your database with some events and use your new archive. With the correct css and styling you will manage to achieve your desired results!

Django simple-archive app

As mentioned above, we have packed the code and created a small app, that can be used to test the archive solution. The installation below takes place with the help of virtualenv:

First create a virtual python instance and install Django. Then clone the repository

user@user:~/simple-archive$ virtualenv --python=python2.6 --no-site-packages ./django-test
user@user:~/simple-archive$ cd django-test/
user@user:~/simple-archive/django-test$ ./bin/pip install django
user@user:~/simple-archive/django-test$ git clone simple-archive

sync the db, and load some fixture data

user@user:~/simple-archive/django-test$ cd simple-archive/
user@user:~/simple-archive/django-test/simple-archive$ ../bin/python syncdb
user@user:~/simple-archive/django-test/simple-archive$ ../bin/python loaddata fixtures
Installed 10 object(s) from 1 fixture(s)
user@user:~/simple-archive/django-test/simple-archive$ ../bin/python runserver
Validating models...

0 errors found
Django version 1.3.1, using settings 'simple-archive.settings'
Development server is running at
Quit the server with CONTROL-C.

Now point your browser to and you'll see the archive, with fictitious data.

Example of a monthly archive expanded

Example of a monthly archive collapsed

How to add a Reply-To header in email messages sent through Plone

Plone ships with an author feedback form that enables logged in users to contact other portal members. When the form is submitted, an email message is sent to the target user, using the site's global email address in the "From:" email header. We had to partially change this behavior by adding a "Reply-To:" in the message headers, so that when the user replies to the email received, the reply will be sent to the user that filled in the contact form, rather than the Plone site's global address.

The solution is easy, albeit not immediately obvious. All we need to do is customize and prepend the following line.

Reply-To: <span tal:replace="options/send_from_address"/>

We can do that either through the web using the ZMI, by pointing our browser to and clicking on customize, or through the file system, by adding a modified in the myproduct/skins/myproduct_custom_templates folder of our custom Plone product.