Bad Ideas and Regular Expressions in Templates
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:
- The existing datasource used in production.
- A planned but non-existant datasource that is intended to be superior to the existing datasource.
- 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.
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('"')
<span class="k">def</span> <span class="nf">render</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">):</span>
<span class="n">val</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">val</span><span class="o">.</span><span class="n">resolve</span><span class="p">(</span><span class="n">context</span><span class="p">)</span>
<span class="k">if</span> <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">regex</span><span class="p">,</span> <span class="n">val</span><span class="p">):</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">nodelist_true</span><span class="o">.</span><span class="n">render</span><span class="p">(</span><span class="n">context</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">nodelist_false</span><span class="o">.</span><span class="n">render</span><span class="p">(</span><span class="n">context</span><span class="p">)</span>
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.