Bad Ideas and Regular Expressions in Templates

11/05/2008

I end up doing a lot of rapid prototyping and functional mockups for (one of) my job(s), and sometimes that ends with really crude solutions to complex problems. Sometimes the really crude solutions are actually kind of neat despite a faint taint of awful.

This story is about one of those times.

The Scenario

I was prototyping a system for a client of a client where the prototyped used an inferior datasource to mock the intended results of the new system based on a superior datasource such that the inferior datasource looked like the results achieved with the non-existant improved system. Got that? Well, that sentence might have been intentionally confusing, so I'll try explaining that again. There were three datasources for this prototyped system:

  1. The existing datasource used in production.
  2. A planned but non-existant datasource that is intended to be superior to the existing datasource.
  3. A datasource worse than the existing production datasource, which I needed to use to create a functional mock of the superior and non-existant datasource.

This is the classic challenge: do more with less, but maybe with a slight twist: make it look like you're doing more, with less. In the short of it, I needed to display special metadata for content, except I didn't actually have the special metadata I needed to display.

A picture of a road in Kamioka-cho, Hida-shi, Gifu-ken, Japan.

The Solution

After staring at my sad stack of cards, I realized that the metadata I needed could be extracted from the structure of urls associated with the data. For example a piece of data with the http://example.com/ab/video/etc/etc/ url had video content, and something at http://blogs.example.com/etc/ was blog content. Throw in another half dozen url structures and I had enough metadata for polishing my impoverished data into something usable.

Even with this realization, I still needed to perform some fairly complex matching, and depending on the results of the matching I needed to represent the data differently. And I wasn't thinking very well, so I didn't just do the matching in views and add an extra value to each result to tell the template in which way to represent the each piece of data.

Instead I wrote a template tag to perform if-else blocks based on successfully matching a supplied regular expression.

from django import template
import re
register = template.Library()

@register.tag
def ifmatches(parser, token):
    lst = token.split_contents()
    val = lst[1]
    regex = lst[2]
    nodelist_true = parser.parse(('else','endifmatches',))
    token = parser.next_token()
    if token.contents == 'else':
        nodelist_false = parser.parse(('endifmatches',))
        parser.delete_first_token()
    else:
        nodelist_false = template.NodeList()
    return MatchesRegexNode(nodelist_true, nodelist_false, val, regex)

class MatchesRegexNode(template.Node):
    def __init__(self, nodelist_true, nodelist_false, val, regex):
        self.nodelist_true = nodelist_true
        self.nodelist_false = nodelist_false
        self.val = template.Variable(val)
        self.regex = regex.strip('"')

    def render(self, context):
        val = self.val.resolve(context)
        if re.search(self.regex, val):
            return self.nodelist_true.render(context)
        else:
            return self.nodelist_false.render(context)

Usage is like this:

{% ifmatches object.url "/video/\w+/" %}
<p class="video">{{ object.title }}</p>
{% else %}
<p class="story">{{ object.title }}</p>
{% endifmatches %}

There are myriad and sundry reasons to hate this solution: you can't precompile the regular expressions and reuse them, you're doing too much calculation in the templates instead of views. Then again, I was on a binge of writing weird template tags and filters for that project:

@register.filter
def kilobytes(value):
    return int(value) / 1000

@register.filter
def truncate_char(value, arg):
    length = len(value)
    arg = int(arg)
    if length > arg:
        return u"%s..." % stripped[:arg-3]
    return value

Sometimes when you're putting out fires in throw-away code you make bad decisions, but that's the whole fun of rapid prototyping: get it done by any means possible, as quickly as possible. Later you review your code and decide never to make the same bad decisions again.

Sometimes we call that learning.

All Rights Reserved, Will Larson 2007 - 2014.