19. Page Templates – Mastering Plone 5 development

19. Page Templates#

In this part you will:

  • Learn to write page templates

Topics covered:

  • TAL and TALES

  • METAL

  • Chameleon

Page Templates are HTML files with some additional information, written in TAL, METAL and TALES.

Page templates must be valid XML.

The three languages are:

  • TAL: "Template Attribute Language"

    • Templating XML/HTML using special attributes

    • Using TAL we include expressions within HTML

  • TALES: "TAL Expression Syntax"

    • defines the syntax and semantics of these expressions

  • METAL: "Macro Expansion for TAL"

    • this enables us to combine, re-use and nest templates together

TAL and METAL are written like HTML attributes (href, src, title). TALES are written like the values of HTML attributes.

They are used to modify the output:

<p tal:content="string:I love red">I love blue</p>

results in:

<p>I love red</p>

Let's try it.

Open the file training.pt and add:

<html>
  <body>
    <p>red</p>
  </body>
</html>

19.1. TAL and TALES#

Let's add some magic and modify the <p>-tag:

<p tal:content="string:blue">red</p>

This will result in:

<p>blue</p>

Without restarting Plone open http://localhost:8080/Plone/@@training.

The same happens with attributes.

Replace the <p>-line with:

<a
  href="http://www.mssharepointconference.com"
  tal:define="a_fine_url string:https://www.ploneconf.org/"
  tal:attributes="href a_fine_url"
  tal:content="string:An even better conference"
>
  A sharepoint conference
</a>

results in:

<a href="https://www.ploneconf.org/"> An even better conference </a>

We used three TAL-Attributes here.

This is the complete list of TAL-attributes:

tal:define

define variables. We defined the variable a_fine_url to the string "https://www.ploneconf.org/".

tal:content

replace the content of an element. We replaced the default content above with "An even better conference"

tal:attributes

dynamically change element attributes. We set the HTML attribute href to the value of the variable a_fine_url

tal:condition

tests whether the expression is true or false, and outputs or omits the element accordingly.

tal:repeat

repeats an iterable element, in our case the list of talks.

tal:replace

replace the content of an element, like tal:content does, but removes the element only leaving the content.

tal:omit-tag

remove an element, leaving the content of the element.

tal:on-error

handle errors.

python expressions#

Till now we only used one TALES expression (the string: bit). Let's use a different TALES expression now.

With python: we can use Python code.

A example:

<p tal:define="title python:context.title" tal:content="python:title.upper()">
  A big title
</p>

With context.title you access information from the context object, that is the object on which the view is called. Modify the template training.pt like this

<p>
  ${python: 'This is the {0} "{1}" at {2}'.format(context.portal_type, context.title, context.absolute_url())}
</p>

Now call the view on different urls and see what happens:

And another python-statement:

<p
  tal:define="talks python:['Dexterity for the win!',
                             'Deco is the future',
                             'A keynote on some weird topic',
                             'The talk that I did not submit']"
  tal:content="python:talks[0]"
>
  A talk
</p>

With python expressions:

  • you can only write single statements

  • you could import things but you should not

tal:condition#

tal:condition

tests whether the expression is true or false.

  • If it's true, then the tag is rendered.

  • If it's false then the tag and all its children are removed and no longer evaluated.

  • We can reverse the logic by perpending a not: to the expression.

Let's add another TAL Attribute to our above example:

tal:condition="python:talks"

We could also test for the number of talks:

tal:condition="python:len(talks) >= 1"

or if a certain talk is in the list of talks:

tal:condition="python:'Deco is the future' in talks"

tal:repeat#

Let's try another attribute:

<p
  tal:define="talks python:['Dexterity for the win!',
                             'Deco is the future',
                             'A keynote on some weird topic',
                             'The talk that I did not submit']"
  tal:repeat="talk talks"
  tal:content="talk"
>
  A talk
</p>
tal:repeat

repeats an iterable element, in our case the list of talks.

We change the markup a little to construct a list in which there is an <li> for every talk:

 1 <ul tal:define="talks python:['Dexterity for the win!',
 2                               'Deco is the future',
 3                               'A keynote on some weird topic',
 4                               'The talk that I did not submit']">
 5     <li tal:repeat="talk talks"
 6         tal:content="talk">
 7           A talk
 8     </li>
 9     <li tal:condition="not:talks">
10           Sorry, no talks yet.
11     </li>
12 </ul>

path expressions#

Regarding TALES so far we used string: or python: or only variables. The next and most common expression are path expressions.

Optionally you can start a path expression with path:

Every path expression starts with a variable name. It can either be an object like context, view or template or a variable defined by you like talk.

After the variable we add a slash / and the name of a sub-object, attribute or callable. The / is used to end the name of an object and the start of the property name.

Properties themselves may be objects that in turn have properties.

<p tal:content="context/title"></p>

We can chain several of those to get to the information we want.

<p tal:content="context/REQUEST/form"></p>

This would return the value of the form dictionary of the HTTPRequest object. Useful for form handling.

The | ("or") character is used to find an alternative value to a path if the first path evaluates to nothing or does not exist.

<p tal:content="context/title | context/id"></p>

This returns the id of the context if it has no title.

<p tal:replace="talk/average_rating | nothing"></p>

This returns nothing if there is no 'average_rating' for a talk.

What will not work is tal:content="python:talk['average_rating'] or ''".

Who knows what this would yield?

We'll get KeyError: 'average_rating'. It is very bad practice to use | too often since it will swallow errors like a typo in tal:content="talk/averange_ratting | nothing" and you might wonder why there are no ratings later on...

You can't and should not use it to prevent errors like a try/except block.

There are several built-in variables that can be used in paths:

The most frequently used one is nothing which is the equivalent to None

<p tal:replace="nothing">this comment will not be rendered</p>

A dict of all the available variables at the current state is econtext

1<dl>
2  <tal:vars tal:repeat="variable econtext">
3    <dt>${variable}</dt>
4    <dd>${python:econtext[variable]}</dd>
5  </tal:vars>
6</dl>

Useful for debugging :-)

Note

In Plone 4 that used to be CONTEXTS

1<dl>
2  <tal:vars tal:repeat="variable CONTEXTS">
3    <dt tal:content="variable"></dt>
4    <dd tal:content="python:CONTEXTS[variable]"></dd>
5  </tal:vars>
6</dl>

Pure TAL blocks#

We can use TAL attributes without HTML Tags.

This is useful when we don't need to add any tags to the markup.

Syntax:

<tal:block attribute="expression">some content</tal:block>

Examples:

<tal:block define="id template/id">
  ...
  <b tal:content="id">The id of the template</b>
  ...
</tal:block>

<tal:news condition="python:context.portal_type == 'News Item'">
  This text is only visible if the context is a News Item
</tal:news>

handling complex data in templates#

Let's move on to a little more complex data. And to another TAL attribute:

tal:replace

replace the content of an element and removes the element only leaving the content.

Example:

<p>
  <img
    tal:define="tag string:<img src='https://plone.org/logo.png'>"
    tal:replace="tag"
  />
</p>

this results in:

<p>&lt;img src='https://plone.org/logo.png'&gt;</p>

tal:replace drops its own base tag in favor of the result of the TALES expression. Thus the original <img... > is replaced.

But the result is escaped by default.

To prevent escaping we use structure

<p>
  <img
    tal:define="tag string:<img src='https://plone.org/logo.png'>"
    tal:replace="structure tag"
  />
</p>

Now let's emulate a typical Plone structure by creating a dictionary.

 1  <table tal:define="talks python:[{'title':'Dexterity for the win!',
 2                                    'subjects':('content-types', 'dexterity')},
 3                                   {'title':'Deco is the future',
 4                                    'subjects':('layout', 'deco')},
 5                                   {'title':'The State of Plone',
 6                                    'subjects':('keynote',) },
 7                                   {'title':'Diazo designs dont suck!',
 8                                    'subjects':('design', 'diazo', 'xslt')}
 9                                  ]">
10      <tr>
11          <th>Title</th>
12          <th>Topics</th>
13      </tr>
14      <tr tal:repeat="talk talks">
15          <td tal:content="talk/title">A talk</td>
16          <td tal:define="subjects talk/subjects">
17              <span tal:repeat="subject subjects"
18                    tal:replace="subject">
19              </span>
20          </td>
21      </tr>
22  </table>

We emulate a list of talks and display information about them in a table. We'll get back to the list of talks later when we use the real talk objects that we created with dexterity.

To complete the list here are the TAL attributes we have not yet used:

tal:omit-tag

Omit the element tag, leaving only the inner content.

tal:on-error

handle errors.

When an element has multiple TAL attributes, they are executed in this order:

  1. define

  2. condition

  3. repeat

  4. content or replace

  5. attributes

  6. omit-tag

19.2. Chameleon#

Since Plone 5 we have Chameleon.

Using the integration layer five.pt it is fully compatible with the normal TAL syntax but offers some additional features:

You can use ${...} as short-hand for text insertion in pure html effectively making tal:content and tal:attributes obsolete.

Here are some examples:

Plone 4 and Plone 5:

1 <a tal:attributes="href string:${context/absolute_url}?ajax_load=1;
2                    class python:context.portal_type.lower().replace(' ', '')"
3    tal:content="context/title">
4    The Title of the current object
5 </a>

Plone 5 (and Plone 4 with five.pt) :

1 <a href="${context/absolute_url}?ajax_load=1"
2    class="${python:context.portal_type.lower().replace(' ', '')}">
3    ${python:context.title}
4 </a>

You can also add pure python into the templates:

1 <div>
2   <?python
3   someoptions = dict(
4       id=context.id,
5       title=context.title)
6   ?>
7   This object has the id "${python:someoptions['id']}"" and the title "${python:someoptions['title']}".
8 </div>

19.3. Exercise 1#

Modify the following template and one by one solve the following problems: :

 1 <table tal:define="talks python:[{'title': 'Dexterity is the new default!',
 2                                   'subjects': ('content-types', 'dexterity')},
 3                                  {'title': 'Mosaic will be the next big thing.',
 4                                   'subjects': ('layout', 'deco', 'views'),
 5                                   'url': 'https://www.youtube-nocookie.com/embed/QSNufxaYb1M?privacy_mode=1'},
 6                                  {'title': 'The State of Plone',
 7                                   'subjects': ('keynote',) },
 8                                  {'title': 'Diazo is a powerful tool for theming!',
 9                                   'subjects': ('design', 'diazo', 'xslt')},
10                                  {'title': 'Magic templates in Plone 5',
11                                   'subjects': ('templates', 'TAL'),
12                                   'url': 'http://www.starzel.de/blog/magic-templates-in-plone-5'}
13                                 ]">
14     <tr>
15         <th>Title</th>
16         <th>Topics</th>
17     </tr>
18     <tr tal:repeat="talk talks">
19         <td tal:content="talk/title">A talk</td>
20         <td tal:define="subjects talk/subjects">
21             <span tal:repeat="subject subjects"
22                   tal:replace="subject">
23             </span>
24         </td>
25     </tr>
26 </table>
  1. Display the subjects as comma-separated.

Solution
 1<table tal:define="talks python:[{'title': 'Dexterity is the new default!',
 2                                  'subjects': ('content-types', 'dexterity')},
 3                                 {'title': 'Mosaic will be the next big thing.',
 4                                  'subjects': ('layout', 'deco', 'views'),
 5                                  'url': 'https://www.youtube-nocookie.com/embed/QSNufxaYb1M?privacy_mode=1'},
 6                                 {'title': 'The State of Plone',
 7                                  'subjects': ('keynote',) },
 8                                 {'title': 'Diazo is a powerful tool for theming!',
 9                                  'subjects': ('design', 'diazo', 'xslt')},
10                                 {'title': 'Magic templates in Plone 5',
11                                  'subjects': ('templates', 'TAL'),
12                                  'url': 'http://www.starzel.de/blog/magic-templates-in-plone-5'}
13                                ]">
14    <tr>
15        <th>Title</th>
16        <th>Topics</th>
17    </tr>
18    <tr tal:repeat="talk talks">
19        <td tal:content="talk/title">A talk</td>
20        <td tal:define="subjects talk/subjects">
21            <span tal:replace="python:', '.join(subjects)">
22            </span>
23        </td>
24    </tr>
25</table>
  1. Turn the title in a link to the URL of the talk if there is one.

Solution
 1 <table tal:define="talks python:[{'title': 'Dexterity is the new default!',
 2                                   'subjects': ('content-types', 'dexterity')},
 3                                  {'title': 'Mosaic will be the next big thing.',
 4                                   'subjects': ('layout', 'deco', 'views'),
 5                                   'url': 'https://www.youtube-nocookie.com/embed/QSNufxaYb1M?privacy_mode=1'},
 6                                  {'title': 'The State of Plone',
 7                                   'subjects': ('keynote',) },
 8                                  {'title': 'Diazo is a powerful tool for theming!',
 9                                   'subjects': ('design', 'diazo', 'xslt')},
10                                  {'title': 'Magic templates in Plone 5',
11                                   'subjects': ('templates', 'TAL'),
12                                   'url': 'http://www.starzel.de/blog/magic-templates-in-plone-5'}
13                                 ]">
14     <tr>
15         <th>Title</th>
16         <th>Topics</th>
17     </tr>
18     <tr tal:repeat="talk talks">
19         <td>
20             <a tal:attributes="href talk/url | nothing"
21                tal:content="talk/title">
22                A talk
23             </a>
24         </td>
25         <td tal:define="subjects talk/subjects">
26             <span tal:replace="python:', '.join(subjects)">
27             </span>
28         </td>
29     </tr>
30 </table>
  1. If there is no URL, turn it into a link to a google search for that talk's title:

Solution
 1 <table tal:define="talks python:[{'title': 'Dexterity is the new default!',
 2                                   'subjects': ('content-types', 'dexterity')},
 3                                  {'title': 'Mosaic will be the next big thing.',
 4                                   'subjects': ('layout', 'deco', 'views'),
 5                                   'url': 'https://www.youtube-nocookie.com/embed/QSNufxaYb1M?privacy_mode=1'},
 6                                  {'title': 'The State of Plone',
 7                                   'subjects': ('keynote',) },
 8                                  {'title': 'Diazo is a powerful tool for theming!',
 9                                   'subjects': ('design', 'diazo', 'xslt')},
10                                  {'title': 'Magic templates in Plone 5',
11                                   'subjects': ('templates', 'TAL'),
12                                   'url': 'http://www.starzel.de/blog/magic-templates-in-plone-5'}
13                                 ]">
14     <tr>
15         <th>Title</th>
16         <th>Topics</th>
17     </tr>
18     <tr tal:repeat="talk talks">
19         <td>
20             <a tal:define="google_url string:https://www.google.com/search?q=${talk/title}"
21                tal:attributes="href talk/url | google_url"
22                tal:content="talk/title">
23                A talk
24             </a>
25         </td>
26         <td tal:define="subjects talk/subjects">
27             <span tal:replace="python:', '.join(subjects)">
28             </span>
29         </td>
30     </tr>
31 </table>

4. Add alternating the CSS classes 'odd' and 'even' to the <tr>. (repeat.<name of item in loop>.odd is True if the ordinal index of the current iteration is an odd number).

Use some CSS to test your solution:

<style type="text/css">
  tr.odd {background-color: #ddd;}
</style>
Solution
 1 <table tal:define="talks python:[{'title': 'Dexterity is the new default!',
 2                                   'subjects': ('content-types', 'dexterity')},
 3                                  {'title': 'Mosaic will be the next big thing.',
 4                                   'subjects': ('layout', 'deco', 'views'),
 5                                   'url': 'https://www.youtube-nocookie.com/embed/QSNufxaYb1M?privacy_mode=1'},
 6                                  {'title': 'The State of Plone',
 7                                   'subjects': ('keynote',) },
 8                                  {'title': 'Diazo is a powerful tool for theming!',
 9                                   'subjects': ('design', 'diazo', 'xslt')},
10                                  {'title': 'Magic templates in Plone 5',
11                                   'subjects': ('templates', 'TAL'),
12                                   'url': 'http://www.starzel.de/blog/magic-templates-in-plone-5'}
13                                 ]">
14     <tr>
15         <th>Title</th>
16         <th>Topics</th>
17     </tr>
18     <tr tal:repeat="talk talks"
19         tal:attributes="class python: 'odd' if repeat.talk.odd() else 'even'">
20         <td>
21             <a tal:define="google_url string:https://www.google.com/search?q=${talk/title};
22                            "
23                tal:attributes="href talk/url | google_url;
24                                "
25                tal:content="talk/title">
26                A talk
27             </a>
28         </td>
29         <td tal:define="subjects talk/subjects">
30             <span tal:replace="python:', '.join(subjects)">
31             </span>
32         </td>
33     </tr>
34 </table>
  1. Only use python expressions.

Solution
 1 <table tal:define="talks python:[{'title': 'Dexterity is the new default!',
 2                                   'subjects': ('content-types', 'dexterity')},
 3                                  {'title': 'Mosaic will be the next big thing.',
 4                                   'subjects': ('layout', 'deco', 'views'),
 5                                   'url': 'https://www.youtube-nocookie.com/embed/QSNufxaYb1M?privacy_mode=1'},
 6                                  {'title': 'The State of Plone',
 7                                   'subjects': ('keynote',) },
 8                                  {'title': 'Diazo is a powerful tool for theming!',
 9                                   'subjects': ('design', 'diazo', 'xslt')},
10                                  {'title': 'Magic templates in Plone 5',
11                                   'subjects': ('templates', 'TAL'),
12                                   'url': 'http://www.starzel.de/blog/magic-templates-in-plone-5'}
13                                 ]">
14     <tr>
15         <th>Title</th>
16         <th>Topics</th>
17     </tr>
18     <tr tal:repeat="talk python:talks"
19         tal:attributes="class python: 'odd' if repeat.talk.odd() else 'even'">
20         <td>
21             <a tal:attributes="href python:talk.get('url', 'https://www.google.com/search?q=%s' % talk['title'])"
22                tal:content="python:talk['title']">
23                A talk
24             </a>
25         </td>
26         <td tal:content="python:', '.join(talk['subjects'])">
27         </td>
28     </tr>
29 </table>
  1. Use the syntax of Plone 5 replacing tal:attribute and tal:content with inline ${} statements.

Solution
 1 <table tal:define="talks python:[{'title': 'Dexterity is the new default!',
 2                                   'subjects': ('content-types', 'dexterity')},
 3                                  {'title': 'Mosaic will be the next big thing.',
 4                                   'subjects': ('layout', 'deco', 'views'),
 5                                   'url': 'https://www.youtube-nocookie.com/embed/QSNufxaYb1M?privacy_mode=1'},
 6                                  {'title': 'The State of Plone',
 7                                   'subjects': ('keynote',) },
 8                                  {'title': 'Diazo is a powerful tool for theming!',
 9                                   'subjects': ('design', 'diazo', 'xslt')},
10                                  {'title': 'Magic templates in Plone 5',
11                                   'subjects': ('templates', 'TAL'),
12                                   'url': 'http://www.starzel.de/blog/magic-templates-in-plone-5'}
13                                 ]">
14     <tr>
15         <th>Title</th>
16         <th>Topics</th>
17     </tr>
18
19     <tr tal:repeat="talk python:talks"
20         class="${python: 'odd' if repeat.talk.odd() else 'even'}">
21         <td>
22             <a href="${python:talk.get('url', 'https://www.google.com/search?q=%s' % talk['title'])}">
23                 ${python:talk['title']}
24             </a>
25         </td>
26         <td>
27             ${python:', '.join(talk['subjects'])}
28         </td>
29     </tr>
30 </table>
  1. Sort the talks alphabetically by title

Solution
 1 <table tal:define="talks python:[{'title': 'Dexterity is the new default!',
 2                                   'subjects': ('content-types', 'dexterity')},
 3                                  {'title': 'Mosaic will be the next big thing.',
 4                                   'subjects': ('layout', 'deco', 'views'),
 5                                   'url': 'https://www.youtube-nocookie.com/embed/QSNufxaYb1M?privacy_mode=1'},
 6                                  {'title': 'The State of Plone',
 7                                   'subjects': ('keynote',) },
 8                                  {'title': 'Diazo is a powerful tool for theming!',
 9                                   'subjects': ('design', 'diazo', 'xslt')},
10                                  {'title': 'Magic templates in Plone 5',
11                                   'subjects': ('templates', 'TAL'),
12                                   'url': 'http://www.starzel.de/blog/magic-templates-in-plone-5'}
13                                 ]">
14     <tr>
15         <th>Title</th>
16         <th>Topics</th>
17     </tr>
18
19 <?python from operator import itemgetter ?>
20
21     <tr tal:repeat="talk python:sorted(talks, key=itemgetter('title'))"
22         class="${python: 'odd' if repeat.talk.odd() else 'even'}">
23         <td>
24             <a href="${python:talk.get('url', 'https://www.google.com/search?q=%s' % talk['title'])}">
25                 ${python:talk['title']}
26             </a>
27         </td>
28         <td>
29             ${python:', '.join(talk['subjects'])}
30         </td>
31     </tr>
32 </table>

Warning

Do not use this trick in your projects! This level of python-logic belongs in a class, not in a template!

19.4. METAL and macros#

Why is our output so ugly?

How do we get our HTML to render in Plone the UI?

We use METAL (Macro Extension to TAL) to define slots that we can fill and macros that we can reuse.

Add this to the <html> tag:

metal:use-macro="context/main_template/macros/master"

And then wrap the code we want to put in the content area of Plone in:

<metal:content-core fill-slot="main">
    ...
</metal:content-core>

This will put our code in a section defined in the main_template called "content-core".

Now replace the main in fill-slot="main" with content-core and see what changes.

The template should now look like below when we exclude the last exercise.

Here also added the css-class listing to the table. It is one of many css-classes used by Plone that you can reuse in your projects:

 1<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
 2      lang="en"
 3      metal:use-macro="context/main_template/macros/master"
 4      i18n:domain="ploneconf.site">
 5<body>
 6
 7<metal:content-core fill-slot="content-core">
 8
 9<table class="listing"
10       tal:define="talks python:[{'title': 'Dexterity is the new default!',
11                                  'subjects': ('content-types', 'dexterity')},
12                                 {'title': 'Mosaic will be the next big thing.',
13                                  'subjects': ('layout', 'deco', 'views'),
14                                  'url': 'https://www.youtube-nocookie.com/embed/QSNufxaYb1M?privacy_mode=1'},
15                                 {'title': 'The State of Plone',
16                                  'subjects': ('keynote',) },
17                                 {'title': 'Diazo is a powerful tool for theming!',
18                                  'subjects': ('design', 'diazo', 'xslt')},
19                                 {'title': 'Magic templates in Plone 5',
20                                  'subjects': ('templates', 'TAL'),
21                                  'url': 'http://www.starzel.de/blog/magic-templates-in-plone-5'},
22                                ]">
23    <tr>
24        <th>Title</th>
25        <th>Topics</th>
26    </tr>
27
28    <tr tal:repeat="talk python:talks"
29        class="${python: 'odd' if repeat.talk.odd else 'even'}">
30        <td>
31            <a href="${python:talk.get('url', 'https://www.google.com/search?q=%s' % talk['title'])}">
32                ${python:talk['title']}
33            </a>
34        </td>
35        <td>
36            ${python:', '.join(talk['subjects'])}
37        </td>
38    </tr>
39</table>
40
41</metal:content-core>
42
43</body>
44</html>

macros in browser views#

Define a macro in a new file macros.pt

<div metal:define-macro="my_macro">
  <p>I can be reused</p>
</div>

Register it as a simple BrowserView in zcml:

<browser:page
  for="*"
  name="abunchofmacros"
  template="templates/macros.pt"
  permission="zope2.View"
  />

Reuse the macro in the template training.pt:

<div metal:use-macro="context/@@abunchofmacros/my_macro">
  Instead of this the content of the macro will appear...
</div>

Which is the same as:

<div
  metal:use-macro="python:context.restrictedTraverse('abunchofmacros')['my_macro']"
>
  Instead of this the content of the macro will appear...
</div>

Restart your Plone instance from the command line, and then open http://localhost:8080/Plone/@@training to see this macro being used in our @@training browser view template.

19.5. Accessing Plone from the template#

In the template you have access to:

  • the context object on which your view is called on

  • the view (and all python methods we'll put in the view later on)

  • the request

With these three you can do almost anything!

Create a new talk object "Dexterity for the win!" and add some information to all fields, especially the speaker and the email-address.

Now access the view training on that new talk by opening http://localhost:8080/Plone/dexterity-for-the-win/training in the browser.

It will look the same as before.

Now modify the template training.pt to display the title of the context:

<h1>${python: context.title}</h1>

19.6. Exercise 2#

  • Render a mail-link to the speaker.

  • Display the speaker instead of the raw email-address.

  • If there is no speaker-name display the address.

  • Modify attributes of html-tags by adding your statements into the attributes directly like title="${python: context.type_of_talk.capitalize()}".

Solution
<a href="${python: 'mailto:{0}'.format(context.email)}">
   ${python: context.speaker if context.speaker else context.email}
</a>

Note

Alternatively you can also use tal:attributes="<attr> <value>" to modify attributes.

19.7. Accessing other views#

In templates we can also access other browser views. Some of those exist to provide easy access to methods we often need:

tal:define="context_state context/@@plone_context_state;
            portal_state context/@@plone_portal_state;
            plone_tools context/@@plone_tools;
            plone_view context/@@plone;"
@@plone_context_state

The BrowserView plone.app.layout.globals.context.ContextState holds useful methods having to do with the current context object such as is_default_page()

@@plone_portal_state

The BrowserView plone.app.layout.globals.portal.PortalState holds methods for the portal like portal_url()

@@plone_tools

The BrowserView plone.app.layout.globals.tools.Tools gives access to the most important tools like plone_tools/catalog

These are very widely used and there are many more.

19.8. What we missed#

There are some things we did not cover so far:

tal:condition="exists:expression"

checks if an object or an attribute exists (seldom used)

tal:condition="nocall:context"

to explicitly not call a callable.

If we refer to content objects, without using the nocall: modifier these objects are unnecessarily rendered in memory as the expression is evaluated.

i18n:translate and i18n:domain

the strings we put in templates can be translated automatically.

There is a lot more about TAL, TALES and METAL that we have not covered. You'll only learn it if you keep reading, writing and customizing templates.