Overwrite save() in Django to Sequentially Replace HTML Elements
The low-down on how I automatically number my footnotes.
Friday, 2011-03-18 | demongin.org, Django, Programming, Testing
If it don't fit, force it: if it breaks, it needed replacing in the first place.
Recently, in an effort to write more clearly, I have been writing essays with obscene amounts of footnotes.
Due to the simplicity of the editor1 that I use to write these essays, this obscene amount of footnotes necessitated an obscene amount of manual tweaking: every time I wanted to add a footnote in the middle of an essay, I had to 1.) find and 2.) manually increment or decrement all of the preceding and subsequent footnote numbers.
In order to save myself some time (and sanity) I decided to make two changes to my process:
- Indicate footnotes with a special, unusual tag and
- Overwrite the save() function of the django model field where I keep the bodies of my essays.
Without further ado:
# overwrite save() to automatically handle footnotes def save(self): from re import compile, finditer, DOTALL tag = "foot" pattern = compile(r"<%s>(.*?)<\/%s>" % (tag,tag), DOTALL) count = 1 s = self.body match_group = finditer(pattern,s) for m in match_group: s = s[:m.start()] + "<%s>%s</%s>" % (tag,count,tag) + s[m.end():] count += 1 self.body = s super(Post, self).save()Basically, in my main model (which I call "Post", as in "blog post")2, I save a field called "body":
class Post(models.Model): body = models.TextField()I like to write my own HTML into my posts (because I'm kind of a formatting micromanager), so the "body" field saves my essays and the raw HTML I write into them.
So when I save, I overwrite the built-in save() function of the "Post" model to do the following:
- compile a simple regex that captures any upper-case sup tag
- initialize the "body" field as "s" (for more legible code)
- do a re.finditer() function using my pattern and my body: this gets me a list of match objects with start() and stop() methods that I can use to get string slice numbers
- iterate over the group of match objects, using each object's start() and stop() number to slice my body, swapping out whatever was matched for a value that I increment at the end of the loop
This is a sentence. Here is a second sentence with a footnote<foot>sadfasdfsdfasdf</foot>. Here is a third....and, when I save, I save a string that looks like this:
This is a sentence. Here is a second sentence with a footnote1. Here is a third.Pretty simple stuff, but not entirely un-clever: sequentially substituting values in a string is something that doesn't come up often and, when it does, it usually involves a generator expression (or something similarly complicated).
Finally, even if you don't want to overwrite one of your own django save() functions to alter a string in a programmatic/sequential fashion like I did here, you can see how this might be applicable in other projects: finditer() is kind of an awesome (and somewhat obscure) method of re, and knowing how to use it might save you some pain elsewhere (particularly in the sysadmin realm where sequential find/replace is more likely to occur).
- i.e. a very lightly tweaked version of the standard django admin and Chrome.
- I don't actually do my imports inside the save() function: they're just here for the sake of clarity in this blog post.