Using Python str, datetime, lists and sets to group dates
Django Python | 2008-09-21 |
I'm in the process of writing my own blog app (project 'belleville'), one that will eventually replace the Wordpress blog that this site is using, and one of the things I need is a templatetag for the sidebar that lists archive dates, grouped by month - it should look something like the "Archive" list just there to the right, or like this:
The problem is that the date field I have to work with is a MySQL datetime column:
created_at = models.DateTimeField(auto_now_add=True)
... and I only need a unique list of year-month values for all of my active posts.
Solution #1
Solution #1 means returning the entire list of posts (this is going to be an expensive query as my number of posts grows), then assigning a 'month' value to each post in the returned list using a combination of Python datetime attributes and str methods.
import datetime
from django import template
from myentries.models import Post
register = template.Library()
def sidebar_date_list():
posts = Post.objects.filter(publish=1)
for post in posts:
post.month = str(post.created_at.year)+'-'+str(post.created_at.month).rjust(2, '0')
return {'posts': posts}
register.inclusion_tag('date_list.html')(sidebar_date_list)
This gives me a long list of all the existing dates in the format I need:
2008-09
2008-09
2008-09
2008-09
2008-09
2008-09
2008-08
2008-08
2008-08
2008-08
2008-08
2008-08
2008-07
2008-07
2008-07
2008-07
2008-07
2008-07
2008-07
2008-07
2008-07
2008-07
2008-07
2008-06
2008-06
2008-06
Then I can do the date grouping in the template using the Django's regroup tag:
{% if posts %}
<h2>Archive</h2>
<ul>
{% regroup posts by month as month_list %}
{% for month in month_list %}
<li><a href="/date/{{ month.grouper }}">{{ month.grouper }}</a></li>
{% endfor %}
</ul>
{% endif %}
Solution #2
But let's face it, all that logic should not be happening in the template. So, back to the template tag. I'd love to be able to do a select and just group by date, but since my 'created_at' column is a datetime, it would just return a unique date for every post anyway. Instead, I'm using using those string and datetime methods to append all the dates to a new list:
def sidebar_date_list():
posts = Post.objects.filter(publish=1).order_by('-created_at')
month_list = []
for post in posts:
post.month = datetime.datetime(post.created_at.year, post.created_at.month, 1)
month_list.append(post.month)
months = set(month_list)
months = list(months)
months.sort(reverse=True)
return {'months': months}
register.inclusion_tag('date_list.html')(sidebar_date_list)
The month list looks like this:
[datetime.datetime(2008, 9, 1, 0, 0), datetime.datetime(2008, 9, 1, 0, 0), datetime.datetime(2008, 9, 1, 0, 0),
datetime.datetime(2008, 9, 1, 0, 0), datetime.datetime(2008, 9, 1, 0, 0), datetime.datetime(2008, 9, 1, 0, 0),
datetime.datetime(2008, 8, 1, 0, 0), datetime.datetime(2008, 8, 1, 0, 0), datetime.datetime(2008, 8, 1, 0, 0),
datetime.datetime(2008, 8, 1, 0, 0), datetime.datetime(2008, 8, 1, 0, 0), datetime.datetime(2008, 8, 1, 0, 0),
datetime.datetime(2008, 7, 1, 0, 0), datetime.datetime(2008, 7, 1, 0, 0), datetime.datetime(2008, 7, 1, 0, 0),
datetime.datetime(2008, 7, 1, 0, 0), datetime.datetime(2008, 7, 1, 0, 0), datetime.datetime(2008, 7, 1, 0, 0),
datetime.datetime(2008, 7, 1, 0, 0), datetime.datetime(2008, 7, 1, 0, 0), datetime.datetime(2008, 7, 1, 0, 0),
datetime.datetime(2008, 7, 1, 0, 0), datetime.datetime(2008, 7, 1, 0, 0), datetime.datetime(2008, 6, 1, 0, 0),
datetime.datetime(2008, 6, 1, 0, 0), datetime.datetime(2008, 6, 1, 0, 0)]
But converting the list to a set eliminates all the duplicate elements:
months = set(month_list)
set([datetime.datetime(2008, 6, 1, 0, 0), datetime.datetime(2008, 9, 1, 0, 0),
datetime.datetime(2008, 7, 1, 0, 0), datetime.datetime(2008, 8, 1, 0, 0)])
Then convert the set back to a list so it can be sorted:
months = list(months)
months.sort(reverse=True)
And you can iterate over that list in the template, it's just that simple:
{% if months %}
<h2>Archive</h2>
<ul>
{% for month in months %}
<li><a href="/date/{{ month|date:"Y-m" }}">{{ month|date:"Y-m" }}</a></li>
{% endfor %}
</ul>
{% endif %}