Booleans in Django templates
I was working on the frontend for the footbag database project today and came across something I found counterintuitive when working with Django templates.
Part of the project involves keeping track of information related to videos. Here's a model for videos:
class MoveDemonstrationVideo(models.Model): """ This is to keep track of move demonstration videos. """ move = models.ForeignKey(Move) video_type = models.CharField(max_length=1, choices=VIDEO_TYPES, default=URL_VIDEO_TYPE) URL = models.URLField() use_start = models.BooleanField(default=False) start_time = models.PositiveSmallIntegerField() use_end = models.BooleanField(default=False) end_time = models.PositiveSmallIntegerField() def __unicode__(self): if self.use_start == True or self.use_end == True: return u'Demonstration video for Move: %s, %s, %s start: %d end %d' % (self.move.name, self.video_type, self.URL, self.start_time, self.end_time) else: return u'Demonstration video for Move: %s, %s, %s' % (self.move.name, self.video_type, self.URL)
On the front end I wanted to conditionally display some text that showed the start and end times for a video if those were specified. This was my first attempt at this:
{% for vid in videos %} {% if vid.use_start or vid.use_end %} <li><a href="{{ vid.URL }}" class="vidtype{{ vid.video_type }}"> click for demonstration ({% if video.use_start %}starts at {{ vid.start_time }} {% endif %} {% if video.use_end %}ends at {{ vid.end_time }} {% endif %}) {% if vid.video_type == vid_types.URL %}(external link){% endif %}</a> </li> {% else %} <li><a href="{{ vid.URL }}" class="vidtype{{ vid.video_type }}"> click for demonstration {% if vid.video_type == vid_types.URL %}(external link){% endif %}</a></li> {% endif %} {% endfor %}
Now this rendered into the following HTML for a video that had both a start and end time:
<ul> <li><a href="https://youtu.be/bCxOBuHigZM" class="vidtype2"> click for demonstration ( ) </a> </li> </ul>
This I found quite perplexing, the {% if vid.use_start or vid.use_end %} conditional is evaluating as true but {% if video.use_start %} and {% if video.use_end %} were not. The next thing I did was to add some diagnostic output:
{% if vid.use_start or vid.use_end %} ***test*** use_start: {{ vid.use_start }} start_time: {{ vid.start_time }} <br /> use_end: {{ vid.use_end }} end_time: {{ vid.end_time }} <br /> {% endif %}
The result from rendering this was:
***test*** use_start: True start_time: 15 <br /> use_end: True end_time: 21 <br />
So it does have a value...
{% for vid in videos %} {% if vid.use_start or vid.use_end %} <li><a href="{{ vid.URL }}" class="vidtype{{ vid.video_type }}"> click for demonstration ({% if video.use_start == True %}starts at {{ vid.start_time }} {% endif %} {% if video.use_end == True %}ends at {{ vid.end_time }} {% endif %}) {% if vid.video_type == vid_types.URL %}(external link){% endif %}</a> </li> {% else %} <li><a href="{{ vid.URL }}" class="vidtype{{ vid.video_type }}"> click for demonstration {% if vid.video_type == vid_types.URL %}(external link){% endif %}</a></li> {% endif %} {% endfor %}
This produced the same results as before.
Most of the confusion stemmed from my experiences as a Python developer that was causing me to make a false assumption about bools in the template system.
Django templates do not contain Python code
Sure I knew that the templating system was the entire Python language but the extent to which it was not Python wasn't too clear to me until now. Specifically boolean values such as True and False are not treated as the python Boolean values when used in the templates. This is a very deliberate design decision, by keeping complicated logic out of the templates the separation of UI and logic is much cleaner.
So what ends up happening with {% if video.use_end == True %} is that the template system is treating True as if it were a literal referring to a variable rather than the Python built-in boolean True. Because there isn't a key named True being passed to the template via the context we get a VariableDoesNotExist exception which the template ends up treating as if it were None. As this is not None this explains why we do not get a hit for the {% if video.use_start == True %} expression.