Package: collective.easytemplate
ContentsIntroductionMotivationUse casesExampleInstallationSecurity noticeRunning unit testsAuthorsSponsorshipAbout the template engine backendsTemplated elementsDocumentFields and widgetsPortletsEmailSecurityTemplate authoring guideTemplate contextcontext variableUnicode and UTF-8Traversingportalportal_stateusermemberportal_urlTagsexplorequeryviewviewletproviderrss_feedlist_folderlatest_newstranslatecurrent_languageAdvanced examplesNews & blog tableDebugging tipsRegistering new tagsIntroductionEasy 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.MotivationPlone lacks out of the box support for custom, extensible,
templating support for content editors.Use casesPossible use cases are e.g.Use unfiltered HTML on page body (<script> et. al)Adding dynamic listings and tables on pages, like news listingAdding dynamic email bodies, titles and receivers in content rules actionsAdding generated content to content rule action emailsShow different text to logged in and anonymous usersCreating a simple text portlet dynamicallyExampleThe 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:MathMarketingChemistryMore information and examples.InstallationAdd to your buildout:eggs =
        collective.easytemplate

zcml =
        collective.easytemplateRun Add-on product installer forEasy Templateproduct.collective.easytemplate depends oncollective.templateenginesandJinja2template engine.Security noticeBecause 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 testsPython eggs Cheetah, Jinja2 and Products.LinguaPlone must be installed in order to run all unit tests.AuthorsMikko Ohtamaa, opensourcehacker.comHans-Peter Locher [mr_savage][jensens]SponsorshipThe development of this product was sponsored byLondon School of Marketing.About the template engine backendsBy default, Jinja 2 template engine is used.
Please refer toJinja 2 documentationfor 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 bycollective.templateenginesbackend.Only on Jinja engine Zope security is supported. Other template
engines do not provide compatible sandboxes.Templated elementsEasy Template product makes it possible to use templates in the following places.DocumentUse Templated Document content type to add scriptable Plone pages.This content item has a tabTemplatewhich 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 adminUnfiltered 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/testTemplateto the object url. It will return
plain text view what the cooked template has eaten.Fields and widgetscollective.easytemplate.fields.TemplatedTextFieldallows 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.PortletsUse 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 TALESExpressionfield to determine whether
portlets should be visible or not. This allows showing
and hiding portlets conditionally.This is useful for special cases likeShowing portlets by languageShowing portlets for specific users onlyShowing portlers for certain timeetc.Read more how to write expressions here.EmailUse Templated Mail Action to add scripting to your content rules based outgoing email messages.Template expansion is available in all the fields:recipients,subjectandmessage. 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"])
                ...SecurityBy 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 availablehere.Template authoring guideThis document describes available variables and functions in
Easy Template elements. Some example template snippets are included.The default Jinja backend exposes tags as functions. Since Jinja
makes clear distinction between variables and functions, you need to add ()
after tags to render them.Template contextTemplate context holds the top level variables you have available in
your template.Variables are defined incontext/plone.pysource code file.context variablePlone usessubsystem called Archetypesto 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 thecontextvariable.
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-8Jinja, like Python 2.x software usual, assumes all strings are either ASCII or Unicode.If you are outputting text whichcontains international charactersis known to be UTF-8you must decode the input text in your template. For Plone, the following is known to be UTF-8All Archetypes text field accessors like Title(), Description() return UTF-8 bytestringsportal_catalog entries like Title, Description reflect directly Archetypes values and contain UTF-8 bytestringsFor other strings, consult Plone source codeTo 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)NoteAs always, there is an exception to the rule. Content rule emails use UTF-8
internally and in them variables must not be decoded.TraversingTraversing 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 }}portalPortal 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_stateportal_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 implementsIPortalStateinterface.Example how to separate output for anonymous and logged in users:{% if portal_state.anonymous() %}
        anon
{% else %}
        logged in
{% endif %}userUser variable holds the current user security information.This implementsBasic userinterface.The most useful feature is getting the current username via getUserName().memberUser membership information. This information depends on the used
member backend (Plone default, LDAP, SQL, custom…).portal_urlportal_url returns the current portal root url when called.Example:<a href="{{ portal_url() }}">Home</a>TagsTags are custom functions you are able to use in your templates.
They provide an easy way to extend templates with your own Python functions.
Tags are registered in tagconfig.py file in the collective.easytemplate package.Easy Template comes with several useful tags out of the box and they are explained below.exploreDump object methods and variables for developer consumption.Warning. This tag is not multiuser safe.
You want to disable this tag on production site, since it is a read priviledge escalation.Explore tag helps you to build scripts by exposing the variables and methods insde the objects.
It prints a tabular output of available methods and variables.Parameters:object: Object to exploreShow the guts of current Templated Document object:{{ explore(context) }}Show what we have available in the portal root:{{ explore(portal) }}Show what was returned by a function which returns a list - take the first element:{{ explore(query({"portal_type":"Folder})[0]) }}queryReturn site objects based on search criteria.Query returns the list of site objects as returned by portal_catalog
search. The objects are catalog brains: dictionaries containing
metadata ids as key.See ZMI portal_catalog tool for avaiable query index and returned metadata fields.Key-value pairs are taken as the parameters and they are directly passed to
the portal_catalog search.The output is limited by the current user permissions.Parameters:searchParameters: Python dictionary of portal_catalog query parameters. index->query mappings. Bad index id does not seem to raise any kind of an error.Return value:List of ZCatalog brain objects. Brain objects have methods getURL, getPath and getObject and dictionary look up for catalog metadata columns.ExamplesReturn the three most fresh News Item objects sorted by date:{{ query({"portal_type":"News Item","sort_on":"Date","sort_order":"reverse","sort_limit":3,"review_state":"published"}) }}Return items in a particular folder:{{ query({path={"query" : "/folder", depth: 0}}) }}For more information about possible query formats seethis old document.viewRender a browser:page based view. If there is no registered view for id, return a placeholder string.Parametername: View id, as it appears in browser/configure.zcml.Parameterfunction: Optional. View instance method name to be called. If omitted, __call__() is used.Example (render sitemap):{{ view("sitemap_view", "createSiteMap") }}viewletRender a viewlet.Parametername: Viewlet id as it appears on portal_view_customizations ZMI page.Example:{{ viewlet("portal.logo") }}providerThis is equivalent of TAL provider expressin which is used to render viewlet and portlet managers.To render all the left column portlets call:{{ provider("plone.leftcolumn") }}rss_feedThe function reads RSS feed. You can iterate manually through entries
and format the output. This is mostly suitable when dealing with HTML
source code.Parametersurl: URL to RSS or RSScache_timeout: Optional, default value 60. Seconds how often the HTTP GET request should be performed.ReturnList of dictionaries with following keys:title,summary,url,updatedandfriendly_date.Example (raw HTML edit):{% for entry rss_feed("http://blog.redinnovation.com/feed/") %}
        <p>
                <b>Title:</b>
                <span>{{ entry.title }}
        </p>

        <p>
                <b>Summary:</b>
                <span>{{ entry.summary }}
        </p>
{% endfor %}plone.app.portlets.rss.RSSFeed is used as the RSS reader backend.list_folderList folder content and return the output as <ul> tag. Mostly suitable for
simple folder output generating.The formatting options offered here are not particular powerful.
You might want to use query() tag for more powerful output formatting.Parametersfolder: The path to the listed folder. Same as the URI path on the site.title: Render this title for the listing as a subheadingfilters: portal_catalog query parameters to be applied for the output. See query() below for examples.exclude_self: If True do not render context Templated Document in the outpputextra_items: String of comma separated entries of URIs which are outside the target folder, but should be appended to the listing.Example (create a course module listing from a course folder):{{ list_folder("courses/marketing/cim-professional-certificate-in-marketing", title="Other modules in this course:", filters={ "portal_type" : "Module"}) }}latest_newsRender list of latest published news from the site. Usescollective.easytemplate.tags/latest_news.pttemplate.latest_news also serves as an example how to drop a custom view into the visual editor.Parameterscount: How many items are renderedExample:{{ latest_news(3) }}translateTranslation catalog look up with an message id.Translates the message to another language. The function assumes
the translation is available in gettext po files.Parametersmessage: gettext msgid to translatedomain: gettext domain where the message belongs, optional, defaults to “plone”language: target language code, e.g. fi, optional, defaults to the currently selected languagedefault: The default value to be displayed if the msgid is missing for the selected languageReturntranslated stringExamples:{{ translate("missing_id", default="Foobar")  }}

{{ translate("box_more_news_link", "plone", "fi")  }}For available default Plone msgids, seePloneTranslations product sourcecurrent_languageGet the current language for the user.This enables conditional showing based on the language.ParametersNo parametersReturnThe current language code as a string, e.g. “fi”Example:{% if current_language() == "fi" %}
        Paivaa
{% else %}
        Hello
{% endif %}Advanced examplesNews & blog tableThe 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 catalogtwinapexis used.This example must be put into unfiltered template input box,
since Kupu seems to insert unwanted &nbsp; 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 tipsIf 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 expressionHard line breaks inside a template expressionRegistering new tagsIf you want to add your template functions
you must add them to collective.easytemplate.tagconfig module (note:
in the future Zope configuration directives and ZCML can be used
to register the tags).All tags implement collective.templateengine.interfaces.ITag interface.For example code, see the existing tags in collective.easytemplate.tags package.Example how to register a custom tag (run in your product’s initialize() method):from collective.easytemplate import tagconfig

from myproducts import MyTag

tagconfig.tags.append(MyTag())