Step 5: Templates and snippets 1

All CKAN pages are rendered from *templates* (CKAN uses Jinja2 as the template
engine) Each page that you see in the CKAN frontend has template defined on
the `ckan/templates` folder (eg the home page is rendered from
`ckan/templates/home/index.html`). Not all the code used to render a particular
page is located in the same template file though, as templates *extend* parent
files which contain common elements and can call other files which are called
*snippets*. Snippets are useful to structure different parts of the page and
hold markup that is used multiple times.

For example the template for the search page
(`ckan/templates/package/search.html`) extends the template `page.html`, which
defines the general layout of the site (header, footer, etc) and calls snippets
for the search form (`snippets/search_form.html`) and the list of results
(`snippets/package_list.html`).

Each template is divided on a series of blocks:

    {% block block_name %}

        # content

    {% endblock %}

These blocks are the ones that extensions can use to extend or modify the core
CKAN templates.

On this step we add some small customizations to the footer of our site.

First of all notice how on `plugin.py` we use the plugins toolkit to call a
function that allows us to register our own template folder on our extension.
Any templates that are present in this folder will now override the ones on
CKAN core.

We've created our templates dir in `ckanext/intro/theme/templates`.

Have a look first of all at the footer template
`ckanext/intro/theme/templates/footer.html`. First thing we added was a
`{% ckan_extends %}` tag on top. This means that we want to extend the existing
core footer template. If we wanted to *completely replace* the core one with
our own, we wouldn't include it, but you generally will want to extend them, as
it will save you a lot of code.

After these there are two blocks, which match existing blocks on the core
footer template.

We modified the left column of the footer, adding an image to it and the
central column, adding some links. There is a crucial difference between the
two though, on the left column we completely *replaced* all the core content with
our image, while on the center column we *appended* content to the existing
one.

This is because on the second block we used the `{{ super() }}` tag. This
basically gets replaced with the contents of the core parent block.

On the next step we will see a bit more about templates and snippets.

*Note*: for a proper introduction to theming and templates have a look at the
theming tutorial:

http://docs.ckan.org/en/latest/theming.html

(At the time of writing this is still a work in progress, you can have a look
 at the draft docs here:

 http://docs.ckan.org/en/847-new-theming-docs/theming.html
 )
Step 6: Templates and snippets 2, variables and helpers

This steps adds two small customizations, one to the left sidebar of the
dataset page and one to the items of the dataset listing in the search
page.

As we registered our template path on the last step, we only need to
drop the relevant template files on the same path as the ones on CKAN
core and the templates will get used.

On the dataset page template
(`ckanext/intro/theme/templates/package/read_base.html`) we extend the
`secondary_content` block adding an extra section. Again, notice the
`{{ super() }}` block that we use. If you remove it, our custom section
will be the only one displayed.

Templates have variables available that allow them render data or call
helper functions. Some of them are available to all templates. Here you
can find a list of them and what they are used for:

http://docs.ckan.org/en/latest/theming/variables-and-functions.html

(Use this if the above link is not yet available:
 http://docs.ckan.org/en/847-new-theming-docs/theming/variables-and-functions.html
)

Other variables are passed from the controllers to specific template,
like for instance the dataset being rendered on the dataset page (called
`package_dict` or `pkg`).

On our dataset page template we are calling `h`, which contains helper
functions for templates, and also using `pkg`, the variable that
contains all fields of the dataset being rendered.

We've also modified the search listing to display some extra fields from
the dataset. To do so we have modified the snippet that renders each
dataset item
(`ckanext/intro/theme/templates/snippets/package_item.html`).

Sadly snippets can not be extended at the moment, so if you want to
modify an existing core snippet you have to copy all its contents and
modify them.

In our case, we added some extra fields using the dataset variable (in
this case called `package`) and a helper function from `h`.

You can of course write your own snippets and call them from your
extension templates. Just be careful to not name them in the same way of
one of the existing core snippets.
Step 7: The public directory and custom routes

On this step we created a completely custom page (ie we didn't extend
one of the exsting ones in CKAN) which displays an image packaged with
our extension.

To be able to serve resources like images, Javascript or CSS files from
your own extension you need to register a directory as a public folder.
This is done with the `toolkit.add_public_directory` function, similarly
to how we did it for the extensions.

Once registered, files on the directory will be served from the CKAN
root level, for example:

http://yourckaninstance/images/sloth.jpg

We also mapped a custom URL (`/custom`) to a specific controllers, which
renders our custom template. This is done using the `IRoutes` interface:

http://docs.ckan.org/en/latest/extensions/plugin-interfaces.html#ckan.plugins.interfaces.IRoutes

(Note how we passed a keyword `inherit=True` when calling the
`implements` method. This is because this interface has other methods
that we are not going to use, and we are happy to use the default
version of them. If we don't use the `inherit` we will need to add all
methods to our plugin to avoid an exception)

Routes map specific URLs (like `/dataset` or `/group/<id>`) to
controllers and actions that will perform some tasks and render the
relevant template to diplay on the frontend.

We added a small controller on our plugin file which just renders our
custom template. Note how all the functions and classes used from CKAN
core are imported via the plugins toolkit.

To make easier to access our page, we added a link to our custom footer
template.
Step 8: Action and Authorization functions

A quick introduction to Action and Authorization functions. Every action that
can be performed on the web frontend or the API is implemented by an
*action function*. These are located in the `create.py`, `update.py`,
`delete.py` and `get.py` files in the `ckan/logic/action` folder. Regardless of
the interface calling the action (web, API, command line) the same function is
called. To see a list of all available actions visit:

http://docs.ckan.org/en/latest/api.html#api-reference

For each action function, there is an *authorization function*, which is called
on each action to see whether the user is authorized to perform it. The default
authorization functions are located in the `ckan/logic/auth` folder, with the
same file and function names as their actions.

Extensions can modify this default authorization function or add new ones for
their own actions implementing the `IAuthFunctions` interface.

To learn more about action and authorization functions, it is very recommended
that you read the following section of the tutorial for writing CKAN
extensions:

http://docs.ckan.org/en/latest/extensions/tutorial.html#implementing-the-iauthfunctions-plugin-interface

In our case, we have modified the default authorization to prevent users from
creating groups. Note how if you are logged in as a normal user (not a
sysadmin) you don't see the "Add Group" button on the groups page.

To do so, our plugin implements `IAuthFunctions` and adds a
`get_auth_function` methods that returns a mapping of our own auth functions.

The auth function that we need to modify is `group_create`. Auth functions need
to return a dict with a `success` key set to True if the user is authorized to
perform the action, or `False` otherwise. In our case then, we just return

   {'success': False}

You could of course, perform much more complex checks. Auth functions always
get two dicts when called: `context` and `data_dict`. `context` has
information about the request, like the user name, and gives access to the CKAN
model, which allows to query the database. `data_dict` is the data posted by
the user on the request. In our case they are the details of the group being
created.

Note: Authorization does not affect sysadmins. Sysadmins can perform any action
so there is no need to consider them on auth functions.

The tutorial for writing extensions contains a more complex example of a custom
authoriation function:

http://docs.ckan.org/en/latest/extensions/tutorial.html
Step 9: Action and Auhtorization functions 2: custom actions

On the last step we saw what action and authorization functions are and how you
register them from your plugin.

On this step, we added a new route (see Step 7), `/csv`, that when visited
outputs a CSV file with a report of all the datasets present on your CKAN
instance. The route points to a new method on our small controller, which in
turn calls an action function called `datasets_report_csv`.

This action function is defined on our `plugin.py` file, and we have registered
it using the `get_actions` method of the `IActions` interface. As with all
action and auth functions, receives a context and data_dict objects, although
in our case we won't use them.

The first thing that the action function does is check if the user is
authorized, this is done calling `toolkit.check_access` with the name of the
registered auth function. If one of your action functions needs the same
authorization as an existing action function you can of course call it as well.
For instance if you want to allow users to do some action on a dataset only if
they can edit it, you would call:

    p.toolkit.check_access('package_update', context, data_dict)

Note how our controller captures the `toolkit.NotAuhtorized` exceptions raised
by the authorization functions and aborts the request with a 401 status.

If the user is authorized (ie on our case is a sysadmin) we then call the
`package_search` action to get details of all datasets, and use the python
`csv` module to create a CSV file. The path of this file is passed to the
controller, which will read it and output it.

Note how we modify the headers of the response to reflect that we are outputing
a CSV file.

To request the dataset report, we added a link to the footer as well.