Location for Templates
4.6.2 Template for a Single Startup Object
Create a new file at/organizer/templates/organizer/startup detail.html.
In the template, we list information associated with a singleStartupobject using an HTML definition list, as shown in Example 4.35. We assume that our template is given a startupobject, from which we will derive all data: we will list all of the fields, or data attributes, of ourStartupmodel class passed to thestartupvariable (which we shall code inChapter 5).
Example 4.35:Project Code
organizer/templates/organizer/startup detail.htmlin5cc3cf5880 1 <article> 2 3 <h2>{{ startup.name }}</h2> 4 <dl> 5 <dt>Date Founded</dt> 6 <dd>{{ startup.founded_date }}</dd> 7 <dt>Website</dt> 8 <dd>{{ startup.website }}</dd> 9 <dt>Contact</dt> 10 <dd>{{ startup.contact }}</dd> 11
12 <dt>Tag{{ startup.tags.count|pluralize }}</dt> 13 {% for tag in startup.tags.all %}
94 Chapter 4 Rapidly Producing Flexible HTML with Django Templates 14 <dd><a href=""> 15 {{ tag.name|title }} 16 </a></dd> 17 {% endfor %} 18 19 </dl> 20 21 <p>{{ startup.description }}</p> 22 23 {% if startup.newslink_set.all %} 24 <section> 25 <h3>Recent News</h3> 26 <ul>
27 {% for newslink in startup.newslink_set.all %}
28 <li>
29 <a href="{{ newslink.link }}"> 30 {{ newslink.title|title }}</a>
31 </li> 32 {% endfor %} 33 </ul> 34 </section> 35 {% endif %} 36 37 {% if startup.blog_posts.all %} 38 <section>
39 <h3>Blog Post{{ startup.blog_posts.all|pluralize }}</h3> 40 <ul>
41 {% for post in startup.blog_posts.all %}
42 <li>
43 <a href="">
44 {{ post.title|title }}</a>
45 </li> 46 {% endfor %} 47 </ul> 48 </section> 49 {% endif %} 50 51 </article>
The code in Example 4.35 does not introduce anything new, and you should be comfortable reading it. For the sake of clarity, however, we will now quickly walk through it.
Lines 3, 6, 8, and 10 print (respectively) the name, foundation date, website, and contact
email of the startup, using (respectively)startup.name,startup.founded date,
startup.website, andstartup.contact. We also print the full text description of thestartupobject on line 21. We print all of these values using the template variable delimiters:{{ variable }}.
We have three loops using aStartup-related manager and thefortemplate tag delimited by{% expression %}. The first loop, starting at line 13 and ending at line 17,
4.6 Building the Rest of Our App Templates 95
iterates through all of theTagobjects related to theStartupobject passed to our template. We print the name of eachTagobject, capitalizing the first letter of every word in the string with thetitletemplate filter. The second loop iterates throughNewsLinkobjects associated with thestartupvariable on lines 27 through 32, printing the URL and headline of the news piece in a simple HTML link (using the anchor tag<a>). We apply thetitlefilter to theNewslinkheadline. The third loop, from lines 41 to 46, displays related blog posts.
Observe that we close scope using{% endfor %}for all of our loops. Anything in scope, or between the start and end tag, will be repeated for each iteration of the loop, including printing of markup.
Unlike with ourTagtemplates, we use neither aniftemplate tag nor the{% else %} tag to verify the existence ofTagobjects. We are thus assuming that aStartupwill always be associated with oneTagobject.
We can improve the display above in three ways. We can 1. Customize the display of the foundation date of the startup. 2. Provide a link directly to theStartupwebsite.
3. Properly format the description of the startup.
4.6.2.1 Using thedateTemplate Filter to Customize Output
While we treat thestartup.founded datevalue on line 4 as a string, it is in fact a Pythondatetime.dateobject. Given the code above, should you render the page for our ‘JamBon Software’ startup, the page will print “Jan. 18, 2013” for the date value.
Django automatically applies thedatefilter.{{ startup.founded date }}is
equivalent to{{ startup.founded date|date }}. Thedatefilter allows us to
customize the way in which the date is printed. By default, it applies the"DATE FORMAT"
argument, meaning that{{ startup.founded date }}is equivalent to
{{ startup.founded date|date }}, which in turn is equivalent to
{{ startup.founded date|date:"DATE FORMAT" }}. Thedatefilter also accepts three other pre-prepared date formats:"SHORT DATE FORMAT","DATETIME FORMAT", and"SHORT DATETIME FORMAT". Our variable is aDateField, not aDateTimeField, which means we cannot use the"DATETIME FORMAT"or"SHORT DATETIME FORMAT"
arguments. However,{{ startup.founded date|date:"SHORT DATE FORMAT" }}
will output “01/18/2013” instead of the default “Jan. 18, 2013” provided by the "DATE FORMAT"argument.
In turn, it is possible to customize the date output by specifying a format string in which each character in the string has a special meaning in relation to the date. For example, {{ startup.founded date|date:"SHORT DATE FORMAT" }}, which will output “01/18/2013,” could also be written{{ startup.founded date|date:"d/m/Y" }}.
Alternatively,{{ startup.founded date|date:"DATE FORMAT" }}can be written {{ startup.founded date|date:"N j, Y" }}, outputting “Jan. 18, 2013.” Django provides 40 case-sensitive characters to allow for date customization. It would not benefit us to look at each one, so instead we examine the ones we intend to use for our own datetimeobject.
96 Chapter 4 Rapidly Producing Flexible HTML with Django Templates
Our call todatewill use theF,j,S, andYformat characters.Fwill print the full month,jwill print the day of the month as an integer,Swill add a suffix to the number (such as st), andYwill print the year as a four-digit number. As such, our call to
{{ startup.founded date|date:"F jS, Y" }}will output “January 18th, 2013” for the JamBon Startup.
For the full list of templatedateformat characters, please see the official reference guide, found onthe official Django documentation page.2
4.6.2.2 Automatic Linking with theurlizeTemplate Filter
Currently, we are printing the URL of the startup’s website as a string. It would be easier for the user if we provided this as a link, allowing them to simply click the link rather than having to copy and paste it.
There are two ways to do this. The first is to code the necessary HTML, as in Example 4.36.
Example 4.36:Django Template Language Code
<a href="{{ startup.website }}">{{ startup.website }}</a>
Observe that we have done something very similar for ourNewsLinkobject in Example 4.37.
Example 4.37:Django Template Language Code
<a href="{{ newslink.link }}">{{ newslink.title|title }}</a>
Our second option is to use theurlizetemplate filter on the current variable: {{ startup.website|urlize }}. In the case our website, ifhttp://jambonsw.com, then theurlizefilter will print<a href="http://jambonsw.com"
rel="nofollow">http://jambonsw.com</a>.
We should always consider the two options becauseurlizeadds therel="nofollow" attribute to our anchor tag. Search engines use the number of links to a website as a way of determining a site’s importance, but links with therel="nofollow"attribute do not contribute to the ranking algorithms. The attribute was introduced as a way for blog writers to limit the effect of spam links posted in comments, effectively telling search engines that those links were not official. As such, the attribute, and thereforeurlize, is very useful in some but not all contexts.
In this context we must ask ourselves who is providing the information about each startup. As the final goal is to have it be community driven, and therefore out of our direct control, I will choose to use theurlizefilter, as I think therel="nofollow"attribute is potentially warranted. However, this is a subjective choice, and you may disagree with it.
4.6 Building the Rest of Our App Templates 97
Info
It is helpful to remember that a URL has the following components: scheme://network_location/path?query#fragments
A network location may be a domain name with top-level domain (TLD) or else an IP
address. Ingoogle.com,googleis the domain name andcomis the TLD.
Theurlizefilter is a finicky filter. The scheme of the URL is optional, but in the event that it is not specified, the domain of the URL must have one of the following TLDs: .com,.edu,.gov,.int,.mil,.net, and.org.
Ghosts of Django Past
In versions prior to Django 1.8, theurlizefilter would not recognize URLs that
had a path but not a scheme. You could not specifydocs.djangoproject
.com/en/1.8/and instead had to ensure the value of the field in the model was https://docs.djangoproject.com/en/1.8/
If we pass the stringlemonde.frto theurlizefilter, it will not print an HTML
anchor tag. Instead, we must providehttp://lemonde.frtourlize.
While knowing about the potential pitfalls of various template filters is useful, this section is also a prelude toChapter 7: Allowing User Input with Forms. We return to this filter then to consider the issue in full.
4.6.2.3 Using thelinebreaksFilter to Correctly Format a Paragraph
One of the features of HTML is that it ignores whitespace. Formatting such as paragraph breaks and multiple spaces are simply ignored. Instead, HTML is supposed to be formatted using the paragraph tag<p>and the line break tag<br />.
The description we have for ourStartupobjects is not currently formatted in HTML, nor would we want it to be. Providing a markup-agnostic format allows us to output to whatever we want. However, it also means that printing it the way we are now will result in multiple paragraphs being squashed together.
For example, take the following paragraph: This is the first paragraph.
This is the second. This is not quite its own.
While the spacing will be correctly printed in the HTML page, the browser will render it as:
This is the first paragraph. This is the second. This is not quite its own. Luckily, Django provides thelinebreaksfilter, which converts a newline (a single carriage return) to a<br />, and two to a<p>. This capability allows regular formatting
98 Chapter 4 Rapidly Producing Flexible HTML with Django Templates
to be preserved in our HTML output. To apply it, we simply change line 21, shown in Example 4.38, to read as shown in Example 4.39.
Example 4.38:Django Template Language Code <p>{{ startup.description }}</p>
Example 4.39:Django Template Language Code {{ startup.description|linebreaks }}
Note that the surrounding paragraph tags have been removed. The filter will provide them for us. Given our example, the output will now be as shown in Example 4.40.
Example 4.40:HTML Code
<p>This is the first paragraph.</p>
<p>This is the second.<br />This is not quite its own.</p>
This approach effectively retains any formatting we may provide in ourStartup description field.
Incorporating all of our changes from the last three subsections, our final template code is shown in Example 4.41.
Example 4.41:Project Code
organizer/templates/organizer/startup detail.htmlinedf261b0c7 1 <article>
2
3 <h2>{{ startup.name }}</h2> 4 <dl>
5 <dt>Date Founded</dt>
6 <dd>{{ startup.founded_date|date:"F jS, Y" }}</dd> 7 <dt>Website</dt>
8 <dd>{{ startup.website|urlize }}</dd> 9 <dt>Contact</dt>
10 <dd>{{ startup.contact }}</dd> 11
12 <dt>Tag{{ startup.tags.count|pluralize }}</dt> 13 {% for tag in startup.tags.all %}
14 <dd><a href=""> 15 {{ tag.name|title }} 16 </a></dd> 17 {% endfor %} 18 19 </dl> 20 21 {{ startup.description|linebreaks }} 22 23 {% if startup.newslink_set.all %} 24 <section> 25 <h3>Recent News</h3>
4.6 Building the Rest of Our App Templates 99
26 <ul>
27 {% for newslink in startup.newslink_set.all %}
28 <li>
29 <a href="{{ newslink.link }}"> 30 {{ newslink.title|title }}</a>
31 </li> 32 {% endfor %} 33 </ul> 34 </section> 35 {% endif %} 36 37 {% if startup.blog_posts.all %} 38 <section>
39 <h3>Blog Post{{ startup.blog_posts.all|pluralize }}</h3> 40 <ul>
41 {% for post in startup.blog_posts.all %}
42 <li>
43 <a href="">
44 {{ post.title|title }}</a>
45 </li> 46 {% endfor %} 47 </ul> 48 </section> 49 {% endif %} 50 51 </article>