2008-12-22

Missteps in Django - Part 2

My next misstep was related to using the regroup tag that I mentioned in the previous post.

First, let's take a look at the example model I'm working with.

class Shipment(models.Model):
description = models.TextField()
created = models.DateTimeField(auto_now_add=True);


I wanted to display these Shipments grouped by they day they had been created and I wanted that day formated like "2008-12-22". Since the 'created' field of the Shipment model is a datetime.datetime instance which, when viewed as a string, looks like "2008-12-23 19:16:06.700114". That was a bit too verbose and besides, I wanted shipments grouped by day, not by microsecond!

My solution was to a add a property (of course!) to my model and see if I could use that in the regroup tag. Here's what that looked like:


class Shipment(models.Model):
description = models.TextField()
created = models.DateTimeField(auto_now_add=True);

def _get_ship_date(self):
return self.created.date()

ship_date = property(_get_ship_date)

Then in my regroup tag, I could do this:

<ul>
{% regroup shipments by ship_date as shipments_by_date %}
{% for ship_date in shipments_by_date %}
<li>Shipments for {{ ship_date.grouper }} </li>
<ol>
{% for shipment in ship_date.list %}
<li> {{ shipment.description }} </li>
{% endfor %}
</ol>
{% endfor %}
</ul>


Hey, that worked, but this was another of my newbie missteps because there was an aspect to the regroup tag that I had overlooked (this series should be called RTFM maybe?). You can apply a filter to the attribute you are regrouping on like so:


{% regroup ship_list by created|date:"Y-n-d" as shipments_by_date %}

And you're done.

1 comment:

Brandon Rhodes said...

If I follow this model consistently — of making the model more and more complicated, full of special-cased little attributes and properties to get around things that I cannot do in Django templates — then I find that my models wind up very complicated by the end of the project without good reason. They accumulate all sorts of cruft, not to represent things like a "Shipment" better, but to provide idiosyncratic little services for views that are over in some other file where I can't see them when I'm refactoring or testing my models.

Even if it takes another line of code or two, I therefore have started solving all such problems by being a bit more careful with what I pass to the view. Remember that everywhere you pass a Django view an object (like a Shipment), you can pass it a little dictionary instead into which you have stuffed exactly the data that you know the view needs to display — including any hard-to-get items that cannot be fetched using a Django template. This means your views get complicated to match what your templates need, instead of your models getting more and more complicated because of the needs of a template one or two steps away from them.

In fact, I know good web consultancies that always pass views nothing but primitive Python data types (numbers, strings, lists, dictionaries) to make sure that the template designers never grab information off of an object that's sensitive or not supposed to be exposed. Doing so decouples completely what the models look like from how the views are written — which is apparently wonderful if you have different teams writing each part. But it takes a bit more code — you have to build all those dictionaries of strings to pass to each template — so I myself have never done it consistently! I pretty much only write a "complicated" view in the case you've outlined here — where the Django template language just doesn't let me grab things off of the model in the way that I need.