Dynamic HTML generation and scripting of pages, content rules, portlets and emails
Project description
Introduction
Easy Template (collective.easytemplate) products brings easy dynamic texts to Plone. You don’t need to create full blown product just for few dynamic pages anymore - the most simplest things can be typed straight from the visual editor.
Templating is a way to add simple programming logic to text output. This products adds or enhances templating supports on various parts of Plone site.
Motivation
Plone lacks out of the box support for custom, extensible, templating support for content editors.
Use cases
Possible use cases are e.g.
Use unfiltered HTML on page body (<script> et. al)
Adding dynamic listings and tables on pages, like news listing
Adding dynamic email bodies, titles and receivers in content rules actions
Adding generated content to content rule action emails
Show different text to logged in and anonymous users
Creating a simple text portlet dynamically
Example
The following example demostrates how text in Templated Document edit mode gets translated to generated HTML snippet in the view mode.
You write in Kupu:
Hello user! Please select one course from below: {{ list_folder("courses") }}
will result to the output:
Hello user!
Please select one course from below:
Installation
Add to your buildout:
eggs = collective.easytemplate zcml = collective.easytemplate
Run Add-on product installer for Easy Template product.
collective.easytemplate depends on collective.templateengines and Jinja2 template engine.
Security notice
Because collective.easytemplate allows entering unsafe HTML, like <script> on the pages by default, its creation is limited to the users with Manager role.
Running unit tests
Python eggs Cheetah, Jinja2 and Products.LinguaPlone must be installed in order to run all unit tests.
Sponsorship
The development of this product was sponsored by London School of Marketing.
About the template engine backends
By default, Jinja 2 template engine is used. Please refer to Jinja 2 documentation for syntax. This syntax is very easy non-XML syntax resembling Django templates.
Easy syntax means that for the basic usage, no HTML knowledge is needed. You can use template logic in both the HTML source code and visual editor views.
Template engine is switchable in the application code. Besides Jinja, Django and Cheetah engines are supported by collective.templateengines backend.
Only on Jinja engine Zope security is supported. Other template engines do not provide compatible sandboxes.
Templated elements
Easy Template product makes it possible to use templates in the following places.
Document
Use Templated Document content type to add scriptable Plone pages.
This content item has a tab Template which allows you to adjust advanced template properties:
Show errors: After checking this option the error messages will be available to the user regardless whether he/she is admin
Unfiltered template: A field for raw HTML code input. This code is not scrambled or filtered by WYSIWYG editing or HTML filtering functions.
You can debug the template code by seeing the direct template engine output by appending /testTemplate to the object url. It will return plain text view what the cooked template has eaten.
Fields and widgets
collective.easytemplate.fields.TemplatedTextField allows you to edit templated document code in Kupu and template is run in view mode. It acts as replacement for Archetypes’s TextField() for your custom content types.
Portlets
Use Templated Portlet portlet to add scripts to your portlets. Templated Portlet is based on `
Enter the template code in the visual editor. Raw HTML editing is not yet supported here.
Portlets have TALES Expression field to determine whether portlets should be visible or not. This allows showing and hiding portlets conditionally.
This is useful for special cases like
Showing portlets by language
Showing portlets for specific users only
Showing portlers for certain time
etc.
Use Templated Mail Action to add scripting to your content rules based outgoing email messages.
Template expansion is available in all the fields: recipients, subject and message. You can dynamically look up the receiver email based on the context object - it doesn’t need to be a fixed address.
Below is an advanced example how to define templated outgoing email which is triggered from a workflow action.
profiles/defaul/contentrules.xml:
<?xml version="1.0"?> <contentrules> <rule name="email_local_coordinator_about_local_user_approval" title="Send email to LC when new LU needs approval" description="Send email to local coordinators that management has approved new member to their center and local coordinator actions are needed" enabled="True" event="Products.CMFCore.interfaces.IActionSucceededEvent" stop-after="False"> <conditions> <condition type="plone.conditions.WorkflowTransition"> <property name="wf_transitions"> <element>my_workflow_transition_id_here</element> </property> </condition> </conditions> <actions> <action type="collective.easytemplate.actions.Mail"> <property name="source">{{ portal.getProperty('email_from_address') }}</property> <property name="message"> New local user {{ title }} needs approval. Please approve local user at {{ context.absolute_url() }} </property> <property name="recipients">{{ context.getTheReceiverEmailAddressFromTheContextSomehow() }}</property> <property name="subject">New local user {{ title }} needs approval</property> </action> </actions> </rule> <assignment location="/" name="email_local_coordinator_about_local_user_approval" enabled="True" bubbles="True" /> </contentrules>
Then sample unit testing code to test this:
from zope.component import getUtility, getMultiAdapter, getSiteManager from Products.MailHost.interfaces import IMailHost from Products.SecureMailHost.SecureMailHost import SecureMailHost class DummySecureMailHost(SecureMailHost): meta_type = 'Dummy secure Mail Host' def __init__(self, id): self.id = id self.sent = [] self.mto = None def _send(self, mfrom, mto, messageText, debug=False): self.sent.append(messageText) self.mto = mto ... class BaseTestCase: """We use this base class for all the tests in this package. If necessary, we can put common utility or setup code in here. Mix-in class, also include FunctionalTestCase or PloneTestCase. """ def afterSetUp(self): ... self.loginAsPortalOwner() sm = getSiteManager(self.portal) sm.unregisterUtility(provided=IMailHost) self.dummyMailHost = DummySecureMailHost('dMailhost') sm.manage_changeProperties({'email_from_address': 'moo@isthemasteofuniverse.com'}) sm.registerUtility(self.dummyMailHost, IMailHost) ... def test_my_email_on_workflow_transition(self): self.workflow = self.portal.portal_workflow self.portal.invokeFactory("MyContentType", "myobject") myobject = self.portal.myobject self.dummyMailHost.sent = [] self.workflow.doActionFor(myobject, "my_workflow_transition_id_here") review_state = self.workflow.getInfoFor(myobject, 'review_state') self.assertEqual(review_state, "my_new_workflow_state") # Check that the email has been send self.assertEqual(len(self.dummyMailHost.sent), 1) # Outgoing emails increased by one self.assertEqual(self.dummyMailHost.mto, ["receiver@dummy.host"]) ...
Security
By default, template functions are limited to the current user priviledges - this means that output may vary depending on which user you have logged in. The user should not be able to escape Zope sandbox.
However, some tags are not totally secure (escapes viewing priviledges) and you might want to disable them on multi-user production site.
Security is not guaranteed for this product. For sites with high security requirements, please consult the author.
Security unit tests are available here.
Template context
Template context holds the top level variables you have available in your template.
Variables are defined in context/plone.py source code file.
context variable
Plone uses subsystem called Archetypes to define content types. Content types are constructed from fields defined in the schema. All the default Plone content types (documents, folders, events, news, etc.) are Archetypes based.
Archetypes based objects are exposed “as is” to the template engine in the context variable. Other context variable functions are defined by Python classes running the object.
You can call getXXX accessor functions to query individual fields values. The exposed fields are defined in the schema source code of Archetypes object.
Examples below.
Print content title:
{{ context.Title().decode("utf-8") }}
Print document body text (HTML):
{{ context.getBody() }}
Get the URL of the current object:
{{ context.absolute_url() }}
If you have a write access to the object you can even set values in the template, though this is not very useful:
{{ context.setTitle('Moo the novel') }}
Unicode and UTF-8
Jinja, like Python 2.x software usual, assumes all strings are either ASCII or Unicode.
If you are outputting text which
contains international characters
is known to be UTF-8
you must decode the input text in your template. For Plone, the following is known to be UTF-8
All Archetypes text field accessors like Title(), Description() return UTF-8 bytestrings
portal_catalog entries like Title, Description reflect directly Archetypes values and contain UTF-8 bytestrings
For other strings, consult Plone source code
To output such text the decode must be performed. You can do this by directly calling decode() method of Python bytecode strings.
For function like accessors:
{{ context.Title().decode("utf-8") }}
For catalog brain data:
{{ brain.Title.decode("utf-8") }}
Otherwise you will see something like this when international characters are encountered:
Traceback (innermost last): Module collective.templateengines.utils, line 104, in wrapExceptions Module collective.templateengines.backends.jinja, line 104, in applier Module jinja2.environment, line 705, in render Module <template>, line 3, in top-level template code UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 1: ordinal not in range(128)
Traversing
Traversing is a mechanism to look up objects in Zope’s object graph.
To access the other objects beside the current Template Document you can traverse in the folder hierarchy using Zope’s traversing mechanism.
Get the parent folder:
{{ context.aq_parent }}
Folder content objects can be traversed using the object id.
Get the sister page in the current folder which has URL id ‘sister’:
{{ context.aq_parent.sister }}
portal
Portal is the root Plone object of your site. You can use it as a traversing start point to query other objects on your site. E.g.
Some of available methods are described in IPortal interface.
To access the top level news folder:
{{ portal.news }}
portal_state
portal_state stores information about the current state of the system. Tells things like if the user is logged in, navigation base, portal title, active language and so on.
This object implements IPortalState interface.
Example how to separate output for anonymous and logged in users:
{% if portal_state.anonymous() %} anon {% else %} logged in {% endif %}
user
User variable holds the current user security information.
This implements Basic user interface.
The most useful feature is getting the current username via getUserName().
member
User membership information. This information depends on the used member backend (Plone default, LDAP, SQL, custom…).
portal_url
portal_url returns the current portal root url when called.
Example:
<a href="{{ portal_url() }}">Home</a>
Advanced examples
News & blog table
The following snippet will create a table with two columns. The left column is filled with a summary and link to all published news on the site. The right column is filled with links to external blog entries, taken from a RSS feed. The news query is language sensitive - only news for the current active language are shown.
Both columns are limited to three entries.
The text is translated and when the default Plone translation catalogs lack suitable msgids, a custom translation catalog twinapex is used.
This example must be put into unfiltered template input box, since Kupu seems to insert unwanted characters into the code.
Example:
<table class="front-page two-column"> <tbody> <tr> <td class="column-2"> <h2> <a href="{{ portal_url() }}/news"> {{ translate("news", "twinapex") }} </a> </h2> {% for item in query({"portal_type":"News Item", "review_state" : "published", "sort_on":"Date", "sort_order":"reverse", "sort_limit":3}) %} <div class="fp-item"> <a href="{{ item.getURL() }}">{{ item.Title }}</a> <p> {{ item.Description }} </p> <p class="timestamp">{{ item.Date }}</p> </div> {% endfor %} <p class="more"> <a class="more" href="{{ portal_url() }}/news"> {{ translate("box_more_news_link", default="More news...") }} </a> </p> </td> <td class="column-2"> <h2> <a href="{{ portal_url() }}/news"> {{ translate("blog", "twinapex") }} </a> </h2> {% for item in rss_feed("http://blog.redinnovation.com/feed/")[0:3] %} <div class="fp-item"> <a href="{{ item.url }}">{{ item.title }}</a> <p class="timestamp">{{ item.friendly_date }}</p> </div> {% endfor %} <p class="more"> <a class="more" href="http://blog.twinapex.fi"> {{ translate("box_morelink", default="More...") }} </a> </p> </td> </tr> </tbody> </table>
Debugging tips
If the template compilation fails you might have made copy-paste errors. Please view the template in raw HTML mode to track down the errors:
HTML tags inside a template expression
Hard line breaks inside a template expression
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Hashes for collective.easytemplate-0.8.0.zip
Algorithm | Hash digest | |
---|---|---|
SHA256 | 7a0c630afa600ba1d8081e5d989e05060c051e059dbea9ce81ca2d8b69b5c8b3 |
|
MD5 | d052b43c160bcdfb295d6e21cffbb0b9 |
|
BLAKE2b-256 | 7a39977667e5095629090b9c44b89907111dd49500e7c819e9c35da475f13cfd |