A guide to building templates for Publ.
Publ templates use the Jinja2 templating system; please see its references for the general syntax of template files.
There are three kinds of page in Publ: entry, category, and error.
How templates work
Short version
When someone requests a page, Publ finds the most-specific template that matches, and uses that to render the page.
Long version
When someone requests a page, Publ looks up whether it’s a category view or an
entry. Category views look something like
http://mysite.com/art/photos/seattle/
or http://mysite.com/blog/minimal
.
Entry views look something like http://mysite.com/art/photos/1529-unwelcome-visitor
.
Every view has a category associated with it; for example,
http://mysite.com/art/photos/1529-unwelcome-visitor
is in the art/photos
category (i.e. all the stuff between the website address and the entry ID, not
including the /
s at either end), and http://mysite.com/blog/minimal
is in
the blog
category; the category is basically everything between the server
name and the last /
(in this case, minimal
is the view).
When you see an entry, there is only one possible template that it chooses,
entry
. When you see a category, if there’s a view indicated, it uses that view
name; otherwise it uses the index
view.
(The category can be blank, incidentally; http://mysite.com/
shows you the
(index
view on the empty category.)
Anyway, given the category and view name, Publ looks
for the closest matching template, by starting out in the template directory
that matches the category name, and then going up one level until it finds a
matching template. And a template will match based on either the exact name, or
the name with .html
, .htm
, .xml
, or .json
added to the end.
So for example, if you ask for http://mysite.com/music/classical/baroque/feed
,
it will look for a template file named feed
, feed.html
, feed.htm
,
feed.xml
, or feed.json
, in the directories
templates/music/classical/baroque
, templates/music/classical
,
templates/music
, and templates
, in that order, returning the first one that
matches. (If no templates match, it shows an error page.)
Note that the default names of entry
and index
can be overridden on a per-entry or
per-category basis.
Error templates
A note on error templates: Error pages generally get handled by whatever
matches the error
template; however, in the case of a specific status code, it
will also look for a template named based on that code and its category. For example, a 404 error
will try to render the 404
template first, then try 400
, then finally error
.
(And of course this can be 404.html
, 404.xml
, 404.json
, and so on,
although in most cases you’ll probably be doing it as HTML.)
Login/logout template
Unlike most templates, login.html
and logout.html
do not support per-category overrides.
Generating CSS files
You can also use the template system to generate CSS, which is useful for having
your CSS files reference other assets via static()
or image()
. This also
makes it quite simple to create CSS templates that inherit from and override
other CSS templates; for example, if you have the file templates/style.css
,
you can have templates/blog/style.css
that looks like:
and then your HTML templates need only refer to the stylesheet using, for example:
Special templates
Technically no template is actually required, but in order to have a functioning site, you should have, at the very least, the following top-level templates:
index.html
: the default category viewentry.html
: the default entry view
The following templates are optional; if they are not provided, Publ will use a built-in default:
error.html
: the generic error page (used if no more specific error template is available)unauthorized.html
: the error page specifically for attempting to access an entry that the logged-in user doesn’t have access tologin.html
: the login pagelogout.html
: the interstitial logout page, if someone visits the logout page via link or manual URL entry (rather than a form submission)
Template naming and overrides
Publicly-visible templates' names must start with a letter, and should only consist of URL-friendly characters (alphanumerics, periods, dashes, and underscores).
Numeric error templates are not considered publicly-visible.
If a template’s name begins with _
the template will be considered “private;”
it will be available internally (such as to Entry-Template
,Index-
Template
, or get_template()
) but it will not be available to the world at large. So, for example,
if you want to override your site’s main page you can create a file like
/content/mainpage.cat
:
which will cause it to render with the template templates/_main_page.html
, but
will not allow other URLs e.g. /blog/_main_page
to use this template.
Simiarly, it is highly recommended that Entry-Template
overrides use this
convention, as it usually does not make sense to render an entry template in a
category context.
Custom filters
Publ provides the following custom filters for use in templates.
strip_html
This filter allows conditional stripping of HTML elements, similar to the built-in striptags
filter except with a bit more flexibility:
allowed_tags
: a string or list of strings for tags to preserve in the outputallowed_attrs
: a string or list of strings for attributes to preserve in preserved tagsremove_elements
: a string or list of strings for tags to completely remove, including their children and text content
For example, with the following template:
and the following entry body:
the template output will be similar to:
Note that it preserves the <a>
and its href
attribute (but not its title
), but it strips the <em>
tag and completely removes the <del>
and its contents.
This provides greater flexibility than passing markup=False
to various template elements such as entry.title
or entry.body
.
first_paragraph
This filter lets you extract the first paragraph from a chunk of text, optionally stripping the enclosing <p>
tag, but preserving all other markup. Pass the output through strip_html
if you need to remove more tags.
Template API
As mentioned before, templates are rendered using Jinja2, and get standard template parameters for both Jinja2 and Flask. Additionally, there will be other things available to you, depending on the template type.
Also, note that the templating system uses Python syntax for passing parameters
to functions. So, for example, some_function(1,dingle=True,berry="none")
passes a number, a boolean (true/false) named dingle
, and a string named
berry
with the value of "none"
. When passing strings, quotes are required.
When passing anything else, the quotes should be left off; False
and "False"
mean very different things in Python. (For starters, "False"
is equivalent to
True
in many contexts.)
All templates
All template types get the default Flask objects; there is more information about these on the Flask templating reference.
The following additional things are provided to all templates:
arrow
: The Arrow time and date libraryget_view
: Requests a view of entries; see the view documentation for the supported arguments.static
: Build a link to a static resource. The first argument is the path within the static resources directory; it also takes the following optional named arguments:absolute
: Whether to force this link to be absoluteFalse
: Use a relative link if possible (default)True
: Use an absolute link
get_entry
: A function that retrieves anentry
object with the given ID. This is a much more efficient way of achieving something like:get_template
: A function that finds a template file for a given category or entry. The first argument is the name of the template to find or a list of template names; the second argument is what to find the template relative to (entry, category, or a file path).Example usage:
This fragment will find all of the entries below the current category, and then render the closest matching
_entry
template for that category, with the filenames mapped as described above. This is useful for having content where its appearance within the template changes based on the category it’s in; for example, if you want your Atom feed to show full content for only some parts of your site, or if you want image thumbnails to generate differently.Note: The included template will only see the variables that are visible at that point in the template. If you need to set a variable that would normally be visible to the template, use the Jinja
{% set %}
command; for example, if the template fragment expects to get a variable namedsubcat
and you want it to refer to the category, you’d do something like:template
: Information about the current templateuser
: Information about the current userimage
: Load an imageThe resulting image works as a URL directly, or you can pass in arguments in the format
(output_scale, [arguments])
, which gets a URL rendered with the specified output scaling and image rendition arguments. The default is to use anoutput_scale
of 1.It also has the following functions on it:
get_img_tag([arguments])
: Makes an<img>
tag with the provided image rendition arguments, with the following additions:alt_text
: Provides text for theimg
’salt
attributetitle
: Provides text for theimg
’stitle
attribute
get_css_background([arguments])
: Makes the appropriatebackground-image
CSS attributes with the provided image rendition arguments, with the following additions:uncomment
: Whether the CSS declaration will be wrapped inside a comment; this is useful if your code editor would otherwise get confused by Jinja declarations inside of CSS.
get_image_attrs([arguments])
: Generates the raw attributes that would be applied to the<img>
tag produced byget_img_tag
. This is probably not useful for templating, however.
Example usage in HTML templates:
and in a CSS template:
Like the entry Markdown tags, you can use a
@
prefix to indicate that the image comes from the static files (rather than from the template’s search path), and external URLs work as well.Unlike the entry Markdown tags, you have to specify width and height by name.
If the image path is absolute (i.e. starts with a
/
), it will be treated as based on the site’s content directory. If it is relative, it will look in the following locations, in the following order:- Relative to the current entry’s file (on entry templates)
- Relative to the current entry’s category within the content directory (on entry templates)
- Relative to the current category within the content directory
- Relative to the template file, mapped to the content directory
(for example, the template
templates/blog/feed.xml
will search incontent/blog
)
login
: Provide a login linkThis can be used directly, i.e.
{{login}}
, or it can be given a redirection path for where to redirect on successful login; for example, if you want to redirect back to the category rather than the current page, you can use:You can also pass in additional parameters to the login handler; for example, it will take a named
me
parameter to provide an identity to start logging in as, although this isn’t generally useful except for test purposes (such as providing quicktest:whatever
links).This will automatically upgrade the URL to
https:
if the site is configured withAUTH_FORCE_HTTPS
.The optional parameter
absolute
will treat the link as absolute; for example,will render as e.g.
logout
: Provide a logout linkThis can be used directly, i.e.
{{logout}}
, or it can be given a redirection path as withlogin
.If this is used as a link target, the user will be given a logout confirmation dialog (this is to prevent certain issues with browsers prefetching pages). If you would like the user to not need to confirm the logout, use it in a
<form method="POST">
. For example:will show a logout confirmation dialog (using the
logout.html
template), whereaswill not.
This will automatically upgrade the URL to
https:
if the site is configured withAUTH_FORCE_HTTPS
.The optional parameter
absolute
will treat the link as absolute; for example,will render as e.g.
secure_url
: A version ofurl_for()
which will automatically upgrade the URL tohttps:
if the site is configured withAUTH_FORCE_HTTPS
.search
: Perform a full-text search on the site contents.If the application is configured with full-text search, this function will allow querying the search index. It takes the following parameters:
query
: The query to perform. This uses the Woosh query syntax, and the following fields are supported:content
: The textual content of the entry body (default)title
: The title of the entrydate
: The publication date of the entrytag
: Visible entry tags
category
: The category to search within; defaults to all categoriesrecurse
: IfTrue
, will include subcategories; ifFalse
, only searches the current category (default)count
: The maximum number of entries to retrieve, orNone
for no limit (default)page
: The page of results to retrievefuture
: Whether to include future scheduled entries
This returns a search result set, which has an API similar to a view; notably, it provides the following properties:
entries
: The search resultsThis takes an optional parameter,
unauthorized
, which indicates the maximum number of unauthorized entries to include in the list (None
will include all of them).has_unauthorized
: Whether there are unauthorized entries
This API is experimental and subject to change.
Example usage:
}
token_endpoint
: Gets the URL of the built-in IndieAuth token endpoint.
A note to advanced Flask users: while url_for()
is available, it shouldn’t
ever be necessary, as all its useful functionality is exposed via the available
objects. If you need to use the endpoints from the Python side, please see the
Python Flask endpoints.
Entry pages
Specific entries are always rendered with its category’s entry
template.
The template gets the following additional objects:
Category pages
Categories are rendered with whatever template is specified, defaulting to
index
if none was specified. The template gets the following additional
objects:
category
: Information about the categoryview
: The default view for this category. It is equivalent to callingget_view()
with the following arguments:category
: This categoryrecurse
:False
date
,first
,last
,before
,after
: set by the URL query parameters
Note that unless any limiting parameters are set, this
view
object will not have any pagination on it. The intention is thatview
will be used as a basis for another more specific view; for example, this example will show 10 entries at a time and getprevious
andnext
as appropriate:
Error pages
Error templates receive an error
object to indicate which error occurred;
otherwise it only gets the default stuff. This object has the following
properties:
code
: The associated HTTP error codemessage
: An explanation of what went wrongcategory
: Information about the categoryexception
: In the case of an internal error, this will be an object with the following properties:type
: The human-readable type of exception (IOError
,ValueError
, etc.)str
: The human-readable exception stringargs
: Further information passed to the exception constructor
Login page
The login template receives the following:
login_url
: A URL for the login form’sACTION
parameterauth
: The authentication handler object, which in turn has the following useful things:handlers
: A list of configured handlers
Each of the configured handlers has the following that is useful in a template:
service_name
: A human-readable name for the servicedescription
: A human-readable description of the service (may contain HTML)url_schemes
: A list of example URL schemes; this is provided as a tuple of(template,placeholder)
. For example, a Twitter handler might provide("https://twitter.com/%","username")
as a scheme.
A simple login.html
might look like this:
Note that the login template uses Flask message flashing to provide error feedback for a failed login.
Unauthorized page
The unauthorized template receives the same data as the entry template. The entry
object will be sanitized and only provide limited information, and the category
object will be based on the URL used to access the entry, rather than the actual category of the entry.
You can access the default login stylesheet with {{url_for('login',asset='css')}}
, if so desired.
Logout page
The logout template does not receive any special variables, and should only provide a logout form with <form method="POST">
which will confirm the logout on submission.