nti.nikola_chameleon

Latest release Supported Python versions https://travis-ci.org/NextThought/nti.nikola_chameleon.svg?branch=master https://coveralls.io/repos/github/NextThought/nti.nikola_chameleon/badge.svg?branch=master Documentation Status

An extremely flexible template system for the Nikola static blog system using Chameleon, z3c.pt and z3c.macro

A basic template using this system is available in base-chameleon, and an extension of that using bootstrap3 is available in bootstrap3-chameleon.

Documentation is hosted at https://ntinikola_chameleon.readthedocs.io/

Installation

Nikola uses a custom mechanism to find plugins instead of using the usual pkg_resources system. That makes it incredibly difficult to install plugins; it’s not enough just to pip install a package from PyPI. Instead, you must also copy a .plugin file to a particular location on disk. This can be:

  • ~/.nikola/plugins/

  • The plugins directory of your Nikola site.

Beside that ‘.plugin’ file there must also be a ‘.py’ file of the same name that the plugin lists as a module (yes, even though the plugin file specifically requests a Python module, yapsy requires that it be a file or directory beside the plugin file—so not really a module).

It’s ridiculous to require everyone to copy plugins into their plugin folder (they’re not even correctly on sys.path, meaning that zope.configuration and many other tools won’t work) and we don’t plan to let Nikola do that automatically (we’re not on the Nikola plugin index and won’t be until they let us do standard installs), so the best we can do is attempt to workaround yapsy’s limitations.

Into your site’s plugin directory, place the following .py file:

# nti_nikola_chameleon.py
from nti.nikola_chameleon import *

Beside that, you’ll need a nti.nikola_chameleon.plugin file:

# -*- mode: conf; -*-
[Core]
Name = nti.nikola_chameleon
Module = nti_nikola_chameleon

[Documentation]
Author = NextThought
Version = 1.0
Website = https://github.com/NextThought/nti.nikola_chameleon
Description = Support for Chameleon ZPT templates.

[Nikola]
PluginCategory = Template

Once you have successfully installed the project, proceed to Getting Started. For a more complete understanding of how this package works, see Leveraging the Component Architecture.

Caution

This documentation is a work in progress.

Getting Started

Note

For the purposes of this document, the implementation of Zope Page Templates provided by Chameleon working together with z3c.pt will simply be referred to as “page templates” or abbreviated to “ZPT.”

Introduction

This package allows developing Nikola templates using Chameleon. Chameleon is a fast implementation of the Zope Page Templates specification. Unlike template systems such as Jinja2 or Mako, page templates are designed to be valid (X)HTML and may be edited in HTML editors. It’s even possible to use visual design tools to produce pages that are then relatively easily turned into page templates; sometimes it’s even possible to edit those templates again in the same visual design tool.

Component Architecture

Page templates can be much more than that, though. When combined with the Zope component architecture (ZCA), they can be designed to be extensible and flexible in a way that the simple template inheritance schemes of other template systems cannot match. This package is designed to embrace that, making it possible to create and extend templates and themes very easily.

Note

Although we believe the component architecture can be used to make themes flexible and make changing and customizing them through inheritance or configuration easier, you are not required to use these features if you don’t want to. You can build an entire theme just based on the files in the templates directory and the Chameleon load: expression.

For a discussion of how the ZCA can be leveraged in your themes, see Leveraging the Component Architecture.

Prerequisites

To create templates with this package, you’ll need a basic understanding of the following:

It may be helpful to understand object traversal, similar to what can be used in the Pyramid web framework.

We use zope.traversing to implement traversal, and provide several “path adapters” and views to make traversal in path expressions more convenient. For more information on those helpers, see Path Helpers.

Macros and Viewlets

Most templates will also make use of macros, and many will also use viewlets. We’ll discuss how each of these can be used to develop flexible templates in Using Macros and Using Viewlets, respectively. Complete examples using these techniques can be found in base-chameleon, and its extension using bootstrap3, bootstrap3-chameleon.

Template Variables

Your templates will have access to all of the template variables Nikola defines. These are made available in the options dictionary, one of the standard names available to page templates. The context standard name is often a Nikola post object (or other Nikola object), and the context-specific template variables are available through it.

Creating a Theme

Creating a theme proceeds as documented in the Nikola reference. This document will focus on the template features that nti.nikola_chameleon provides. Make sure you’ve read Getting Started before reading this.

Your theme should be laid out according to the Nikola documentation. You’ll need to specify nti.nikola_chameleon as the engine in your theme meta file.

The only directory that nti.nikola_chameleon is interested in is your templates/ folder.

There are three types of files that nti.nikola_chameleon will look for in this directory: templates, macros (helpers) and ZCML configuration. A complete them can be written just with template files and (optionally, but helpfully) macro files.

Note

This document will contain frequent mentions of the ways the component architecture can be used to make themes flexible and make changing and customizing them through inheritance or configuration easier. You are not required to use these features if you don’t want to. You can build an entire theme just based on the files in the templates directory and the Chameleon load: expression.

Templates

Files in the templates directory that match the pattern *.tmpl.pt are ZPT files that Nikola can directly use as templates (when stripped of the .pt suffix). For example, index.tmpl.pt will be used when Nikola requests index.tmpl. Nikola maintains a list of the various templates it may use by default (although a number of those templates, such as base.tmpl and archive_navigation_helper.tmpl are internal helpers specific to the default Nikola theme implementations, even if they are not called out as such).

The most obvious way to create a theme, then, is to create a .tmpl.pt file for each standard template that Nikola uses and populate it with your design.

Views for Sharing Macros

Each .tmpl.pt file found in this directory is registered as a default view with the same name so it can be found via traversal. This is useful to be able to share macros and fill slots. For example, if base.tmpl.pt is the generic “layout” template and it defines the macro base with the slot content, the story.tmpl.pt may take advantage of that layout by using that macro and filling in that slot, finding the base.tmpl macro via traversal:

<html metal:use-macro="context/@@base.tmpl/index/macros/base"
      xmlns:i18n="http://xml.zope.org/namespaces/i18"
      xmlns:metal="http://xml.zope.org/namespaces/metal">

    <article metal:fill-slot="content">
         My content template goes here
     </article>
</html>

Although the standard Chameleon load: expression type is available, the traversal based mechanism is much more flexible because it allows themes that extend yours to provide a new base.tmpl view. It is also useful to provide different macros depending on the context object (or whatever objects you traverse through). For more on that, see Leveraging the Component Architecture, Theme Inheritance and ZCML.

Macros

Files in the templates directory that match the pattern *.macro.pt are not used directly by Nikola as templates. Instead, they are parsed to find all of the macros they define. Each macro is registered as the default macro for its name so that the macro: expression type from z3c.macro can be used to find it.

The most direct translation from the Nikola base template implementations and documentations would be to have each _helper.tmpl become a .macro.pt file, for example, math_helper.macro.pt and post_helper.macro.pt for math_helper.tmpl and post_helper.tmpl, respectively.

Continuing our story example above, suppose the file post_helper.macro.pt defined an html_title macro:

<metal:block xmlns:tal="http://xml.zope.org/namespaces/tal"
             xmlns:metal="http://xml.zope.org/namespaces/metal"
             xmlns:i18n="http://xml.zope.org/namespaces/i18n">

  <metal:block metal:define-macro="html_title">
    <h1 class="p-name entry-title" itemprop="headline name"
        tal:define="title options/title"
        tal:condition="python:title and not context.meta('hidetitle')">
      <a href="${context/permalink}" class="u-url">${context/title}</a>
    </h1>
  </metal:block>

</metal:block>

Our story.tmpl.pt (and other files) could use this macro like so:

<html metal:use-macro="context/@@base.tmpl/index/macros/base"
      xmlns:i18n="http://xml.zope.org/namespaces/i18"
      xmlns:metal="http://xml.zope.org/namespaces/metal">

    <article metal:fill-slot="content">
        <header>
           <h1 metal:use-macro="macro:html_title">Replaced by the title</h1>
        </header>
        My content template goes here
     </article>
</html>

Now, we could have implemented that with the load: expression type:

<html metal:use-macro="context/@@base.tmpl/index/macros/base"
      xmlns:i18n="http://xml.zope.org/namespaces/i18"
      xmlns:metal="http://xml.zope.org/namespaces/metal">

    <article metal:fill-slot="content">
        <header tal:define="post_helper load:post_helper.macro.pt">
           <h1 metal:use-macro="post_helper/macros/html_title">Replaced by the title</h1>
        </header>
        My content template goes here
     </article>
</html>

However, as with templates, the use of the macro: expression type allows themes to extend us and replace that macro with their own version, and it allows us to produce macros that do different things depending on context. For more on that, see Leveraging the Component Architecture, Theme Inheritance and ZCML.

Caution

If you implement a macro of the same name in two different files, nti.nikola_chameleon will warn you, and the one in the last file that defines it will be what is registered.

See also

Using Macros

ZCML

Finally, after registering all the templates and macros, if your directory contains a theme.zcml file, nti.nikola_chameleon will load that file. It is a standard zope.configuration file.

You can use this file to replace any registrations that nti.nikola_chameleon makes by default. You can also use it to provide more specific versions of macros, tailored to particular types of objects, and you can use it to provide viewlets. (For more on viewlets see Using Viewlets.) You can also use it to rename entire templates or register more specific templates.

The theme.zcml file is executed in the nti.nikola_chameleon package. This means that you can easily refer to the various object types with a simple . prefix.

If your theme extends another theme, the ZCML will be executed in order of theme inheritance; this allows themes to replace registrations from earlier themes. For more on theme inheritance, see Theme Inheritance.

Let’s take a look at an example. Don’t worry if much of it doesn’t make sense yet, we’ll cover those concepts later.

<!-- -*- mode: nxml -*- -->
<configure  xmlns="http://namespaces.zope.org/zope"
        xmlns:i18n="http://namespaces.zope.org/i18n"
        xmlns:zcml="http://namespaces.zope.org/zcml"
        xmlns:z3c="http://namespaces.zope.org/z3c"
        xmlns:browser="http://namespaces.zope.org/browser"
        >

  <include package="z3c.macro" />
  <include package="z3c.macro" file="meta.zcml" />
  <include package="z3c.template" file="meta.zcml" />
  <include package="zope.viewlet" file="meta.zcml" />
  <include package="nti.nikola_chameleon" file="meta.zcml" />

  <!-- Extra macros -->
  <z3c:macro name="open_graph_metadata"
         for=".interfaces.IPost"
         view="*"
         template="post_helper.pt"
         layer="*" />

  <!-- Viewlets and Viewlet managers -->
  <!-- To extend, use a new name. To replace use the same name
       with at least as specific a registration.
  -->

  <!-- Extra head -->
  <!-- The normal extra head for a page is called 'default_extra_head' -->
  <browser:viewletManager
      name="extra_head"
      provides=".interfaces.IHtmlHeadViewletManager"
      class="zope.viewlet.manager.WeightOrderedViewletManager"
      permission="zope.Public"
      />

  <browser:viewlet
      name="default_extra_head"
      manager=".interfaces.IHtmlHeadViewletManager"
      template="v_index_extra_head.pt"
      permission="zope.Public"
      layer=".interfaces.IIndexPageKind"
      weight="0"
      />

  <!--
  We don't have files on disk that match all the template names
  that Nikola likes to use by default. So lets set up some aliases
  to the files that we *do* have that implement the required
  functionality.
  -->
  <z3c:template
    template="index.tmpl.pt"
    name="archiveindex.tmpl"
    layer=".interfaces.IArchiveIndexPageKind"
    />

  <z3c:template
    template="generic_post_list.pt"
    name="tag.tmpl" />

  <z3c:template
    template="generic_post_list.pt"
    name="author.tmpl" />
</configure>

Other Files

Any other files in this directory are ignored by nti.nikola_chameleon. You can use plain .pt files to implement additional macros or entire templates. You can refer to them in your theme.zcml file (preferred) and access them via macro: expressions or traversal, or you could explicitly reference them using load: expressions.

An Introduction to the Zope Component Architecture

Note

This document is not intended to be a comprehensive guide to zope.component or component systems in general. It will only discuss concepts that are relevant to this package. For more information, see the zope.component documentation or this guide.

This document will provide a brief introduction to some of the concepts in a component architecture and an example specifically of using zope.component. If you’re already familiar with these things, you can skip it.

Definitions

For our purposes, we will define a component as a unit of software that provides some specific functionality. What that functionality is and what it does is the component’s interface; a single component may provide multiple different functions and thus multiple interfaces. (Interfaces can also be thought of as a way to represent a particular concept.)

A component registry provides us with a way to find components that provide specific interfaces. Sometimes there can me multiple components that can all provide the same interface (different ways to implement a specific function), so we can choose between them based on a name.

Sometimes a component will need particular other objects in order to fulfill its function. In this case the component registry stores a factory, and when we ask it for the component, we provide the objects we want to work with to the registry. The registry then finds the correct factory, passes it the objects, and returns the resulting component. This process is called using an adapter because the factory adapts the objects we give it to produce the desired component. (In Python, factories are often classes, and the returned component is just an instance of the class.)

Certain types of factories will know how to adapt certain kinds of objects, while other types of factories will know how to adapt others, so the component registry will inspect the objects we give it to find the most specific factory—the one that’s the best fit for the objects we give.

In Python, objects are components. Interfaces can either be represented by classes, or more flexibly by the interface objects created by zope.interface. (These are more flexible than classes because they can be added and removed from particular objects independently of any code.)

Example

For example, let’s imagine we’re working in the United Nations general assembly, and we need to provide language translators for the various diplomats.

We’ll begin by defining an interface for someone who can speak a language, plus some specific language examples:

>>> from zope.interface import Interface
>>> class ILanguageSpeaker(Interface):
...    """Someone who can speak a language"""
>>> class IGermanSpeaker(ILanguageSpeaker):
...    """Someone who can speak German."""
>>> class ISpanishSpeaker(ILanguageSpeaker):
...    """Someone who can speak Spanish"""
>>> class IFrenchSpeaker(ILanguageSpeaker):
...    """Someone who can speak French."""

Now we’ll create some diplomat classes and let them speak their native language (because the class is a factory, this is called implementing an interface; the resulting class instance is said to provide the interface):

>>> from zope.interface import implementer

>>> class SensibleRepr(object):
...     def __repr__(self):
...        return '<%s>' % (type(self).__name__)

>>> @implementer(IGermanSpeaker)
... class GermanDiplomat(SensibleRepr):
...    """The German diplomat speaks German."""

>>> @implementer(ISpanishSpeaker)
... class SpanishDiplomat(SensibleRepr):
...    """The Spanish diplomat speaks Spanish."""

>>> @implementer(IFrenchSpeaker)
... class FrenchDiplomat(SensibleRepr):
...    """The French diplomat speaks French."""

>>> french_diplomat = FrenchDiplomat()
>>> spanish_diplomat = SpanishDiplomat()
>>> german_diplomat = GermanDiplomat()

None of the diplomats speak the same language and are thus unable to communicate. Let’s remedy that by providing a translator. We’ll first define an interface to represent someone that can translate:

>>> class ILanguageTranslator(ILanguageSpeaker):
...    """Someone who can translate between languages. """

If we ask the component registry for a translator for the Spanish and German diplomats, we won’t find anyone yet (we use zope.component.getMultiAdapter() to request an adapter for multiple objects):

>>> from zope import component
>>> component.getMultiAdapter((spanish_diplomat, german_diplomat), ILanguageTranslator)
Traceback (most recent call last):
...
zope.interface.interfaces.ComponentLookupError: ((<SpanishDiplomat...>, <GermanDiplomat...>),...

Let’s create someone who can speak both languages:

>>> @implementer(ISpanishSpeaker, IGermanSpeaker)
... class Steve(object):
...    """Steve speaks two languages."""
...
...    def __repr__(self):
...       return "<Hi, I'm Steve>"
>>> steve = Steve()

Now lets hire that person and put them to work as a translator by registering them in the component registry (notice that the object that implements ILanguageTranslator is given the two people who need the translating done, but Steve doesn’t need them to do his job—he can translate for any Spanish and German speakers—so we just use a lambda function):

>>> component.provideAdapter(lambda spanish_speaker, german_speaker: steve,
...                          provides=ILanguageTranslator,
...                          adapts=(ISpanishSpeaker, IGermanSpeaker))

We can now find someone who will translate for the diplomats:

>>> component.getMultiAdapter((spanish_diplomat, german_diplomat), ILanguageTranslator)
<Hi, I'm Steve>
Extending Interfaces

But what if the diplomats need to have a conversation about Security Council matters, something that Steve isn’t cleared for? We’ll need an interface to represent a translator with a security clearance:

>>> class ISecureLanguageTranslator(ILanguageTranslator):
...   """A secure translator."""

We’ll imagine that computer translation skills are proceeding apace and are good enough for this sort of thing, so we’ll create a computer that can speak all the languages. As a computer, it’s considered inherently secure:

>>> @implementer(ISecureLanguageTranslator,
...              ISpanishSpeaker,
...              IGermanSpeaker,
...              IFrenchSpeaker)
... class ComputerTranslator(object):
...    def __init__(self, *args):
...        self.a, self.b = args
...    def __repr__(self):
...        return '<ComputerTranslator for %r %r>' %(self.a, self.b)

(The computer might want to know exactly who it is translating for—maybe to adapt to regional dialects—so we’ll let it have access to the diplomats.) Now we can install the computer to do some secure translating:

>>> component.provideAdapter(ComputerTranslator,
...                          provides=ISecureLanguageTranslator,
...                          adapts=(ISpanishSpeaker, IGermanSpeaker))
>>> component.provideAdapter(ComputerTranslator,
...                          provides=ISecureLanguageTranslator,
...                          adapts=(ISpanishSpeaker, IFrenchSpeaker))

The diplomats can now have a secure conversation:

>>> component.getMultiAdapter((spanish_diplomat, german_diplomat), ISecureLanguageTranslator)
<ComputerTranslator for <SpanishDiplomat> <GermanDiplomat>>

Steve only speaks Spanish and German, but what if the Spanish and French speakers want to have a (non-secure) conversation about their home town football teams? Steve can’t do it. Can anyone?

>>> component.getMultiAdapter((spanish_diplomat, french_diplomat), ILanguageTranslator)
<ComputerTranslator for <SpanishDiplomat> <FrenchDiplomat>>

The computer can! Because ISecureLanguageTranslator extends ILanguageTranslator, when we ask for the latter, the registry is smart enough to know that a secure translator can just as well handle non-secure communications.

Names

Steve is a great German speaker, but his Spanish accent is a bit rough, and the Spanish diplomat would prefer someone a bit easier to understand, so we’ll hire someone else.

>>> @implementer(ISpanishSpeaker, IGermanSpeaker)
... class Joe(object):
...    """Joe speaks two languages."""
...
...    def __repr__(self):
...       return "<Hi, I'm Joe>"
>>> joe = Joe()

This time, we’ll register the translator so that the Spanish diplomat can ask for the translator by name:

>>> component.provideAdapter(lambda spanish_speaker, german_speaker: joe,
...                          name="Joe",
...                          provides=ILanguageTranslator,
...                          adapts=(ISpanishSpeaker, IGermanSpeaker))

>>> component.getMultiAdapter((spanish_diplomat, german_diplomat),
...                           ILanguageTranslator,
...                           name="Joe")
<Hi, I'm Joe>

Steve is still available by default:

>>> component.getMultiAdapter((spanish_diplomat, german_diplomat), ILanguageTranslator)
<Hi, I'm Steve>

Leveraging the Component Architecture

Background

If you’re not familiar with component architectures, and zope.component in particular, please take a few minutes to read An Introduction to the Zope Component Architecture.

Key Objects

Three of the the standard names available in templates play a particularly important part in the way templates, macros and viewlets are used.

Those names are context, request and view. These names are inherited from the Zope family of Web frameworks, but they have equal relevance here. We’ll begin by discussing each of the objects individually, and then in Component Lookup we will explore how they come together.

Context

The context is the object being rendered. It is most commonly a Nikola post (represented by IPost or IMathJaxPost), but it can also be a IPostList on index pages (or IMathJaxPostList), or IGallery, IListing or ISlide on their respective pages.

Tip

The template variables for specific template pages are available from the context object, in addition to being available from the options dictionary. Using context can result templates that are more clear in their intent.

Request

The request (also known as the layer for the role it plays) expresses the intent of the render. It tells us the purpose of our rendering. It will always provide the IPageKind interface. Indeed, it will actually always provide one of the more specific interfaces that better identifies what we’re trying to render, such as ITagsPageKind.

In the Zope web framework, various interfaces are applied to the request to determine the “skin”, or appearance, of web pages. Layers are also used to determine available functionality such as the contents of menus. IPageKind is used for similar purposes.

View

Finally, the view is the code that initiated the rendering. In the web frameworks, this typically corresponds to the last portion of the URL. It determines the overall output by choosing the template to render and providing additional functionality. For example, if the context is a JPEG photograph, a URL like /photo.jpg/full_screen may use a view called full_screen and result in a full-screen view of the photograph, while for the same photo, a URL like /photo.jpg/thumbnail may produce a small thumbnail using a view called thumbnail

In Nikola, it is always the Nikola engine that initiated the rendering process, and so the view object is always the same, an instance of View. This object provides a templates attribute to access the ChameleonTemplates object, through which the Nikola site object can be found.

In addition, the view also has “layer” interfaces applied to it to express various aspects of system configuration. Currently the only such layer is the ICommentKind. More specifically, a particular subinterface identifying whether comments are disabled (ICommentKindNone) or enabled (ICommentKindAllowed) and if they are enabled what specific comment system is being used (ICommentKindDisqus).

Caution

The specific layer interfaces applied to requests and views are subject to change in future versions. The division now is somewhat arbitrary, but the intent is to allow for registering macros for a request layer of IStoryPageKind when comments are ICommentKindDisqus; to do that we need to be able to separate those interfaces on different objects, or introduce a bunch of unified interfaces (IStoryPageKindCommendKindDisqus), which is intractable.

Component Lookup

Now that we know about the three key objects, we can talk about exactly why they are so key and how they are used. In short, they are used for component lookup through the system, and it is this layer of indirection that permits us flexibility and allows for easy Theme Inheritance. Or to put it more pragmatically, they allow us to declaratively configure how templates are composed and used, instead of creating a mess of spaghetti code.

Throughout, we will use the example of Nikola rendering a page (not a blog post) in a system that has Disqus comments enabled.

Templates

Let’s begin at the beginning. When Nikola asks to find a template, say page.tmpl, we begin by determining the correct context, request and view to use, applying the appropriate layers to each object.

At this point we have a context implementing IPost, a request implementing IPostPageKind, and a view implementing ICommentKindDisqus.

These three objects together are used to ask the component registry for an adapter to IContentTemplate. This means that, in addition to the name, we have three degrees of freedom for finding a template. This is a great way to keep programattic logic out of your templates, streamlining them, reducing the chances of error, and shifting runtime checks to faster declarative configuration.

By default, if a page.tmpl.pt file exists on disk, it will be found registered for the page.tmpl template name. All such default templates are registered with the lowest priority, least-specific interfaces possible. This means that you can easily provide a more specific template customized exactly for pages that have disqus comments enabled.

In your theme.zcml, you would include a z3c.template directive to register this template. (We’ll say that this custom template file is named disqus_page.pt. Remember that the template we’re replacing, the one Nikola is asking for, is named page.tmpl)

<configure  xmlns="http://namespaces.zope.org/zope"
            xmlns:i18n="http://namespaces.zope.org/i18n"
            xmlns:zcml="http://namespaces.zope.org/zcml"
            xmlns:z3c="http://namespaces.zope.org/z3c"
            xmlns:browser="http://namespaces.zope.org/browser"
            >
  <include package="z3c.template" file="meta.zcml" />

  <z3c:template
     template="disqus_page.pt"
     name="page.tmpl"
     context=".interfaces.IPost"
     layer=".interfaces.IPostPageKind"
     for=".interfaces.ICommentKindDisqus"
     />

</configure>

Note

In the z3c:template directive, layer means the request object, and for means the view object.

Although this is certainly possible, and may work well for extremely special cases, or for keeping your .pt files as full HTML that can be edited in a visual editor, replacing an entire template like this is rare, though. There are more effective ways to control the content on portions of a page with macros and viewlets.

Adding Templates

Another way to use this lookup of templates in the component registry is to add templates that don’t exist on disk (and hence wouldn’t be registered by default). This is useful when templates are similar enough that they can be collapsed into a single file, with all of what needs to be different between them managed by macros and viewlets registered for specific interfaces. The other templating systems Nikola supports don’t have this flexibility, so Nikola asks for a different template name in almost every situation; we can instead share a template and simply supply it under different names.

Again, your theme.zcml would include z3c.template directives to do this:

<configure  xmlns="http://namespaces.zope.org/zope"
            xmlns:i18n="http://namespaces.zope.org/i18n"
            xmlns:zcml="http://namespaces.zope.org/zcml"
            xmlns:z3c="http://namespaces.zope.org/z3c"
            xmlns:browser="http://namespaces.zope.org/browser"
            >
   <include package="z3c.template" file="meta.zcml" />


    <!--
    We don't have files on disk that match all the template names
    that Nikola likes to use by default. So lets set up some aliases
    to the files that we *do* have that implement the required
    functionality.
    -->
    <z3c:template
      template="index.tmpl.pt"
      name="archiveindex.tmpl"
      layer=".interfaces.IArchiveIndexPageKind"
      />

    <z3c:template
      template="generic_post_list.pt"
      name="tag.tmpl" />

    <z3c:template
      template="generic_post_list.pt"
      name="author.tmpl" />
  </configure>
Macros

When you use the macro: expression type in a template, the registered macro is also looked up based on the current context, request, and view. Suppose we are in our disqus_page.pt rendering with our IPost context, IPostPageKind request layer, and ICommentKindDisqus view.

<div metal:use-macro="macro:comments" />

The registered macro named comments will be looked up for those three objects.

Just as with templates, the macros that are found in .macro.pt files are registered for the most generic, least specific interfaces possible. They can be augmented or replaced in your theme.zcml using the z3c.macro directives:

<configure xmlns="http://namespaces.zope.org/zope"
           xmlns:i18n="http://namespaces.zope.org/i18n"
           xmlns:zcml="http://namespaces.zope.org/zcml"
           xmlns:z3c="http://namespaces.zope.org/z3c"
           xmlns:browser="http://namespaces.zope.org/browser"
        >

  <include package="z3c.macro" />
  <include package="z3c.macro" file="meta.zcml" />

  <!-- Extra macros -->
  <z3c:macro
     name="comments"
     template="comment_helper.pt"
     for=".interfaces.IPost"
     view=".interfaces.ICommentKindDisqus"
     layer=".interfaces.IPostPageKind" />

</configure>

By default, the name of the macro in the template file is the same as the name it is registered with: the name attribute in the ZCML element. If they need to be different, you can supply the macro attribute in ZCML.

For example, if we have two different kinds of comment systems in comment_helper.pt:

<tal:block>

   <metal:block metal:define-macro="comments-facebook">
      <!-- Stuff for Facebook comments -->
   </metal:block>
   <metal:block metal:define-macro="comments-disqus">
      <!-- Stuff for Disqus comments -->
   </metal:block>
</tal:block>

We could register them and use them both for different comment systems with this ZCML:

<configure xmlns="http://namespaces.zope.org/zope"
           xmlns:i18n="http://namespaces.zope.org/i18n"
           xmlns:zcml="http://namespaces.zope.org/zcml"
           xmlns:z3c="http://namespaces.zope.org/z3c"
           xmlns:browser="http://namespaces.zope.org/browser"
        >

  <include package="z3c.macro" />
  <include package="z3c.macro" file="meta.zcml" />

  <!-- Extra macros -->
  <z3c:macro
     name="comments"
     macro="comments-disqus"
     template="comment_helper.pt"
     for=".interfaces.IPost"
     view=".interfaces.ICommentKindDisqus"
     layer=".interfaces.IPostPageKind" />
  <z3c:macro
     name="comments"
     macro="comments-facebook"
     template="comment_helper.pt"
     for=".interfaces.IPost"
     view=".interfaces.ICommentKindFacebook"
     layer=".interfaces.IPostPageKind" />

</configure>

For more on using macros (and in particular, how to find a macro for a different context), see Using Macros.

Viewlets

As you might have guessed, viewlets are handled the same way. When you use the provider: tales expression in a template, the resulting provider is found in the component registry using the current context, request and view.

Note

Although the provider: expression type supports generic content providers, this package exclusively uses viewlets. This is because viewlets can be developed using only templates and ZCML, without any Python code.

Unlike macros and templates, there is no automatic registration for viewlet managers and viewlets. Instead, they must all be registered in ZCML.

Suppose we have a template that uses a viewlet:

<head>
  ...
  <!--! The extensible viewlet manager; anyone can add things based
       on view, context and request/layer to it. -->
  <tal:block content="structure provider:extra_head" />
</head>

We would set up and fill that viewlet with this ZCML:

<configure xmlns="http://namespaces.zope.org/zope"
           xmlns:i18n="http://namespaces.zope.org/i18n"
           xmlns:zcml="http://namespaces.zope.org/zcml"
           xmlns:z3c="http://namespaces.zope.org/z3c"
           xmlns:browser="http://namespaces.zope.org/browser"
        >

  <include package="zope.viewlet" file="meta.zcml" />

  <!-- Extra head -->
  <!-- The normal extra head for a page is called 'default_extra_head' -->
  <browser:viewletManager
      name="extra_head"
      provides=".interfaces.IHtmlHeadViewletManager"
      class="zope.viewlet.manager.WeightOrderedViewletManager"
      permission="zope.Public"
      />

  <browser:viewlet
      name="default_extra_head"
      manager=".interfaces.IHtmlHeadViewletManager"
      template="v_index_extra_head.pt"
      permission="zope.Public"
      layer=".interfaces.IIndexPageKind"
      weight="0"
      />

  <browser:viewlet
      name="archive_index_extra_head"
      manager=".interfaces.IHtmlHeadViewletManager"
      template="v_archiveindex_extra_head.pt"
      permission="zope.Public"
      layer=".interfaces.IArchiveIndexPageKind"
      weight="1"
      />

  <!-- And so on as needed -->

</configure>

For much more about viewlets, see Using Viewlets.

Views

When you use the @@view_name syntax in a path expression, the previous path element becomes the context and an adapter from just context and request are looked up by that name.

Example template:

<html metal:use-macro="context/@@base.tmpl/index/macros/base"
      xmlns:tal="http://xml.zope.org/namespaces/tal"
      xmlns:metal="http://xml.zope.org/namespaces/metal">

   ...
</html>

The macro to use is looked for beginning with the expression context/@@base.tmpl. This takes the context object of this template and uses it to find the view (for the current request/layer) named @@base.tmpl. All the .tmpl.pt files are registered as most generic, least specific named views automatically. You can add your own view registrations for specific combinations of context and request if desired, though this usually requires writing Python code.

Tip

Any callable that accepts a context and request can be registered as a view. This package provides several other helpful views.

Using Macros

Macros are a simple way to create re-usable snippets of templates. They can be used as-is, or they can be lightly customized at the point of use by filling slots.

This document will cover the ways in which macros can be used in Nikola themes created with this package; it will generally assume that you already understand the basics of creating and using macros on Chameleon. See the macro reference documentation for more details.

One important thing to point out: the value of a use-macro attribute is a true expression. It is evaluated to find the macro object. It’s not just a simple name. This allows most of the scenarios we will discuss below.

Using Macros From the Same Template

While not particularly common, it is possible to create and use macros all within the scope of a single template. One of the standard names defined for path expressions is template referring to the currently executing template. A Chameleon template has a macros dictionary that lists all the macros defined within by name. Therefore, to use a macro from the same template, we would write an expression like template/macros/macro_name (here, in a file called hello.tmpl.pt):

<html>

  <p metal:define-macro="hello">
      Hello <p metal:define-slot="name">world</p>
  </p>

  <body>

    <p tal:repeat="post context/posts"
       metal:use-macro="template/macros/hello">
       <p metal:fill-slot="name">${post/author}</p>
   </p>

  </body>

</html>

Using Macros From Other Templates

When a template defines macros, they can be used from other templates as well. The key is that use-macro takes an expression; we just have to provide an expression that evaluates to a macro.

Load Expressions

The simplest (but least flexible) way to do this is with Chameleon’s built-in load: expression. This will load a template in from a file relative to the current file. If the hello macro from the previous section was defined in hello.tmpl.pt, the file index.tmpl.pt could use it like this:

<html>
  <body tal:define="hello-template load:hello.tmpl.pt">
    <p tal:repeat="post context/posts"
       metal:use-macro="hello-template/macros/hello">
     <p metal:fill-slot="name">${post/author}</p>
    </p>
  </body>
</html>
Flexibility Through the Component Architecture

Far more flexible is to make use of the component architecture. We can do this in a few ways. One is to rely on the fact that views are automatically registered for each .tmpl.pt found in the theme’s templates directory. Each such view will have an index attribute that is a template, and which in turn will have the macros dictionary. We could change index.tmpl.pt to use this view:

<html>
  <body>
    <p tal:repeat="post context/posts"
       metal:use-macro="context/@@hello.tmpl/index/macros/hello">
     <p metal:fill-slot="name">${post/author}</p>
    </p>
  </body>
</html>

How is this more flexible? Isn’t that just another way to refer to a file?

This is more flexible for two reasons. The first is that a theme that extends us (see Theme Inheritance) could provide its own hello.tmpl view (maybe defining different CSS classes or using <div> instead of <p>) and we would automatically use it. The second (and similar) reason is that the traversal through context gives the component architecture a chance to define a more specific view for that particular type of context. (Of course we could traverse through any object and potentially locate a more specific view for that object.) This lets us (or themes that extend us) do something like use a generic macro for most types of objects, but also provide a custom macro that automatically gets used for galleries or slides. All without having to change the base template!

macro: Expressions

An even more powerful and flexible way to locate macros is with the z3c.macro macro: expression type. Unlike all of the above approaches, this abstracts the notion of a macro away from its location (we don’t have to think about what file or view the macro is defined in). Instead, we just use the macro’s name: <p metal:use-macro="macro:hello" />.

At first this may seem less flexible: at least with traversal we got to choose a variable to traverse through and could register macros for particular context objects. But the macro expression actually automatically searches for the macro based on all three of the context, request, and view. That gives us the same three degrees of freedom to define custom macros for particular types of context objects, pages and comment systems that we have for templates.

All macros found in *.macro.pt files are automatically registered for the lest specific, most generic interfaces possible. You can easily add your own macros in ZCML for more specific registrations.

Finding Macros For a Different Context

The macro: expression always looks up its target based on the current context, request and view. Sometimes, particularly when working with the contents of a container—such as the posts in an index—you want to look up a macro with a different context (the object in the container). The @@macros view lets you do this:

<html>
  <body>
    <p tal:repeat="post context/posts">
     <p metal:use-macro="post/@@macros/display">Display a post.</p>
    </p>
  </body>
</html>
The template Variable and Macros

One useful quirk involves the template variable: When you use a macro from another file, no matter how you got it, whether from a load expression or the component architecture, while that macro is executing, template still refers to the file that called it! This is yet another way of overriding bits and pieces of macros if macros are looked up from the template variable; of course it does introduce fairly tight coupling between the files.

Suppose we have page.tmpl.pt for ordinary (non blog-post) pages:

<html>
  <header metal:define-macro="header">
      <h1>${context/title}</h1>
      <div class="metadata"
           metal:use-macro="template/macros/metadata">
        Put the metadata here
      </div>
  </header>
  <div metal:define-macro="metadata">
    <!--! Pages don't have any metadata -->
  </div>
  <body>
      <article>
        <header metal:use-macro="template/macros/header">
        </header>
        ...
      </article>
  </body>
</html>

Now, post.tmpl.pt could use the header macro from page.tmpl.pt and still fill in its own metadata macro:

<html>
  <div metal:define-macro="metadata">
      <h1>Author: ${context/author}</h1>
  </div>
  <body>
      <article>
        <header metal:use-macro="context/@@page.tmpl/index/macros/header">
        </header>
        ...
      </article>
  </body>
</html>

This is similar to filling slots, but works with any level of nesting.

Macro Limitations and Challenges

  • Macros always expand to exactly one piece of content (content for a macro cannot come from multiple places).

  • Slots can be difficult to use effectively through multiple levels of nesting.

  • The macro namespace is flat.

Some of these challenges are addressed with viewlets.

Using Viewlets

Viewlets are a mechanism for creating pluggable themes.

They are broadly similar to macros, in that they allow the contents of a particular part of a template to be defined elsewhere. Moreover, they are also registered based on the context, request and view, allowing specific viewlets to be used for specific types of objects. There are some important differences, though:

  • Multiple viewlets can create content for one spot. (If a only a single viewlet is defined, it’s functionally very much like a macro with no slots.)

  • They must be registered in ZCML. There is currently no support for automatically registering viewlets from the filesystem.

  • They have a hierarchy in naming: a template references a viewlet manager, and the viewlets are attached to the viewlet manager.

They have an extra level of flexibility because they can be written partly or entirely in Python. However, this document will only focus on how they can be used with template files.

Viewlet Managers and the provider: Expression

We’ll jump right in with an example from the base-chameleon theme. First, here is a (simplified) template called generic_post_list.pt:

 <html xmlns:tal="http://xml.zope.org/namespaces/tal"
       xmlns:metal="http://xml.zope.org/namespaces/metal">
    ...
    <body>
     <article tal:replace="structure provider:post_list_article" />
    </body>
 </html>

The provider: expression is new here. That will look up and invoke a content provider.

Viewlet Managers

In the context of nti.nikola_chameleon, that’s almost always going to be a viewlet manager. A viewlet manager is responsible for finding all the applicable viewlets attached to it, finding out which ones are available at the given time, sorting them, and then rendering them. This package provides one default viewlet manager, nti.nikola_chameleon.view.ConditionalViewletManager that does all of those steps. It sorts viewlets based on their weight attribute, and will disable viewlets that have their available attribute (if one exists) set to a false value.

So that’s the first step in using viewlets (after deciding where in the template viewlets might be useful, of course): define your viewlet manager. This is done in ZCML:

<configure xmlns="http://namespaces.zope.org/zope"
          xmlns:i18n="http://namespaces.zope.org/i18n"
          xmlns:zcml="http://namespaces.zope.org/zcml"
          xmlns:z3c="http://namespaces.zope.org/z3c"
          xmlns:browser="http://namespaces.zope.org/browser" >

 <include package="zope.viewlet" file="meta.zcml" />
 <include package="nti.nikola_chameleon" file="meta.zcml" />

 <browser:newViewletManager id="IPostListArticle" />
 <browser:viewletManager
     name="post_list_article"
     provides=".viewlets.IPostListArticle"
     class=".view.ConditionalViewletManager"
     permission="zope.Public"
     />
 </configure>

The line <browser:newViewletManager id="IPostListArticle" /> creates a new kind of viewlet manager. This is important because, as you’ll see, individual viewlets are attached to the kind of the viewlet manager (not its name). In advanced cases, this allows for a hierarchy of viewlet managers to be defined, but in most cases each individual viewlet manager should use its own type.

Note

There are a handful of pre-defined kinds of viewlet managers in nti.nikola_chameleon.interfaces: IHtmlHeadViewletManager, IHtmlBodyContentViewletManager, IHtmlBodyContentHeaderViewletManager, IHtmlBodyContentFooterViewletManager and IHtmlBodyFooterViewletManager. These are very generic and in complex situations it would be easy to accidentally register too much on any given kind of viewlet manager, so only use them with caution. They can work well for simple themes or when well documented, though.

The next line, beginning with <browser:viewletManager actually creates and registers a viewlet manager. We give it a name (post_list_article) by which a provider: expression can find it, we set it up to be the kind of viewlet manager we just defined (provides=".viewlets.IPostListArticle"), and we tell it what sort of class will implement the viewlet functionality.

Note

Using class=".view.ConditionalViewletManager" is always recommended.

Note

Setting permission="zope.Public" is unfortunately required at this time. In the future, a directive might be created that doesn’t require this.

Note

The viewletManager directive does allow the for, layer and view attributes to register the viewlet manager for a particular context, request and view, respectively. Usually registering individual viewlets for particular contexts, requests and views themselves is enough and we can leave these attributes out. However, in advanced cases, where you want to use a completely different kind of viewlet manager (IPostListArticle in this case) of the same name (post_list_article) this may be helpful. It can also be helpful to disable an entire viewlet manager when extending a theme.

Viewlet Manager Templates

Not seen here is the template argument. This allows you to provide a template into which the viewlets attached to the manager will be rendered. The template is passed the attached viewlets in its viewlets option. If you do not specify a template, viewlets default to being rendered one after the other, something like this:

<tal:block xmlns:tal="http://xml.zope.org/namespaces/tal"
           tal:repeat="viewlet options/viewlets">
    <tal:block tal:replace="structure viewlet" />
</tal:block>

Viewlets

Now that we have a viewlet manager, we can add individual viewlets to it. For our purposes, a viewlet is a template that gets run to fill in a piece of content for its viewlet manager. Recall that viewlets are associated with viewlet manager via the viewlet manager’s kind, or interface. Again, this is done in ZCML:

<configure xmlns="http://namespaces.zope.org/zope"
          xmlns:i18n="http://namespaces.zope.org/i18n"
          xmlns:zcml="http://namespaces.zope.org/zcml"
          xmlns:z3c="http://namespaces.zope.org/z3c"
          xmlns:browser="http://namespaces.zope.org/browser" >

 <include package="zope.viewlet" file="meta.zcml" />

 <browser:viewlet
     name="default_article"
     manager=".viewlets.IPostListArticle"
     template="v_generic_article_post_list.pt"
     layer=".interfaces.ITagPageKind"
     permission="zope.Public"
     article_class="tagpage"
     />

 <browser:viewlet
     name="default_article"
     manager=".viewlets.IPostListArticle"
     template="v_generic_article_post_list.pt"
     layer=".interfaces.IAuthorPageKind"
     permission="zope.Public"
     article_class="authorpage"
     />
</configure>

Here we have defined two viewlets and attached them to the IPostListArticle viewlet manager with their manager attribute. Each of them has specified a template to run and a name (as well as the boilerplate permission attribute). They have been specified for different request layers with the layer attribute. Because the layers are orthogonal (a request provides only one page kind), at most only one of these two viewlets will be found for any given request. (We could also specify view and context attributes to further narrow it down, if necessary.)

Passing Parameters

Notice that they both use the same template argument. And what’s that article_class attribute for? Let’s take a look at that template file, v_generic_article_post_list.pt:

 <article class="${view/article_class}"
        xmlns:tal="http://xml.zope.org/namespaces/tal"
            xmlns:metal="http://xml.zope.org/namespaces/metal">
   <header>
       <tal:block replace="structure provider:content_header" />
   </header>

   <ul metal:use-macro="macro:html_posts_postlist">
       <li>A post</li>
   </ul>
 </article>

It turns out that all the extra attributes you specify in ZCML get turned into attributes on the view object when the viewlet runs. So we are able to “parameterize” the template in this way. It turns out the only thing we want to do differently when we are listing posts for a tag and posts for an author is use a different CSS class name (class="${view/article_class}").

There is a subtlety here: The view object while the viewlet template renders is not the same as the view object when the template was called.

Caution

Because ICommentKind is currently based on the view, access to the type of comment system in use is lost when rendering a viewlet.

Overwriting, Extending, and Multiple Viewlets

We’ve seen an example where only one viewlet at a time is expected to match. What if we may actually want more than one viewlet to render? For example, we may have some standard styles that we always want to put into the HTML <head> (while allowing themes that extend us to modify or replace those), while also allowing for additional styles to be added when rendering particular types of pages (again, context/request/view discriminators).

This is where the name attribute becomes useful.

  • A more specific registration with the same name will replace the less specific registration.

  • Registrations with a different name will extend the set of applicable viewlets.

  • Equally specific registrations with the same name aren’t allowed, but an extending theme can replace them.

Let’s look at an example. Suppose our main template defines the <head> to use a content provider:

<html>
  <head>
     ...
    <!--! The extensible viewlet manager; anyone can add things based
         on view, context and request/layer to it. -->
    <tal:block content="structure provider:extra_head" />
  </head>
</html>

We can then define this viewlet manager and add some specific viewlets to it in ZCML:

<configure>
  ...
  <!-- Extra head -->
  <!-- The normal extra head for a page is called 'default_extra_head' -->
  <browser:viewletManager
    name="extra_head"
    provides=".interfaces.IHtmlHeadViewletManager"
    class="zope.viewlet.manager.WeightOrderedViewletManager"
    permission="zope.Public"
    />

  <!-- posts, pages, stories and books all have the same extra header -->
  <browser:viewlet
    name="default_extra_head"
    manager=".interfaces.IHtmlHeadViewletManager"
    template="v_post_extra_head.pt"
    permission="zope.Public"
    layer=".interfaces.IPostPageKind"
    weight="0"
    />
  <!-- books get extra -->
  <browser:viewlet
    name="book_extra_head"
    manager=".interfaces.IHtmlHeadViewletManager"
    template="v_book_extra_head.pt"
    permission="zope.Public"
    layer=".interfaces.IBookPageKind"
    weight="1"
    />
   ...
</configure>

We register a viewlet called default_extra_head for each page kind. (For posts, pages and stories we rely on the fact that all the specific page kinds extend the generic IPostPageKind interface to only write that down once.) We then use a new name to define the extra style that books get when we render IBookPageKind; if we had used the same name again, we would have hidden the default head.

This is also an example of sorting viewlets: we specify the weight attribute to be able to ensure what order the styles are added in.

Supplied Viewlets

This package supplies one viewlet for producing links to feeds in HTML bodies. See the documentation for nti.nikola_chameleon.feeds.HTMLFeedLinkViewlet for more information.

Theme Inheritance

nti.nikola_chameleon support themes that extend themes. In fact, with a well designed and documented base theme, nti.nikola_chameleon makes it easy to customize as much or as little as you’d like.

Extending a theme is just like creating a theme. The only difference is that you need to specify the parent theme that you’re extending by name. For example, here’s the theme meta file for the bootstrap3-chamaleon theme, which extends the base-chameleon theme:

[Theme]
engine = nti.nikola_chameleon
author = NextThought
author_url =
license = Apache2
based_on = Bootstrap 3 <https://getbootstrap.com/>
tags = bootstrap
parent = base-chameleon

[Family]
family = bootstrap3

[Nikola]
bootswatch = True

From that point on, you can override and customize the necessary pieces of the theme. Specifically how you do that will depend on the theme you’re extending.

Replacing Files

If you have a *.tmpl.pt with the same name as one from the parent theme, your file will replace that file when it is used as a template name by Nikola and when it is used as a view name for the purposes of using macros. (Make sure you define the necessary macros!) This is a little bit blunt, so we don’t usually recommend replacing entire files (or using file views to find macro names for that matter).

Replacing Macros

Any macros you define with the same name (and specificity) as macros defined by the parent theme will replace the parent theme macros. For example, any macros defined in *.macro.pt files in your theme will override such macros defined in *.macro.pt files in the parent theme (because macros defined that way all have the same, very low, specificity).

You can override a macro of a given name for only some kinds of objects with an appropriate definition in your theme.zcml.

Extending and Replacing Viewlets

Adding to or replacing viewlets defined in the parent theme is the same as doing so in a single theme: just pay attention to the names and the specificity of your viewlet declarations.

ZCML Execution

The theme.zcml files are executed from the base theme down to the most derived theme. The execution context is the same for each execution, so base themes can provide features that child themes can test for.

Path Helpers

nti.nikola_chameleon provides some helpers for use in path expressions. These are implemented either as path adapters, which use a namespace:name syntax, or as views, which use @@view_name syntax. Both of these methods leverage the component architecture.

This document will mention each path helper provided, and where appropriate, direct you to more information.

Post Helpers

These helpers are applied to any IPost object.

Formatted Date

The post object has a formatted_date method that produces a string representing a date, and which requires one parameter, the name of the date format. There is a standard format called webiso, and the user-configured date format is at options/date_format.

The formatted_date path adapter lets us call this function easily in a path expression.

Type

Python Expression

Path Expression

Constant

python:post.formatted_date('webiso')

post/formatted_date:webiso

Variable

python:post.formatted_date(options['date_format'])

post/formatted_date:?date_format

Meta

Posts have a method called meta that returns metadata. It takes a string argument naming what metadata to return. Although these are almost always used in templates in a static way, it is possible that the metadata might be a variable.

The meta path adapter lets us call this function easily in a path expression.

Type

Python Expression

Path Expression

Constant

python:post.meta('type')

post/meta:type

Variable

python:post.meta(options['meta'])

post/meta:?meta

Post Text

The text method on a post can take an optional parameter telling it to truncate to a teaser length. This is configurable by the user for index pages, but repeatedly accessing the configuration is tedious. The post_text view provides helpers to automatically take this into account for indexes. It also helps when embedding posts into other pages.

Typical example:

<tal:block tal:repeat="post context/posts">
  <article tal:define="post_text post/@@post_text"
           class="h-entry post-${post/meta:type}
                  ${post_text/preview}">
      <div class="p-${post_text/content_kind}
                  entry-${post_text/content_kind}"
           tal:content="structure:post_text/content">
          The content of the post.

          This may be just a teaser.
      </div>
  </article>
</tal:block>

Generic Views

These view objects are registered as default views for any object. You can override that registration for something more specific if needed.

Feeds

The feeds view provides helpers for producing RSS and Atom links in the header of pages. See the methods defined there for more information.

Typical usage:

<tal:block xmlns:tal="http://xml.zope.org/namespaces/tal"
           tal:define="kind options/kind|nothing;
                       feeds context/@@feeds">
  ${structure:python:feeds.feed_translations_head(kind=kind , feeds=False)}
</tal:block>
CSS

The post_css has helpers to produce CSS, typically based on the kind of page being rendered.

Typical usage:

<article metal:fill-slot="content"
             tal:define="post context"
             class="post-${context/meta:type} h-entry hentry
                            ${context/@@post_css/pagekind_class}"
             itemscope="itemscope" itemtype="http://schema.org/Article">
Macros

See also

Using Macros

The macros view provides an alternative to the macro: expression type. It is used when you wish to look up a macro having a different context than the current context. The object you traverse through to reach the @@macros view becomes the context used to find and execute the macro. This is most helpful when dealing with a list of posts.

Typical usage:

<div tal:repeat="post context/posts">
  ...
  <p metal:use-macro="post/@@macros/comment_link"
     class="commentline" />
</div>

API Reference

Caution

Unless otherwise explicitly documented, the objects referenced here are documented solely for exploration purposes and do not feature stable APIs for public use.

Interfaces

Interfaces for rendering Nikola with chameleon.

The most important interface in this package for themes is IPageKind and its various subclasses. These function similarly to IBrowserLayer in a Zope 3 application, in that they are applied to the request variable when a template is rendered, and more specific templates or macros can be registered for the request based on that layer. Each request will only have one such interface applied to it.

Similarly, the ICommentKind (with its subclasses ICommentKindNone and ICommentKindAllowed and its various system types markers) will be applied to the view variable.

Templating

Templating support for Chameleon under Nikola.

Adapters

Adapters for various object types.

Feeds

The Chameleon ZPT plugin for nikola.

Macros

Supporting code for z3c.macro.

Request

Request objects.

View

View objects.

These objects will typically provide chunks of functionality that are ugly to write and/or test in templates. They will be registered in ZCML against specific (context, request) pairs (as specific as needed). They (and their attributes, methods and templates) can then be easily accessed in a template by traversal: context/@@view_name/method.

Plugin

The Chameleon ZPT plugin for nikola.

ZCML

ZCML directives.

New in version 0.0.1a2.

Changes

1.0.0 (2018-05-26)

  • Posts that have a true value for has_math will now implement the IMathJaxPost interface when used as the context.

  • Index pages that have any posts using MathJax will now have a context object that implements IMathJaxPostList.

  • Add a view for getting CSS data. Currently it has context/@@post_css/pagekind_class.

  • Update to Nikola 8; drop support for Python 2.7.

  • Add interfaces.IRootPage which is added to pages for which the metadata field nti-extra-page-kind is set to root.

  • Make the featured list available to all pages, not just index pages. Together with the IRootPage this can be used to promote blog posts to the root index.html.

  • Add embedded_content to the @@post_text view.

  • Initial support for template-based shortcodes. See https://github.com/NextThought/nti.nikola_chameleon/issues/5

0.0.1a2 (2017-10-14)

  • Map the Nikola messages function onto the native i18n functionality of Chamleon. Attributes like i18n:translate are now preferred to explicit calls to options/messages when possible.

  • Add support for viewlets. Several default viewlet managers are supplied, and a ZCML directive <browser:newViewletManager> is provided so themes can create new viewlet managers:

    <browser:newViewletManager id="ILeftColumn" />
    <browser:viewletManager
        name="left_column"
        provides=".viewlets.ILeftColumn" />
    
  • Add a path adapter to easily get formatted dates from a post, either a static format (post/formatted_date:webiso) or dynamically from a variable (post/formatted_date:?date_format).

  • Add a view to get the text of a post, respecting teaser settings: post/@@post_text/content.

  • Move feed support to a @@feeds view for headers, and a viewlet for body:

    <browser:viewlet
       name="feed_content_header"
       manager=".interfaces.IHtmlBodyContentHeaderViewletManager"
       class=".feeds.HTMLFeedLinkViewlet"
       layer=".interfaces.IAuthorPageKind"
       permission="zope.Public"
       weight="1"
       classification_name="author"
       />
    
  • Add a view interface (ICommentKind) for comment systems. Only Disqus is currently supported. Note that this may move in the future to be a layer.

0.0.1a1 (2017-10-09)

  • Preliminary PyPI release. While this package is functional, it is not yet documented sufficiently to be of general use. It is also not expected to be fully stable.

Development

nti.nikola_chameleon is hosted at GitHub:

Project URLs

Indices and tables