Location for Templates
4.5.3 Adding Logic with Template Tags
To iterate and print the lists of startups and blog posts associated with aTagobject, we need something a little more powerful than simple variables. We need a loop.
Just as Django allows for variables through the{{ }}delimiters, Django provides template tags, which are delimited by{% %}. Template tags allow for conditional logic, loops, and evaluation of functions and methods, meaning that we can run and evaluate Python in our template.
Info
On top of the{{ variable }}and{% expression %}delimiters, Django supplies
{# #}, which is used to write comments in templates. The utility of using template comments over markup-specific comments is that they will not appear in the output of the rendered templates. This further allows for a separation of comments specific to the template code from comments specific to markup code.
We first consider how to get a list of startups, how to iterate through them, and then how to display this information in the template. We then repeat the process for the list of blog posts.
4.5.3.1 Iterating through a QuerySet in a Template to Print a List of
StartupObjects
Recall fromChapter 3that our many-to-many relationship can be directly accessed as a manager object via the related name variable: the manager that provides the set ofStartup objects associated with ourTagobject can be accessed viatag.startup set. The
manager method we use here isall(), meaning we are callingtag.startup set
.all().
Theall()manager method returns aQuerySetobject, which can act like a Python list and allows us to iterate through it using a Pythonforloop. Valid Python code would be for startup in tag.startup set.all(). In Django templates, we remove the parentheses from the method, meaning that a valid template tag of the loop above is{% for startup in tag.startup set.all %}. Unlike variables, template tags provide scope, and therefore we must limit the scope by closing template tags. In this case, we do so with {% endfor %}. Anything in scope, including markup and other logic, will be repeated because of our loop. As in Python, our loop gives us access to thestartupobject referenced to in theforloop, allowing us to print the name of the startup object with {{ startup.name }}.
82 Chapter 4 Rapidly Producing Flexible HTML with Django Templates
We can thus use the unordered list HTML tag<ul>with list item<li>to print a list ofStartupobjects, as in Example 4.10.
Example 4.10:Project Code
organizer/templates/organizer/tag detail.htmlinb9feddf8e3 1 <h2>{{ tag.name }}</h2>
2 <section>
3 <h3>Startups</h3> 4 <ul>
5 {% for startup in tag.startup_set.all %} 6 <li><a href=""> 7 {{ startup.name }} 8 </a></li> 9 {% endfor %} 10 </ul> 11 </section>
Take a moment to consider that our call totag.startup set.all()results in a database query.
InChapter 5, we write the view (as seen in our Hello World example) that will pairTag object data to this view. When we ask for the djangoTag, it will result in the code (omitting the blog post list) shown in Example 4.11.
Example 4.11:HTML Code <h2>django</h2> <section>
<h3>Startups</h3> <ul>
<li><a href="">JamBon Software</a></li> </ul>
</section>
<!-- blog post section omitted -->
This is exactly what we want, unless theTagobject is not associated with any startups. For instance, our webTagis unassociated with any content. This leads to the code in Example 4.12 being output.
Example 4.12:HTML Code <h2>web</h2> <section> <h3>Startups</h3> <ul> </ul> </section>
4.5 Building a First Template: A Single Tag Object 83
This is obviously undesirable. Thankfully, we can control this behavior with conditional logic. Like thefortag, Django provides aniftag in addition to basic Boolean logic operators (and,or,==,!=,>, etc.). We don’t need any operators in our case, however: we just need to know whether there is any content in the queryset returned by
tag.startup set.all(). Luckily, because querysets behave like Python lists, we can simply ask if the list exists, remembering to provide expression delimiters with our conditional logic:{% if tag.startup set.all %}. This is equivalent to{% if tag.startup set.all > 0 %}. We close the tag with{% endif %}. Anything in the scope of the tag is processed only if the condition is met. Our code now reads as in Example 4.13.
Example 4.13:Project Code
organizer/templates/organizer/tag detail.htmlined4f0664d9 1 <h2>{{ tag.name }}</h2>
2 {% if tag.startup_set.all %} 3 <section>
4 <h3>Startups</h3> 5 <ul>
6 {% for startup in tag.startup_set.all %} 7 <li><a href=""> 8 {{ startup.name }} 9 </a></li> 10 {% endfor %} 11 </ul> 12 </section> 13 {% endif %}
If we print our page for the webTag, we’ll output the code in Example 4.14. Example 4.14:HTML Code
<h2>web</h2> <section>
<h3>Blog Posts</h3> <ul>
<!-- list of posts related to tag -->
</ul> </section>
That’s a very lonely piece of HTML. Let’s inform the user that the tag isn’t currently associated with anyStartupobjects. We can do this by providing anelsecondition to ourifcondition above, as in Example 4.15.
Example 4.15:Project Code
organizer/templates/organizer/tag detail.htmlin1038c236e3 1 <h2>{{ tag.name }}</h2>
84 Chapter 4 Rapidly Producing Flexible HTML with Django Templates
3 <section>
4 <h3>Startups</h3> 5 <ul>
6 {% for startup in tag.startup_set.all %} 7 <li><a href=""> 8 {{ startup.name }} 9 </a></li> 10 {% endfor %} 11 </ul> 12 </section> 13 {% else %}
14 <p>This tag is not related to any startups.</p> 15 {% endif %}
4.5.3.2 Iterating through aQuerySetin a Template to Print a List ofPost
Objects
We can now follow the same steps we saw withStartupto print the blog posts list. Recall that in ourPostmodel we specified arelated nameoption to our many-to-many field, as shown in Example 4.16.
Example 4.16:Project Code blog/models.pyin67f70808d7 10 class Post(models.Model):
. ...
20 tags = models.ManyToManyField( 21 Tag, related_name='blog_posts')
Instead oftag.post set.all, our related name attribute gives us access to theTag objects associated withPostobjects viatag.blog posts.all.
Just as before, we can loop throughout theQuerySetreturned by this call, encompassing the entire process in anifblock. The code we create in the first step is shown in Example 4.17.
Example 4.17:Project Code
organizer/templates/organizer/tag detail.htmlin67f70808d7 16 <section>
17 <h3>Blog Posts</h3> 18 <ul>
19 {% for post in tag.blog_posts.all %} 20 <li><a href=""> 21 {{ post.title }} 22 </a></li> 23 {% endfor %} 24 </ul> 25 </section>
4.5 Building a First Template: A Single Tag Object 85
In the second step, we surround the code with that shown in Example 4.18.
Example 4.18:Project Code
organizer/templates/organizer/tag detail.htmlinea6fec30c2 16 {% if tag.blog_posts.all %}
. ...
27 {% else %}
28 <p>This tag is not related to any blog posts.</p> 29 {% endif %}
Consider that our code may print a rather unappealing page. For example, the webTag currently in our database results in the output shown in Example 4.19.
Example 4.19:HTML Code <h2>web</h2>
<p>This tag is not related to any startups.</p> <p>This tag is not related to any blog posts.</p>
It would be prettier if the template only printed a message about missing content if neither list prints any content. As such, we can remove both of theelseblocks and add a thirdifstatement at the end of the template. The full code thus reads as in Example 4.20.
Example 4.20:Project Code
organizer/templates/organizer/tag detail.htmlin963f1a5ce9 1 <h2>{{ tag.name }}</h2>
2 {% if tag.startup_set.all %} 3 <section>
4 <h3>Startups</h3> 5 <ul>
6 {% for startup in tag.startup_set.all %} 7 <li><a href=""> 8 {{ startup.name }} 9 </a></li> 10 {% endfor %} 11 </ul> 12 </section> 13 {% endif %} 14 {% if tag.blog_posts.all %} 15 <section> 16 <h3>Blog Posts</h3> 17 <ul>
86 Chapter 4 Rapidly Producing Flexible HTML with Django Templates
18 {% for post in tag.blog_posts.all %} 19 <li><a href=""> 20 {{ post.title }} 21 </a></li> 22 {% endfor %} 23 </ul> 24 </section> 25 {% endif %}
26 {% if not tag.startup_set.all and not tag.blog_posts.all %} 27 <p>This tag is not related to any content.</p>
28 {% endif %}
Just as in Python, we negate the Boolean value of a variable using thenotkeyword in theifcondition in the template.