CKAN pages are generated from Jinja2 template files. This tutorial will walk you through the process of writing your own template files to modify and replace the default ones, and change the layout and content of CKAN pages.
A CKAN theme is simply a CKAN plugin that contains some custom templates and static files, so before getting started on our CKAN theme we'll have to create an extension and plugin. For a detailed explanation of the steps below, see /extensions/tutorial
.
Use the
paster create
command to create an empty extension:cd /src paster --plugin=ckan create -t ckanext
Create the file with the following contents:
/../ckanext/example_theme/v01_empty_extension/plugin.py
Edit the
entry_points
in to look like this:entry_points=''' [ckan.plugins] example_theme=ckanext.example_theme.plugin:ExampleThemePlugin ''',
Run
python setup.py develop
:cd python setup.py develop
Add the plugin to the
ckan.plugins
setting in your file:ckan.plugins = stats text_preview recline_preview example_theme
Start CKAN in the development web server:
$ paster serve --reload Starting server in PID 13961. serving on 0.0.0.0:5000 view at http://127.0.0.1:5000
Open the CKAN front page in your web browser. If your plugin is in the
ckan.plugins
setting and CKAN starts without crashing, then your plugin is installed and CKAN can find it. Of course, your plugin doesn't do anything yet.
Every CKAN page is generated by rendering a particular template. For each page of a CKAN site there's a corresponding template file. For example the front page is generated from the ckan/templates/home/index.html
file, the /about
page is generated from ckan/templates/home/about.html
, the datasets page at /dataset
is generated from ckan/templates/package/search.html
, etc.
To customize pages, our plugin needs to register its own custom template directory containing template files that override the default ones. Edit the file that we created earlier, so that it looks like this:
/../ckanext/example_theme/v02_empty_template/plugin.py
This new code does a few things:
It imports CKAN's plugins toolkit module:
/../ckanext/example_theme/v02_empty_template/plugin.py
The plugins toolkit is a Python module containing core functions, classes and exceptions for CKAN plugins to use. For more about the plugins toolkit, see
/extensions/tutorial
.It calls :py
~ckan.plugins.implements
to declare that it implements the :py~ckan.plugins.interfaces.IConfigurer
plugin interface:/../ckanext/example_theme/v02_empty_template/plugin.py
This tells CKAN that our :py
~ckanext.example_theme.v2.plugin.ExampleThemePlugin
class implements the methods declared in the :py~ckan.plugins.interfaces.IConfigurer
interface. CKAN will call these methods of our plugin class at the appropriate times.It implements the :py
~ckan.plugins.interfaces.IConfigurer.update_config
method, which is the only method declared in the :py~ckan.plugins.interfaces.IConfigurer
interface:/../ckanext/example_theme/v02_empty_template/plugin.py
CKAN will call this method when it starts up, to give our plugin a chance to modify CKAN's configuration settings. Our :py
~ckanext.example_theme.v2.plugin.ExampleThemePlugin.update_config
method calls :py~ckan.plugins.toolkit.add_template_directory
to register its custom template directory with CKAN. This tells CKAN to look for template files in whenever it renders a page. Any template file in this directory that has the same name as one of CKAN's default template files, will be used instead of the default file.
Now, let's customize the CKAN front page. We first need to discover which template file CKAN uses to render the front page, so we can replace it. Set debug
to true
in your file:
[DEFAULT]
# WARNING: *THIS SETTING MUST BE SET TO FALSE ON A PRODUCTION ENVIRONMENT*
debug = true
Reload the CKAN front page in your browser, and you should see a Debug link in the footer at the bottom of the page. Click on this link to open the debug footer. The debug footer displays various information useful for CKAN frontend development and debugging, including the names of the template files that were used to render the current page:
The first template file listed is the one we're interested in:
Template name: home/index.html
Template path: /usr/lib/ckan/default/src/ckan/ckan/templates/home/index.html
This tells us that home/index.html
is the root template file used to render the front page. The debug footer appears at the bottom of every CKAN page, and can always be used to find the page's template files, and other information about the page.
Note
Most CKAN pages are rendered from multiple template files. The first file listed in the debug footer is the root template file of the page. All other template files used to render the page (listed further down in the debug footer) are either included by the root file, or included by another file that is included by the root file.
To figure out which template file renders a particular part of the page you have to inspect the source code of the template files, starting with the root file.
Now let's override home/index.html
using our plugins' custom templates
directory. Create the directory, create a home
directory inside the templates
directory, and create an empty index.html
file inside the home
directory:
- /
- ckanext/
- example_theme/
- templates/
- home/
index.html <-- An empty file.
If you now restart the development web server (kill the server using Ctrl-c, then run the paster serve
command again) and reload the CKAN front page in your web browser, you should see an empty page, because we've replaced the template file for the front page with an empty file.
Note
If you run paster serve
with the --reload
option, then it isn't usually necessary to restart the server after editing a Python file, a template file, your CKAN config file, or any other CKAN file. If you've added a new file or directory, however, you need to restart the server manually.
CKAN template files are written in the Jinja2 templating language. Jinja template files, such as our index.html
file, are simply text files that, when processed, generate any text-based output format such as HTML
, XML
, CSV
, etc. Most of the template files in CKAN generate HTML
.
We'll introduce some Jinja2 basics below. Jinja2 templates have many more features than these, for full details see the Jinja2 docs.
Jinja2 expressions are snippets of code between {{ ... }}
delimiters, when a template is rendered any expressions are evaluated and replaced with the resulting value.
The simplest use of an expression is to display the value of a variable, for example {{ foo }}
in a template file will be replaced with the value of the variable foo
when the template is rendered.
CKAN makes a number of global variables available to all templates. One such variable is app_globals
, the Pylons app_globals object, which can be used to access certain global attributes including some of the settings from your CKAN config file. For example, to display the value of the ckan.site_title
setting from your config file you would put this code in any template file:
/../ckanext/example_theme/v03_jinja/templates/home/index.html
Note
Not all config settings are available to templates via app_globals
. The sqlalchemy.url
setting, for example, contains your database password, so making that variable available to templates might be a security risk.
If you've added your own custom options to your config file, these will not be available in app_globals
.
Insert cross-ref to custom config options section.
Note
Jinja2 expressions can do much more than print out the values of variables, for example they can call Jinja2's global functions, CKAN's template helper functions <template helper functions>
and any custom template helper functions <custom template helper functions>
provided by your extension, and use any of the literals and operators that Jinja provides.
See variables-and-functions
for a list of variables and functions available to templates.
ckan.site_title
is an example of a simple string variable. Some variables, such as ckan.plugins
, are lists, and can be looped over using Jinja's {% for %}
tag.
Jinja tags are snippets of code between {% ... %}
delimiters that control the logic of the template. For example, we can output a list of the currently enabled plugins with this code in any template file:
/../ckanext/example_theme/v03_jinja/templates/home/index.html
Other variables, such as ckan.tracking_enabled
, are booleans, and can be tested using Jinja's {% if %}
tag:
/../ckanext/example_theme/v03_jinja/templates/home/index.html
Finally, any text between {# ... #}
delimiters in a Jinja2 template is a comment, and will not be output when the template is rendered:
/../ckanext/example_theme/v03_jinja/templates/home/index.html
- Mention what happens if you try to access a variable or attribute that doesn't exist.
- Mention filters. And can ckan template helper functions be used as filters?
CKAN provides a custom Jinja tag {% ckan_extends %}
that we can use to declare that our home/index.html
template extends the default home/index.html
template, instead of completely replacing it. Edit the empty index.html
file you just created, and add one line:
/../ckanext/example_theme/v04_ckan_extends/templates/home/index.html
If you now reload the CKAN front page in your browser, you should see the normal front page appear again. When CKAN processes our index.html
file, the {% ckan_extends %}
tag tells it to process the default home/index.html
file first.
Jinja templates can contain blocks that child templates can override. For example, CKAN's default home/layout1.html
template (one of the files used to render the CKAN front page) has a block that contains the Jinja and HTML code for the "featured group" that appears on the front page:
/../ckan/templates/home/layout1.html
This code calls a template snippet <snippets>
that contains the actual Jinja and HTML code for the featured group, more on snippets later.
When a custom template file extends one of CKAN's default template files using {% ckan_extends %}
, it can replace any of the blocks from the default template with its own code by using {% block %}
. Create the file with these contents:
/../ckanext/example_theme/v05_block/templates/home/layout1.html
This file extends the default layout1.html
template, and overrides the featured_group
block. Reload the CKAN front page in your browser. You should see that the featured groups section of the page has been replaced, but the rest of the page remains intact.
Note
Most template files in CKAN contain multiple blocks. To find out what blocks a template has, and which block renders a particular part of the page, you have to look at the source code of the default template files.
If you want to add some code to a block but don't want to replace the entire block, you can use Jinja's {{ super() }}
tag:
/../ckanext/example_theme/v06_super/templates/home/layout1.html
When the child block above is rendered, Jinja will replace the {{ super() }}
tag with the contents of the parent block. The {{ super() }}
tag can be placed anywhere in the block.
Now let's put some interesting content into our custom template block. One way for templates to get content out of CKAN is by calling CKAN's template helper functions.
For example, let's replace the featured group on the front page with an activity stream of the site's recently created, updated and deleted datasets. Change the code in to this:
/../ckanext/example_theme/v07_helper_function/templates/home/layout1.html
Reload the CKAN front page in your browser and you should see a new activity stream.
To call a template helper function we use a Jinja2 expression (code wrapped in {{ ... }}
brackets), and we use the global variable h
(available to all templates) to access the helper:
/../ckanext/example_theme/v07_helper_function/templates/home/layout1.html
To see what other template helper functions are available, look at the template helper functions reference docs <template-helper-functions>
.
Plugins can add their own template helper functions by implementing CKAN's :py~ckan.plugins.interfaces.ITemplateHelpers
plugin interface. (see /extensions/tutorial
for a detailed explanation of CKAN plugins and plugin interfaces).
Let's add another item to our custom front page: a list of the most "popular" groups on the site (the groups with the most datasets). We'll add a custom template helper function to select the groups to be shown. First, in our plugin.py
file we need to implement :py~ckan.plugins.interfaces.ITemplateHelpers
and provide our helper function. Change the contents of plugin.py
to look like this:
/../ckanext/example_theme/v08_custom_helper_function/plugin.py
We've added a number of new features to plugin.py
. First, we defined a function to get the most popular groups from CKAN:
/../ckanext/example_theme/v08_custom_helper_function/plugin.py
This function calls one of CKAN's action functions to get the groups from CKAN. See /extensions/tutorial
for more about action functions.
Next, we called :py~ckan.plugins.implements
to declare that our class now implements :py~ckan.plugins.interfaces.ITemplateHelpers
:
/../ckanext/example_theme/v08_custom_helper_function/plugin.py
Finally, we implemented the :py~ckan.plugins.interfaces.ITemplateHelpers.get_helpers
method from :py~ckan.plugins.interfaces.ITemplateHelpers
to register our function as a template helper:
/../ckanext/example_theme/v08_custom_helper_function/plugin.py
Now that we've registered our helper function, we need to call it from our template. As with CKAN's default template helpers, templates access custom helpers via the global variable h
. Edit to look like this:
/../ckanext/example_theme/v08_custom_helper_function/templates/home/layout1.html
Now reload your CKAN front page in your browser. You should see the featured organization section replaced with a list of the most popular groups.
Simply displaying a list of group titles isn't very good. We want the groups to be hyperlinked to their pages, and also to show some other information about the group such as its description and logo image. To display our groups nicely, we'll use CKAN's template snippets.
Template snippets are small snippets of template code that, just like helper functions, can be called from any template file. To call a snippet, you use another of CKAN's custom Jinja2 tags: {% snippet %}
. CKAN comes with a selection of snippets, which you can find in the various snippets
directories in ckan/templates/
, such as ckan/templates/snippets/
and ckan/templates/package/snippets/
.
Autodoc all the default snippets, link to reference docs.
ckan/templates/group/snippets/group_list.html
is a snippet that renders a list of groups nicely (it's used to render the groups on CKAN's /group
page and one user dashboard pages, for example):
/../ckan/templates/group/snippets/group_list.html
(As you can see, this snippet calls another snippet, group_item.html
, to render each individual group.)
Let's change our file to call this snippet:
/../ckanext/example_theme/v09_snippet/templates/home/index.html
Here we pass two arguments to the {% snippet %}
tag:
/../ckanext/example_theme/v09_snippet/templates/home/index.html
the first argument is the name of the snippet file to call. The second argument, separated by a comma, is the list of groups to pass into the snippet. After the filename you can pass any number of variables into a snippet, and these will all be available to the snippet code as top-level global variables. As in the group_list.html
docstring above, each snippet's docstring should document the parameters it requires.
If you reload your CKAN front page in your web browser now, you should see the dataset of the day rendered nicely.
Just as plugins can add their own template helper functions, they can also add their own snippets. To add template snippets, all a plugin needs to do is add a snippets
directory in its templates
directory, and start adding files. The snippets will be callable from other templates immediately.
Note
For CKAN to find your plugins' snippets directories, you should already have added your plugin's custom template directory to CKAN, see template
overriding
.
Let's create a custom snippet to display our most popular groups, and put the <h3>Most popular groups</h3>
heading and the code to call the helper function to retrieve the groups into the snippet, so that we can reuse the whole thing on different parts of the site if we want to.
Create a new directory containing a file named example_theme_most_popular_groups.html
with these contents:
/../ckanext/example_theme/v10_custom_snippet/templates/snippets/example_theme_most_popular_groups.html
Explain the HTML and CSS being used in the snippet above, and where it comes from.
Now edit your file and change it to use our new snippet instead of the default one:
/../ckanext/example_theme/v10_custom_snippet/templates/home/index.html
Warning
Default snippets can be overridden. If a plugin adds a snippet with the same name as one of CKAN's default snippets, the plugin's snippet will override the default snippet wherever the default snippet is used.
Also if two plugins both have snippets with the same name, one of the snippets will override the other. <-- TODO: Verify whether this is true
To avoid unintended conflicts, we recommend that snippet filenames begin with the name of the extension they belong to, e.g. snippets/example_theme_*.html
.
Exactly what order are snippets
directories read in, and what overrides what?
Note
Snippets don't have access to the global template context variable, c
(see variables-and-functions
). Snippets can access other global variables such as h
, app_globals
and request
, as well as any variables explicitly passed into the snippet by the parent template when it calls the snippet with a {% snippet %}
tag.
We should probably remove any CSS classes from earlier examples and leave them unstyled, introduce CSS for the first time here.
In the custom snippet example above we used some HTML tags and CSS classes to make the most popular groups look good and fit into the rest of the CKAN theme, for example we used <div class="box">
, <section class="module">
and <header class="module-heading">
. You might be wondering where these tags and classes come from, and how a theme developer knows what CSS classes are available in CKAN and what HTML and CSS they should use to make their custom templates fit into the CKAN theme as a whole.
There are two places to look for CSS classes available in CKAN:
- The Bootstrap 2.3.2 docs. All of the HTML, CSS and JavaScript provided by Bootstrap is available to use in CKAN.
CKAN's development primer page, which can be found on any CKAN site at
/development/primer.html
, for example demo.ckan.org/development/primer.html.The primer page demonstrates many of the HTML and CSS elements available in CKAN, and by viewing the source of the page you can see what HTML tags and CSS classes they use.
Insert a link to the frontend style guide when it's finished, which will be much better than the primer.
Let's make our recently changed datasets activity stream and most popular groups look better by laying them out and styling them. Change your home/index.html
template to look like this:
This example needs fixing. The activity stream should be in a snippet like the groups are. The group_item.html
snippet is not really meant to be used the way it's being used here and as a result the CSS is wrong (also the template crashes unless you hack it).
/../ckanext/example_theme/v11_HTML_and_CSS/templates/home/index.html
This new template uses Bootstrap's grid system to layout the two main elements. Bootstrap's grid system lays out the main elements of a page using rows and columns. You define a row with a <div class="row">
tag, and then within the row you define columns with <div class="span6">
tags:
<div class="row">
<div class="span6">
<!-- Contents of the left column go here. -->
</div>
<div class="span6">
<!-- Contents of the right column go here. -->
</div>
</div>
(The number in span6
defines the width of the column, it can be anything from span1
to span12
, but all of the columns in a given row cannot add up to more than 12.)
Within the left column, we use some CKAN CSS classes box
, module
, module-heading
and module-content
, this uses CKAN's system of CSS modules to draw the elements in nice, visually distinct sections with associated titles:
<div class="box">
<section class="module">
<header class="module-heading">
<h3>Recent activity</h3>
</header>
<div class="module-content">
{{ h.recently_changed_packages_activity_stream(limit=5) }}
</div>
</section>
</div>
Change your snippets/example_theme_most_popular_groups.html
template to look like this:
/../ckanext/example_theme/v11_HTML_and_CSS/templates/snippets/example_theme_most_popular_groups.html
This uses the same box
and module-heading
classes to draw a box around the most popular groups.
Now reload your CKAN front page in your browser, you should see the activity stream and groups lists layed out and styled nicely.