From e77af5622321212239116b4014ea501600b2165e Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Mon, 9 Sep 2013 14:45:08 +0200 Subject: [PATCH] [#847] Lots of theming docs tweaks Use substitutions for lots of things, and also lots of text tweaks, new stuff about how to find out which template a page uses, global variables, etc. --- doc/theming.rst | 373 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 268 insertions(+), 105 deletions(-) diff --git a/doc/theming.rst b/doc/theming.rst index 268888e3c74..494d5320733 100644 --- a/doc/theming.rst +++ b/doc/theming.rst @@ -1,6 +1,12 @@ .. _Jinja2: http://jinja.pocoo.org/ +.. _CKAN front page: http://127.0.0.1:5000 + +.. |extension_dir| replace:: ``ckanext-example_theme`` +.. |setup.py| replace:: ``ckanext-example_theme/setup.py`` ``ckan.plugins`` +.. |plugin.py| replace:: ``ckanext-example_theme/ckanext/example_theme/plugin.py`` +.. |index.html| replace:: ``ckanext-example_theme/ckanext/example_theme/templates/home/index.html`` +.. |snippets_dir| replace:: ``ckanext-example_theme/ckanext/example_theme/templates/snippets`` -.. |extension_dir| replace:: ckanext-example_theme ======= Theming @@ -11,16 +17,19 @@ If you just want to do some simple customizations such as changing the title of your CKAN site, or making some small CSS customizations, :doc:`getting-started` documents some simple configuration settings you can use. -If you want more control, follow the tutorial below to learn how to develop -your custom CKAN theme. +If you want more control, CKAN themes can customize all aspects of CKAN's +frontend, including changing any of CKAN's templates or template snippets, +adding custom snippets and helper functions, customizing CSS and JavaScript, +and adding static files such as images. Follow the tutorial below to learn how +to develop your custom CKAN theme. ---------------- Theming tutorial ---------------- -This tutorial walks you through the process of creating a CKAN theme, and -demonstrates all of the main features of CKAN theming. +This tutorial walks you through the process of creating an example CKAN theme +that demonstrates all of the main features of CKAN theming. Installing CKAN @@ -48,12 +57,11 @@ an extension and plugin. For a detailed explanation of the steps below, see cd |virtualenv|/src paster --plugin=ckan create -t ckanext |extension_dir| -2. Create the file ``|extension_dir|/ckanext/example_theme/plugin.py`` - with the following contents: +2. Create the file |plugin.py| with the following contents: .. literalinclude:: ../ckanext/example_theme/v1/plugin.py -3. Edit the ``entry_points`` in ``|extension_dir|/setup.py``:: +3. Edit the ``entry_points`` in |setup.py|:: entry_points=''' [ckan.plugins] @@ -77,21 +85,18 @@ an extension and plugin. For a detailed explanation of the steps below, see .. parsed-literal:: - $ paster serve |development.ini| + $ paster serve --reload |development.ini| Starting server in PID 13961. serving on 0.0.0.0:5000 view at http://127.0.0.1:5000 - If your plugin is in the :ref:`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. - - -Customizing CKAN's HTML with Jinja2 -=================================== + Open the `CKAN front page`_ in your web browser. If your plugin is in the + :ref:`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. -Replacing a default template file ---------------------------------- +Customizing CKAN's HTML with Jinja2 templates +============================================= 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 @@ -100,13 +105,13 @@ front page is generated from the ``ckan/templates/home/index.html`` file, the datasets page at ``/dataset`` is generated from ``ckan/templates/package/search.html``, etc. -.. todo:: - Explain how to find out which template file is used for a given page. +Replacing a default template file +--------------------------------- To customize pages, our plugin needs to register its own custom template directory containing templates file that override the default ones. -Edit the ``plugin.py`` file that we created earlier, so that it looks like +Edit the |plugin.py| file that we created earlier, so that it looks like this: .. literalinclude:: ../ckanext/example_theme/v2/plugin.py @@ -115,7 +120,7 @@ This new code does a few things: 1. It imports CKAN's *plugins toolkit* module: - .. literalinclude:: ../ckanext/example_theme/plugin_v2.py + .. literalinclude:: ../ckanext/example_theme/v2/plugin.py :start-after: import ckan.plugins as plugins :end-before: class ExampleThemePlugin(plugins.SingletonPlugin): @@ -125,8 +130,15 @@ This new code does a few things: 2. It calls :py:func:`~ckan.plugins.core.implements` to declare that it implements the :py:class:`~ckan.plugins.interfaces.IConfigurer` plugin - interface. This tells CKAN that our - :py:class:`~ckanext.example_theme.plugin_v2.ExampleThemePlugin` class + interface: + + .. literalinclude:: ../ckanext/example_theme/v2/plugin.py + :start-after: # Declare that this class implements IConfigurer. + :end-before: def update_config( + + + This tells CKAN that our + :py:class:`~ckanext.example_theme.v2.plugin.ExampleThemePlugin` class implements the methods declared in the :py:class:`~ckan.plugins.interfaces.IConfigurer` interface. CKAN will call these methods of our plugin class at the appropriate times. @@ -134,62 +146,95 @@ This new code does a few things: 3. It implements the :py:meth:`~ckan.plugins.interfaces.IConfigurer.update_config` method, which is the only method declated in the - :py:class:`~ckan.plugins.interfaces.IConfigurer` interface. CKAN will call - this method when it starts up, to give our plugin a chance to modify CKAN's - configuration settings. + :py:class:`~ckan.plugins.interfaces.IConfigurer` interface: + + .. literalinclude:: ../ckanext/example_theme/v2/plugin.py + :pyobject: ExampleThemePlugin.update_config -4. Finally, our - :py:meth:`~ckanext.example_theme.plugin_v2.ExampleThemePlugin.update_config` + CKAN will call this method when it starts up, to give our plugin a chance to + modify CKAN's configuration settings. Our + :py:meth:`~ckanext.example_theme.v2.plugin.ExampleThemePlugin.update_config` method calls :py:func:`~ckan.plugins.toolkit.add_template_directory` to - register its custom template directory with CKAN: + register its custom template directory with CKAN. + This tells CKAN to look for template files in |templates_dir| 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 :ref:`debug` to ``true`` in your |development.ini| 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 name of the template file +that was used to render the current page: + +.. todo:: Insert a screenshot of the debug link. + +:: + + 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 main 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, and other information about the +page. - .. literalinclude:: ../ckanext/example_theme/plugin_v2.py - :start-after: # that CKAN will use this plugin's custom templates. +Now let's override ``home/index.html`` using our plugins' custom ``templates`` +directory. Create the |templates_dir| directory, create a ``home`` directory +inside the ``templates`` directory, and create an empty ``index.html`` file +inside the ``home`` directory: - This tells CKAN to look for template files in the - ``|extension_dir|/ckanext/example_theme/templates/`` 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. +.. parsed-literal:: -Now, let's customize the CKAN front page. Create the -``|extension_dir|/ckanext/example_theme/templates/`` directory, create a -``home`` directory inside the ``templates`` directory, and create an empty -``index.html`` file inside the ``home`` directory:: + |extension_dir|/ + ckanext/ + example_theme/ + templates/ + home/ + index.html <-- An empty file. - |extension_dir|/ - ckanext/ - example_theme/ - templates/ - home/ - index.html <-- An empty file. +If you now 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. -Restart the development server (``paster serve development.ini``), and open the -CKAN front page (`127.0.0.1:5000 `_ by default) 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. +Jinja2 +------ -Extending default templates with ``{% ckan_extends %}`` -------------------------------------------------------- +.. todo:: Brief introduction to Jinja2 here. 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 templates file in CKAN generate ``HTML``. -In Jinja templates snippets of text like ``{% ... %}`` are -Jinja tags that control the logic of the template. For example, 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. +In Jinja templates snippets of text like ``{% ... %}`` are Jinja *tags* that +control the logic of the template. + + +Extending templates with ``{% ckan_extends %}`` +----------------------------------------------- + +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: .. literalinclude:: ../ckanext/example_theme/v3/templates/home/index.html -If you now restart the development server and 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 ``index.html`` file first. +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. Replacing template blocks with ``{% block %}`` @@ -205,7 +250,8 @@ by default:: {% for group in c.group_package_stuff %}
- {% snippet 'snippets/group_item.html', group=group.group_dict, truncate=50, truncate_title=35 %} + {% snippet 'snippets/group_item.html', group=group.group_dict, + truncate=50, truncate_title=35 %}
{% endfor %} @@ -216,8 +262,6 @@ by default:: Fix ``c.group_package_stuff`` above (stupid name). -.. todo:: Fix the line wrapping in the code sample above - .. todo:: Insert screenshot of the part of the page that this template renders? When a custom template file extends one of CKAN's default template files using @@ -227,30 +271,28 @@ file again and change the contents to: .. literalinclude:: ../ckanext/example_theme/v4/templates/home/index.html -Restart the development server, and reload the CKAN front page in your browser. +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. -.. topic:: Extending parent blocks with Jinja's ``{{ super() }}`` +.. todo:: + + Explain how to find out what blocks a given template provides. + Do you have to just look at the source? - 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:: - {% ckan_extends %} +Extending parent blocks with Jinja's ``{{ super() }}`` +------------------------------------------------------ - {% block secondary_content %} - {{ super() }} - Hello block world! - {% endblock %} +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: - When the child block above is rendered, Jinja will replace the - ``{{ super() }}`` tag with the contents of the parent block. +.. literalinclude:: ../ckanext/example_theme/v5/templates/home/index.html - .. todo:: Make the ``super()`` example above into a proper included example. +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. -.. todo:: Need something here about what variables are available to templates: - c, h, g. etc. plus anything explicitly passed in by the controller or parent - template. Template helper functions ------------------------- @@ -261,13 +303,12 @@ One way for templates to get content out of CKAN is by calling CKAN's For example, let's replace the featured groups on the front page with an activity stream of the site's recently created, updated and deleted datasets. -Change the code in ``index.html`` to this: +Change the code in |index.html| to this: .. literalinclude:: ../ckanext/example_theme/v6/templates/home/index.html -Reload the CKAN front page in your browser (it shouldn't be necessary to -restart the web server, if you've only made changes to template files) and -you should see a new activity stream on the front page. +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 @@ -293,32 +334,51 @@ Let's add another item to our custom front page: a "dataset of the day". We'll add a custom template helper function to select the dataset to be shown. First, in our ``plugin.py`` file we need to implement :py:class:`~ckan.plugins.interfaces.ITemplateHelpers` and provide our helper -function. Change the contents of -``|extension_dir|/ckanext/example_theme/plugin.py`` to look like this: +function. Change the contents of ``plugin.py`` to look like this: + +.. todo: This probably breaks when the site has no datasets. .. literalinclude:: ../ckanext/example_theme/v7/plugin.py -.. todo:: Explain exactly what the new lines above do. +We've added a number of new features to ``plugin.py``. First, we defined a +function to get the dataset of the day from CKAN: - Mention why the helper is named as it is. +.. literalinclude:: ../ckanext/example_theme/v7/plugin.py + :pyobject: dataset_of_the_day + +This function uses CKAN's *action functions* to get the dataset from CKAN. +See :doc:`writing-extensions` for more about action functions. + +Next, we called :py:func:`~ckan.plugins.implements` to declare that your class +now implements :py:class:`~ckan.plugins.interfaces.ITemplateHelpers`: + +.. literalinclude:: ../ckanext/example_theme/v7/plugin.py + :start-after: # Declare that this plugin will implement ITemplateHelpers. + :end-before: def update_config(self, config): + +Finally, we implemented the +:py:meth:`~ckan.plugins.interfaces.ITemplateHelpers.get_helpers` method from +:py:class:`~ckan.plugins.interfaces.ITemplateHelpers` to register our function +as a template helper: + +.. literalinclude:: ../ckanext/example_theme/v7/plugin.py + :pyobject: ExampleThemePlugin.get_helpers 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 ``|extension_dir|/ckanext/example_theme/home/index.html`` to look -like this: +Edit |index.html| to look like this: .. literalinclude:: ../ckanext/example_theme/v7/templates/home/index.html -Now restart your web server and reload your CKAN front page in your browser. -You should see the name of a random dataset appear on the page, and each time -you reload the page you'll get a different name. +Now reload your `CKAN front page`_ in your browser. You should see the title of +a random dataset appear on the page, and each time you reload the page you'll +get a different name. -Simply displaying the name of a dataset isn't very good. We want to show the -dataset's title not its name, have the title be hyperlinked to the dataset's -page, and also show some other information about the dataset such as its notes -and file formats. To display our dataset of the day nicely, we'll use CKAN's -*template snippets*. +Simply displaying the title of a dataset isn't very good. We want the dataset +to be hyperlinked to the it's page, and also to show some other information +about the dataset such as its notes and file formats. To display our dataset of +the day nicely, we'll use CKAN's *template snippets*. Template snippets @@ -343,7 +403,9 @@ a group's page, an organization's page or a user's page: .. literalinclude:: ../ckan/templates/snippets/package_item.html :end-before: #} -Let's change our ``index.html`` file to call this snippet: +.. todo:: Fix this docstring. + +Let's change our |index.html| file to call this snippet: .. literalinclude:: ../ckanext/example_theme/v8/templates/home/index.html @@ -353,8 +415,8 @@ the snippet as parameters. As in the ``package_item.html`` docstring above, each snippet's docstring should document the parameters it requires. In this example was pass just one parameter to the snippet: the dataset to be rendered. -If you reload your CKAN front page in your web browser now, you should see the -dataset of the day rendered nicely. +If you reload your `CKAN front page`_ in your web browser now, you should see +the dataset of the day rendered nicely. Adding your own template snippets @@ -366,17 +428,17 @@ their own snippets. To add template snippets, all a plugin needs to do is add a The snippets will be callable from other templates immediately. Let's add a custom snippet to change how our dataset of the day is displayed. -Create a new directory ``|extension_dir|/templates/snippets/`` containing -a file named ``example_theme_dataset_of_the_day.html`` with these contents: +Create a new directory |snippets_dir| containing a file named +``example_theme_dataset_of_the_day.html`` with these contents: .. literalinclude:: ../ckanext/example_theme/v9/templates/snippets/example_theme_dataset_of_the_day.html -Now edit your ``index.html`` file and change to use our new snippet: +Now edit your |index.html| file and change to use our new snippet: .. literalinclude:: ../ckanext/example_theme/v9/templates/home/index.html -Restart your web server and reload your CKAN front page in your browser, and -you should see the display of the dataset of the day change. +Reload your `CKAN front page`_ in your browser, and you should see the display +of the dataset of the day change. .. todo:: @@ -412,6 +474,106 @@ you should see the display of the dataset of the day change. .. todo:: Verify whether this is completely true. +Global variables available to templates +--------------------------------------- + +.. _Pylons app_globals object: http://docs.pylonsproject.org/projects/pylons-webframework/en/latest/glossary.html#term-app-globals + +CKAN passes a number of variables that templates can use in Jinja statements or +expressions. For example, one such variable is ``app_globals``, the `Pylons +app_globals object`_, which can be used to access global attributes including +the config settings from the CKAN config file. + +.. todo:: Move these examples into a separate file. + +.. todo:: Change this example to use custom config settings, + dataset of the day, datasets of the day, show dataset of the day. + +:: + + {% ckan_extends %} + + {% block secondary_content %} + +

Site title: {{ g.site_title }}

+ + {% endblock %} + +``{{ g.site_title }}`` will output the :ref:`ckan.site_title` setting from your +config file (a string) into the web page. + +Some variables, such as :ref:`ckan.plugins`, are lists. We can use a +``{% for %}`` loop to print out the list of currently enabled plugins:: + +

Plugins enabled:

+ + +.. todo:: Mention what happens if you try to access a variable or attribute + that doesn't exist. + +.. todo:: Mention filters. And can ckan template helper functions be used as + filters? + +.. todo:: Mention tests. + +The following global variables are available to all templates: + +.. todo:: + + Add explanation of template variables and examples of how to use them in + Jinja2 templates. + + +``c`` + ``pylons.util.AttribSafeContextObj`` + + Using this is discouraged, better to use ``h``. + ``c`` is not available to snippets (but I think the rest are?) + +``h`` + ``ckan.config.environment._Helpers`` object + +``app_globals`` + ``ckan.lib.app_globals._Globals`` object + +``request`` + ``Request`` object + +``response`` + ``Response`` object + + .. todo:: Remove this? Doesn't appear to be used. + +``session`` + .. todo:: Remove this? Doesn't appear to be used. + +``N_`` + function ``gettext_noop`` + +``_`` + function ``ugettext`` + +``translator`` + ``gettext.NullTranslations`` instance + +``ungettext`` + function ``ungettext`` + +``actions`` + class ``ckan.model.authz.Action`` + + .. todo:: Remove this? Doesn't appear to be used and doesn't look like + something we want. + +.. todo:: + + Mention that any more variables explicitly passed in by the controller or + parent template are also available. + Bootstrap --------- @@ -466,3 +628,4 @@ Customizing CKAN's JavaScript * jQuery * Bootstrap's JavaScript stuff * Other stuff in javascript-module-tutorial.rst +