Skip to content

aprendía

(I was learning)

Shortcut or hack?

Using “firstof“ in place of “if“

Update: I have now taken a note not to write anything at 2:00 am. Why? Well, the whole premise of my idea is bogus. See why.

I recentlylearned a few things that may seem quite novice to many, but they were big helpers for me. So please don’t fault me for being slow to the party, but…

One little piece of knowledge that has recently made my life a little easier is Django’s firstof templatetag. One of Jeff Croft’s recent articles pointed it out. Despite its presence from the beginning, this was the first that I had ever seen it used or even realized what it could do for me.

This fantastic litte tag takes a list of variables and returns the first one that is not False (there is a little more it to it too; see the docs). This can save all sorts of work, turning this:

{% if user.first_name %}
  {{ user.first_name }}
{% else %}
  {{ user.username }}
{% endif %}

…into this:

{% firstof user.first_name user.username %}

The good stuff

But then I realized that this may has the ability to make other template code much shorter and easier to read. This is because the firstof tag “[o]utputs nothing if all the passed variables are False.”

So think of the last form you templated all of the if statements that get used. They’re everywhere.

{% if form.first_name.errors %}
  {{ form.first_name.errors }}
{% endif %}
<label for="id_first_name">First name</label>
{{ form.first_name }}

{% if form.last_name.errors %}
  {{ form.last_name.errors }}
{% endif %}
<label for="id_last_name">Last name</label>
{{ form.last_name }}

{% if form.email.errors %}
  {{ form.title.errors }}
{% endif %}
<label for="id_email">Email</label>
{{ form.title }}

{% if form.username.errors %}
  {{ form.username.errors }}
{% endif %}
<label for="id_username">Username</label>
{{ form.username }}

But if we pass firstof only one variable, the above could look like this:

{% firstof form.first_name.errors %}
<label for="id_first_name">First name</label>
{{ form.first_name }}

{% firstof form.last_name.errors %}
<label for="id_last_name">Last name</label>
{{ form.last_name }}

{% firstof form.email.errors %}
<label for="id_email">Email</label>
{{ form.title }}

{% firstof form.username.errors %}
<label for="id_username">Username</label>
{{ form.username }}

That is a difference of only eight lines (23 for the former, 15 in the latter) but we have gained some readability. Further, there are forms that are much more complicated than this and any reduction in code helps in maintenance and scanability.

Obviously this method would fall short if you wanted to output other code along with the variable. But if you just want to spit out the variable, firstof is quite handy.

But now the real question that I have — is this a quality shortcut or is it a hack? firstof is meant to be a shorthand replacement for an if/else statement. Is it against best practices to use it as shorthand for just and if? I’m not sure how I feel about it. I think I’ll give a try on some upcoming projects and see if it makes me feel like a sinner.

A proper solution

I will likely try it just because that is the easy way out — at least in the short run. But I think a proper solution would be to make if more robust. Change it such that:

  1. it can accept display as a final argument,
  2. if the display argument is present the block does not have to be closed with an endif and the variable is simply displayed,
  3. without the display argument if would behave as usual.

Thus our previous example could read:

…
{% if form.email.errors display %}
<label for="id_email">Email</label>
{{ form.title }}
…

And while we’re at it, the same could be done for ifchanged, although that may not be quite as handy because almost anytime that ifchanged is used you are outputting some other HTML with it.

Conclusion

Depending on the response of Django designers and developers, I think I have just found myself a new project. We’ll see if my low-level skills can do something like that. Feel free to email or tweet @benspaulding with yay’s, nay’s, or other ideas.