Archive

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 liveinspector.gr, and specifically on the festivals page. We have created a simple solution that you can checkout on www.liveinspector.gr/festivals, 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 https://github.com/unweb/simple-archive.

 

liveinspector.gr 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.

models.py:

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',
                             max_length=200)
    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.

views.py:

# 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 = datetime.datetime.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:
        event_dict[event.date.year][event.date.month].append(event)
 
    #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]}
        list_events.append(adict)
 
    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.

templates/event_index.html:

<html>

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

<body>
    {% 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">{{events.0.date|date:"F"}}</span>
                            <span class="counter"> ({{events|length}})</span>
                        </div>
                        <ul class="events" style="display:none;">
                        {% for event in events %}
                            <li class="event">
                            <a class="title" href="#">{{event.name}}</a>
                            <div class="date">{{event.date|date:"j M Y"}}</div>
                            <div class="place">{{event.place}}</div> 
                            </li>
                        {% endfor %}
                        </ul>
                    </li>
                {% endif %}
            </li>
            {% endfor %}
        </ul>
    {% endfor %}
</body>

<script>
    // toggle month
    var toggleMonth = function(month) {
        $(month).children('.events').slideToggle('slow');
        $(month).find('.month-data div.collapsed').toggle();
        $(month).find('.month-data div.expanded').toggle(); 
    }

    // expand the current month
    toggleMonth($('#{{now.year}}-{{now.month}}'));
    
    // intercept click to toggle month
    $('.month-data').click(function() {
        var month = $(this).parent();
        toggleMonth(month);
    });
</script>

</html>

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 http://github.com/unweb/simple-archive.git 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 manage.py syncdb
user@user:~/simple-archive/django-test/simple-archive$ ../bin/python manage.py loaddata fixtures
Installed 10 object(s) from 1 fixture(s)
user@user:~/simple-archive/django-test/simple-archive$ ../bin/python manage.py runserver
Validating models...

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

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

Example of a monthly archive expanded

Example of a monthly archive collapsed