nti.nikola_chameleon¶
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:
the page templates language as implemented by Chameleon
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
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.
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 |
|
|
Variable |
|
|
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 |
|
|
Variable |
|
|
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.
See also
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
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.1 (unreleased)¶
Nothing changed yet.
1.0.0 (2018-05-26)¶
Posts that have a true value for
has_math
will now implement theIMathJaxPost
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 fieldnti-extra-page-kind
is set toroot
.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 nativei18n
functionality of Chamleon. Attributes likei18n:translate
are now preferred to explicit calls tooptions/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¶
https://pypi.python.org/pypi/nti.nikola_chameleon (PyPI entry and downloads)