Skip to content
This repository has been archived by the owner on Jun 19, 2023. It is now read-only.

Genshi to Jinja conversion

David Read edited this page Jan 12, 2015 · 12 revisions

<html> & Genshi cruft

<html xmlns:py="http://genshi.edgewall.org/"
  xmlns:i18n="http://genshi.edgewall.org/i18n"
  xmlns:xi="http://www.w3.org/2001/XInclude"
  py:strip="">
...
</html>

-> replace with

{% extends "page.html" %}

Headings

On the tab

<py:def function="page_title">New - Harvest Source</py:def>

-> becomes

{% block title %}New - Harvest Source - {{ super() }}{% endblock %}

In the page

<py:def function="page_heading">New harvest source </py:def>

-> becomes

<h1>New harvest source</h1>

No sidebar

<py:def function="body_class">no-sidebar</py:def>

-> delete

Sidebar

<py:def function="sidebar">

-> moves to

{% block secondary_content %}

Optional head

<py:def function="optional_head">
 ...
</py:def>

e.g. links to CSS -> becomes

{% block optional_head %}
 ...
{% endblock %}

Breadcrumbs

  <py:match path="breadcrumbs">
    <li><a href="/data/search">Datasets</a></li>
    <li><a href="/harvest">Harvest sources</a></li>
    <li><a href="/harvest/new">New source</a></li>
  </py:match>

becomes

{% block breadcrumb_content %}
  {{ h.build_nav('dgu_search', _('Datasets')) }}
  {{ h.build_nav('harvest', _('Harvest sources')) }}
  {{ h.build_nav('harvest_new', _('New source')) }}
{% endblock %}

NB The first param is the route 'name' - you probably need to add it as a new param at the start of the route definition in plugin.py e.g.

map.connect('/harvest/new', controller=controller, action='new')

becomes

map.connect('harvest_new', '/harvest/new', controller=controller, action='new')

Main content block

<div py:match="content">
  ...
</div>

-> becomes

{% block primary_content_inner %}
  ...
{% endblock %}

Macros/Includes and py:def

In some cases py:def will be replaced with a block name (see breadcrumbs, title) but in others they'll actually be macros (see _dgu_jinja_util.html). The common form for macros is :

{% macro name(arg1, arg2) %}

{% endmacro %}

When you want to use the macro elsewhere you should use it as

{% import "_dgu_jinja_util.html" as m with context %}

{{ m.mymacro() }} 

includes

<xi:include href="../layout.html" />

Delete this reference, but check the file it refers to. If it doesn't exist then it will fallback to ckan/templates/layout.html which has nothing in, so you can ignore it.

${}

${...}

-> becomes

{{ ... }}

I suggest we do put in a space just inside each bracket, for easy reading.

errors

Typically in genshi there was a lot of

<py:if="errors.get('field')">
    <div>....
</py:if>

we should change to using the macros display_error in _dgu_jinja_util.html instead. It is used as

{{ display_error(errors, name, field_error=False, msg=None) }}

errors is the usual error dict, and name is the name of the field. If it isn't present the macro does nothing. If it is present it will show an error message using:

  1. msg if not None
  2. the contents of the error dict

The field_error is used for cases where the errors were previously displayed as a p class="field-error" and the default is to show a normal bootstrap alert.

c.something (context variable)

${c.user}
  • A couple of context variables can still be used: c.user, c.user_obj
  • Most you should pass in from the controller using extra_vars:
render('source/new.html', extra_vars={'data': data})

and then you can use them in the template unqualified:

{{data}}

Casts and type checks

You can't use dict(), or int() in the template. There are helpers for some h.as_dict(), and for things like bool() you should check if val. You also can't do something like

if type(x) is dict

instead do

if h.is_dict(x)

g.something (globals)

${g.site_url}

Some are defined? e.g. g.tracking_enabled Others need passing in using extra_vars

h.something (helpers)

{{ h.url_for(...) }}

These should all still work - both ckan and DGU helpers.

Rendered HTML passed in

Controller:

c.form = render('source/new_source_form.html', extra_vars=vars)

Template:

${h.literal(c.form)}

Consider calling the snippet from the template, rather than rendering it separately and passing in the HTML.

{% snippet 'snippets/new_form.html' %}

<py:for> <py:if> etc.

<py:for>
{% for item in navigation %} .. {% endfor %}

<py:if>
{% if group.description %} .. {% else %} .. {% endif %}
{% if group.description %} .. {% elif group.name %} ... {% else %} ... {% endif %}

Setting vars

Sometimes you need to set a var to save calling a function too frequently. You can do this like

{% set myvar = h.slow_function() %}

You need to be careful though, as inside a forloop this can leak out and may not be set on each iteration. In these cases you should do:

{% for x in loop_var %}
    {% with %}
        {% set var = h.myfunction() %}
    {% endwith %}
{% endfor %}

You could set the var in the with block, but perhaps it is more legible to structure it as above.

Helper templates

Helper templates (such as _dgu_util.html) are problematic because they are used in several places. Instead use _dgu_jinja_util.html until all the necessary templates have been replaced.

The following snippet has been removed from _dgu_jinja_util.html as it caused recursion problems, and so you will need to include it in your page (such as inventory/read.html)

{% block optional_head %}
    <link rel="alternate" type="application/rdf+xml" href="{{h.url_for(controller='package', action='read', id=c.pkg.name, format='rdf')}}"/>
    <script type="text/javascript" src="{{h.url_for_static('/scripts/dgu-package.min.js')}}"></script>
    <script type="text/javascript">
      window.DATASET_ID = "{{c.pkg_dict.get('id')}}";
    </script>
    {{ super() }}
{% endblock %}

Unrendered Comments

The extra "!" tells Genshi not to leave it in the rendered HTML.

<!--! Comment -->

-> replace with

{# Comment #}

But you can leave rendered comments:

<!-- Comment -->