Disclaimer: For those who don’t know me, I have a propensity for writing long-winded posts (and emails), so I apologize in advance to those not patient enough to wade through them fully. I try to be thorough, and I hope such posts are worth your time.
Today’s topic is templating in the Courant News platform; specifically, the philosophy behind it and some of the special or unique features.
Philosophy
Courant News is built on the Django web framework, and uses its templating system. Although one can use other templating engines with Django, we strongly believe in the Django template design philosophy. Jeff Croft wrote an excellent post on this topic a few years ago. You should go read it before continuing if you are not familiar with Django templates.
Most college news organizations have limited (if any) programming talent in-house, and their efforts should ideally be dedicated to doing actual programming and development work. They should not be unnecessarily distracted by the need to assist in the implementation of site design and theming. For those organizations without coders, customizing the look and feel and basic functionality of the site should not require a programmer or an extensive training process.
I’m not going to explain how to write Django templates, as there are plenty of sources where you can learn about that. Instead I want to focus on some Courant News-specific details. Normal Django template variables, tags, filters, and inheritance all play a role and can be used in a normal fashion.
Template Hierarchy
Courant News is designed to allow organizations to control almost all aspects of their site either using the admin interface or through the template system. As much as possible, we try to avoid creating any reason for individual organizations to modify most of the core Python code, especially the views. As a result, the built-in views use a convention-based template hierarchy similar to how WordPress works, but while still giving you the absolute freedom to deviate if you desire.
For example, let’s take section pages. Courant News allows you to define as many nest-able sections as your site requires. For example, the Yale Daily News has a top-level News section, with University, City, Features, and Sci/Tech subsections. Although we don’t use sub-subsections, there is nothing preventing the repeated nesting of sections as needed. Now, when someone goes to the URL for, say, the University News section, Courant will search for one of these templates and use the first one it finds:
- /sections/news/university/detailed
- /sections/news/subsection_detailed
- /sections/news/detailed
- /sections/detailed
The flexibility that this affords is incredible. You can create just a “/sections/detailed” template which will determine how all your sections will look, and leave it at that. But if you want a specific section to have a different look, you create a folder with its slug name and a “detailed” template inside the folder. Voila, Courant will pick it up and you have a customized section template. You can also control how any subsections of that section will look by defining a “subsection_detailed” template. This is useful if you want subsections to return to the default style or if you want all subsections to have a certain different styling. And you can continue adding folders for nested subsections to meet your specific needs.
A similar system exists for Articles, which is based on a set of “Display Types” that you can define and associate with each article. Courant ships with a single, default “Standard” display type, but you can define additional types to handle different types of articles. For example, the YDN will have a new “Prominent Media” display type, which will show a large image above the article text (our standard type just puts media in a sidebar). Issues have an identical display type system to allow for multiple reusable issue display layouts.
Throughout Courant News, the system will just search for templates placed according to such conventions, which makes it easy to customize specific aspects of a site while having standard fallbacks and thus allowing a minimal of initial setup work and an evolving site over time. Note that you can create whatever other template files you desire, these sets of conventions are only for choosing the initial template file to load. The YDN has an extensive set of support templates containing HTML snippets that get used in multiple places, and we take full advantage of template inheritance as well.
File Extensions
Above you may have noticed that the template search paths did not use any file extensions. The reason for this is that Courant supports arbitrary file types and extensions in URLs. By default, any URL without a file extension will be assumed to be an HTML page, and all template search paths will get a “.html” appended to them. However, if a user puts .rss or .mobile on the end of a page’s URL, Courant will attempt to use a template with that file extension. So to create a mobile version of the default section template, I’d simply create “/sections/detailed.mobile” in addition to my “/sections/detailed.html” which normally gets used. And likewise for “/sections/detailed.rss” or any other file extension you might want to use, such as .print. Courant will ship with a base site package that will include templates such as a default section RSS feed template, since such things are pretty standard. But if you wanted your subsection to have a custom RSS feed, you could do that just as easily using the same convention scheme described above.
The power of this system is that you get immediate support for arbitrary file extensions without requiring any code modifications at all. No need for custom wrangling of a url configuration file or anything complicated.
“Get” Tag
One thing that is often difficult for people learning how to use a new template system is learning how to fetch the data they require. This often requires learning a maze-like plethora of template tags or function names. Early versions of Courant fell into this trap, as there are countless permutations of ways that one might want or need to fetch and filter data for display on a news site. The situation quickly became untenable, even for us programmers. Our solution was the “get” template tag, which allows template authors to fetch data in sentence-like format which is easy to read and understand. Exploration of the full extent of the get tag will take a long post of its own, but I’ll highlight some interesting parts here.
Knowing what data is necessary is often straightforward, such as the article for an article view page, a section for a given section page or an issue for a given issue archive page. However, for many pages, especially the all-important home page, each news site chooses to display different sets of data. Instead of Courant trying to guess or tell you what to use, the get tag allows you to get whatever you need. It is similarly useful in sidebar widgets and other such blocks on sites that are independent of the main page focus.
For example, if we wanted to just list all the articles by section in the latest issue, we might have the following calls in our homepage template:
{% get issue order by -published_at limit 1 as latest_issue %}
{{ issue.name }}
{% get sections in issue latest_issue as section_list %}
{% for section in section_list %}
{{ section.name }}
{% get articles in section section in issue issue as articles %}
-
{% for article in articles %}
- {{ article.heading }} {% endfor %}
This example just shows the most basic use case, which is filtering result sets by their relationships to other objects. As shown, the tag supports limiting the number of results; it can also use an offset, so if you want 6 articles after the latest 5, you could use:
{% get articles in section "University News" limit 6 offset 5 as article_list %}
The tag also supports filtering based on date ranges, so that you can get articles from the last week or 17 days, or events from the next 3 weeks.
{% get events from next 3 weeks as upcoming_events %}
Another useful bit of functionality is the ‘with’ clause, which enables you to restrict objects by what types of relationships they have. For example, we might want the latest 5 articles with attached media for display in a special carosel box on our homepage.
{% get articles with media limit 5 as latest_articles_with_media %}
You can do similar calls to restrict by tag classifications. There are a number of other bits of syntax functionality, but I’ll leave those for another day. The tag will have extensive documentation which I will post sometime in the not-too-distant future.
One of the most important things about the get tag is that it isn’t limited to the content types built-in Courant. If you build your own Django models, you can register them with the get tag, optionally providing some custom code to handle some of the filtering logic if necessary. For example, the Section model uses custom code to determine how a string passed as a section name (such as “University News” in the example above) gets mapped to a section object, taking into account the nest-ability of sections. Because all Courant built-in models use the same registration function as your additional models will, they are not special in any way and your own code can fit in seamlessly.
from django.db import models
from courant.news.models import Article
from courant.gettag import gettag
class CustomArticle(Article):
custom_field = models.CharField(max_length=100)
gettag.register(CustomArticle)
It’s really that simple. In your templates you could then use
{% get custom_articles in section some_section as articles %}
to get all instances of your CustomArticle model in a given section. I’ll have blog posts in the future that describe more about how to write custom article and media types and how they fit cleanly into the rest of the system.
Conclusion
As I have passed the 1500 word mark, I’m going to wrap this post up. I’ve covered three of the important features of the Courant template system, and I hope it has served as an insight into the kind of thinking that went into the philosophy and design of Courant News. I’ll have more posts soon going into more detail about these features, as well as a slew of others for which I didn’t have space to talk about today.