Skip to main content

Skin support for BFG.

Project description

Overview

This package provides a framework to make files in a directory structure available as skin components (the term originates from the CMF package which provides comparable functionality on Zope 2). There’s built-in integration with routes, views and components.

Skins make separation of library code and presentation easy, and come with a customization story. An example of usage within library code is a view class:

class FrontPage(object):
    __call__ = SkinObject("site/frontpage")

The site/frontpage object could appear in the file system as:

/.../skins/site/frontpage.pt

Where the skins directory is the registration mount point. Note that the file extension is excluded from the object name. This is a behavior of the skin template factory which is associated with the .pt extension). Custom factories for other file extensions can be registered (see application setup).

If another skin directory contains an object by the same name, the most recent registration takes priority, e.g.:

/.../custom-skins/site/frontpage.pt

An alternative spelling which makes it easy to use skin objects as views is:

front_page = SkinObject("site/frontpage")

The front_page is now a callable which takes context and request, similar to BFG views. Note that skin objects may be exposed as views automatically (see views for instructions). This makes it easy to publish some directory of files, be it static (e.g. images, stylesheets) or dynamic (e.g. templates).

About

The package is written and maintained by Malthe Borch and Stefan Eletzhofer. Available as-is under the BSD license.

To contribute or get support for this package, please visit the #repoze channel on freenode irc or write to the repoze-dev mailinglist.

Usage

The package allows configuration using the ZCML language. The skin directive is defined in the meta.zcml file:

<include package="repoze.bfg.skins" file="meta.zcml" />

To configure all the included components (recommended), include the package instead:

<include package="repoze.bfg.skins" />

Application setup

Once you’ve included the repoze.bfg.skins ZCML, you may use the ZCML-directive <bfg:skins> to register a directory with templates and make them available as template components, e.g.::

<bfg:skins path="skins" />

The path parameter indicates a relative path which contains the skin object files.

The skins directive makes available skin components for use in library code. The default factory (see Factories) simply uses the relative path as the component name; some factories may strip off the file extension (this is the case for the page template factory).

To expose the contents of a skin directory as views, we can insert a view registration directive into the skins directive:

<bfg:skins path="skins">
   <bfg:view />
</bfg:skins>

The view directive has no required attributes, but all the attributes which are applicable for the standalone directive [1] are available, except name which is defined by the component and view which is given by the skin object.

When wrapped inside skins, an option index is available to allow registering default index views (e.g. index.pt):

<bfg:skins path="skins">
   <bfg:view index="index.pt" />
</bfg:skins>

Routes integration

Every skin component corresponds to a relative path. We can configure a route to map a subpath to skin components for which a view is registered:

<route
   name="skins"
   path="/content/*subpath"
   factory="repoze.bfg.skins.RoutesTraverserFactory"
   />

This traverser will convert the subpath into a view name and let BFG render the view if possible.

View integration

Skin components are registered as named utilities. We can use the getUtility function to retrieve a skin component by name.

For convenience, the SkinObject class doubles as a descriptor which can be used as a class attribute; it uses a getUtility call when accessed:

class MyView(object):
    __call__ = SkinObject("document_view")

The weak binding to the skin object makes it easy to depend on skin components from library code without a hard dependency.

Factories

The skin objects are instances of the SkinObject base class. We may associate a custom factory for particular file extensions:

class MySkinObject(SkinObject):
    pass

We register the class as a named utility component:

<utility
   name=".my"
   component=".MySkinObject"
   provides="repoze.bfg.skins.interfaces.ISkinObjectFactory"
   />

Page template factory

Included with the package is a factory for Zope Page Templates (with the file extension “.pt”). The Chameleon rendering engine is used.

Page templates registered as skin objects will be called skin templates. Support is provided to locate other skin templates and include them as macros. This is made pluggable such that applications can add additional functionality.

This package provides a new expression skin: which will retrieve a skin object by name:

The skin object factory for page templates provide the macros attribute. The following snippet illustrates this:

<div
  tal:define="master skin: main_template"
  metal:use-macro="master.macros['main']"
  />

The route: expression maps to the route_url framework function:

<img tal:attributes=”src string:${route: skins}/images/logo.png” />

Automatic discovery and reload

When the global setting debug is set (to any non-trivial value), skin objects are discovered at run-time and files are automatically reloaded when changed.

Developer’s Guide

This interactive narrative is written as a doctest.

You can run the tests by issuing the following command at the command-line prompt:

$ python setup.py test

In the course of the narrative we will demonstrate different setups. We have prepared a directory with skin files:

./tests/skins
./tests/skins/index.pt
./tests/skins/about/index.pt
./tests/skins/images/logo.png

We will use the ZCML configuration language to register this structure for use as skins. The following configure function loads in a ZCML configuration string.

>>> from repoze.bfg.skins import tests as testing

The ZCML-directive skins takes a path argument; we’ll provide a path which points to a directory within the testing harness.

>>> import os
>>> path = os.path.join(testing.__path__[0], 'skins')
>>> testing.configure("""
... <configure xmlns="http://namespaces.repoze.org/bfg">
...    <include package="repoze.bfg.skins" />
...    <skins path="%s" />
... </configure>""" % path)

Skin objects as components

After this bit of configuration, our skin objects are available as components.

>>> from zope.component import getUtility
>>> from repoze.bfg.skins.interfaces import ISkinObject

The name of the component is the relative path.

>>> getUtility(ISkinObject, name="images/logo.png")
<repoze.bfg.skins.models.SkinObject name="images/logo.png" at ...>

The page template factory strips off the file extension. The file “index.pt” becomes a skin template with the name “index”.

>>> getUtility(ISkinObject, name="index")
<repoze.bfg.skins.models.SkinTemplate name="index" at ...>

The component name is available in the name attribute:

>>> getUtility(ISkinObject, name="index").name
u'index'

Descriptor usage

The SkinObject class works as a descriptor. This is useful to tie user interface classes with skin files with a weak binding.

>>> from repoze.bfg.skins import SkinObject

Use it as a class attribute.

>>> class MyClass(object):
...     index = SkinObject("index")
...     logo = SkinObject("images/logo.png")

The property works on the class itself, and on instances.

>>> MyClass.index
<repoze.bfg.skins.models.SkinTemplate name="index" at ...>
>>> MyClass.logo
<repoze.bfg.skins.models.SkinObject name="images/logo.png" at ...>

Or directly as a view function:

>>> index_view = SkinObject("index")

The view function takes context and request arguments, but we can get away with trivial arguments for now.

>>> print index_view(None, None).body
<html>
  <body>
    Hello, world!
  </body>
</html>

View registration

We can register views for skin objects by wrapping a view directive in the skins directive.

There are no required arguments to the view directive:

>>> testing.configure("""
... <configure xmlns="http://namespaces.repoze.org/bfg">
...    <include package="repoze.bfg.skins" />
...    <skins path="%s">
...      <view />
...    </skins>
... </configure>""" % path)

The BFG framework function render_view_to_response lets us look up views by name and retrieve a response object for a given context and request.

>>> context = testing.DummyContext()
>>> request = testing.DummyRequest("")
>>> from repoze.bfg.view import render_view_to_response

Note in the example how the directory separator character has been replaced by an underscore.

>>> response = render_view_to_response(
...    context, request, name="images_logo.png")
>>> response.status
'200 OK'
>>> response.content_type
'image/png'

The page template view first renders the template before returning the response.

>>> response = render_view_to_response(
...    context, request, name="index")
>>> response.status
'200 OK'
>>> response.content_type
'text/html'

The response body contains the rendered page template:

>>> print response.body
<html>
  <body>
    Hello, world!
  </body>
</html>

The view directive accepts a index option; optionally use it to specify an index filename for directories, e.g.

>>> testing.configure("""
... <configure xmlns="http://namespaces.repoze.org/bfg">
...    <include package="repoze.bfg.skins" />
...    <skins path="%s">
...      <view index="index.pt" />
...    </skins>
... </configure>""" % path)

This registers index views for each directory:

>>> response = render_view_to_response(
...    context, request, name="")
>>> response.status
'200 OK'
>>> response.content_type
'text/html'
>>> response = render_view_to_response(
...    context, request, name="about")
>>> response.status
'200 OK'
>>> response.content_type
'text/html'

Routes

Now that we have views registered, let’s configure a route.

>>> testing.configure("""
... <configure xmlns="http://namespaces.repoze.org/bfg">
...    <include package="repoze.bfg.skins" />
...    <route
...       name="test"
...       path="/*subpath"
...       factory="repoze.bfg.skins.RoutesTraverserFactory"
...       />
... </configure>""")

To try it out, we’ll configure a router to use the components we’ve set up.

>>> from repoze.bfg.router import Router
>>> from zope.component import getSiteManager
>>> router = Router(getSiteManager())

We need to set the root factory to the routes mapper.

>>> from zope.component import getUtility
>>> from repoze.bfg.interfaces import IRoutesMapper
>>> router.root_factory = getUtility(IRoutesMapper)

Our “test” route lets us pass in any valid skin path:

>>> testing.DummyRequest("/images/logo.png").get_response(router)
<Response at ... 200 OK>
>>> testing.DummyRequest("/index").get_response(router)
<Response at ... 200 OK>

Templates

Included with the package is functionality to support interaction between templates registered as skin components.

An expression type skin is available for page templates to look up other skin components by name.

>>> from chameleon.zpt.template import PageTemplate
>>> template = PageTemplate("""
... <html tal:define="master skin: index"
...       metal:use-macro="master.macros['main']">
...    <body metal:fill-slot="body">
...       <h1>Welcome</h1>
...       <img src="${route: test}/images/logo.png" />
...    </body>
... </html>""")
>>> print template.render(context=context, request=request)
<html>
  <body>
    <h1>Welcome</h1>
    <img src="http://localhost/images/logo.png" />
  </body>
</html>
>>> print MyClass.index.render()
<html>
  <body>
    Hello, world!
  </body>
</html>

Changelog

0.15 (2009-11-12)

  • Make name attribute public.

  • Normalize path (ZCML does this, but we might be used imperatively).

  • Raise runtime-error if view is attempted registered for unknown skin object (should never happen, but did because of an internal bug).

0.14 (2009-11-09)

  • Look up skin object on call if object has not been resolved.

  • Added index view registration option.

  • Use Chameleon egg.

  • Pin package versions for testing.

0.13 (2009-10-30)

  • Rewrite. Backwards-compatibility broken.

    Migration path:

    Skins registrartion directive renamed to <bfg:skins>.

    To register views for skin objects, the <bfg:view> directive should be used inside a <bfg:skins> declaration. See documentation.

    Previous users should consult documentation for more information.

  • Made compatible with repoze.bfg 1.1a4.

  • Disuse component.adapts (unuseable in any BFG app), to make compatible with repoze.bfg 1.1a6+.

0.12 (2009-02-12)

  • Added convenience method get_skin_template_view. [malthe]

  • The get_skin_template method now accepts an optional request_type parameter, which takes priority in adaptation. [malthe]

  • The provides parameter has been retired; instead, a class parameter may be provided. By default this is set to the SkinTemplate class; to register a view, simply set it to SkinTemplateView (full module path required). [malthe]

0.11 (2009-02-09)

  • View permission is now only registered if a view must be provided. [malthe]

  • Multiple interfaces may be specified as provides. [malthe]

0.10 (2009-01-28)

  • Added parameter content_type which will set the content type of the view response. [malthe]

  • Added macros attribute to the template object. [malthe]

0.9 (2008-12-05)

  • Updated signatures for skin template factory lookup functions. [malthe]

  • Added support for skin api methods. [malthe]

0.8 (2008-12-05)

  • Provide ISkinMacro unless provides is set; however, always provide ISkinTemplate. Meanwhile, the macro accessor looks only for skin templates registered for the ISkinMacro interface. [malthe]

0.7 (2008-12-04)

  • If provides is set, do not automatically provide the ISkinTemplate interface as well; this behavior made it difficult to program cascading rendering schemes. [malthe]

  • Keyword-arguments are now accepted by the utility methods for rendering skin templates using Python. [malthe]

  • Added security assertions to macro rendering function to prevent infinite loop if a template tries to render itself. [malthe]

0.6 (2008-12-03)

  • Do not register macro components separately, but make them available from the macro attribute of a skin template. [malthe]

0.5 (2008-12-03)

  • Added component lookup scheme for the bound skin template object which makes skin API components available using get_<name> where <name> is the component name. [malthe]

  • Restructured package and changed look up scheme for skin APIs and macros. A symbol template is now available to skin templates; from this object, methods get_api and get_macro can be used to look up skin APIs and macros, respectively. [malthe]

  • Added render_skin_template_to_response and render_skin_template methods for general template rendering. [fairwinds]

0.4 (2008-11-13)

  • Added name attribute to skin template interface. [malthe]

  • No longer provide repoze.bfg.interfaces.IView by default; the provides attribute may now be used to specify an additional interface which the skin templates will provide. [malthe]

0.3 (2008-10-29)

  • Fix performance issue where template objects would be instantiated at every call. [malthe]

  • Pass keyword arguments to skin template callable. [malthe]

  • Instantiate page template directly. [malthe]

0.2 (2008-10-03)

  • Templates located in subdirectories are now named by replacing the operating system path separator with a forward slash symbol (often this will be the same character); before a dot ‘.’ was used. [malthe]

  • Added Template API base class. [malthe]

  • Renamed IApi to ITemplateAPI. [malthe]

  • Template API components should adapt (context, request, template), where template is the skin template object (such an API might need to provide access to the template file itself, in order to get a path to resources local to the template). [malthe]

  • Added render method to skin template class to allow rendering to a string instead of to a WebOb response. [malthe]

  • Renamed package to repoze.bfg.skins [seletz]

  • Added logic to allow registering and acquiring template API components from templates. [malthe]

  • Changed the Skin Template View to be a class, and added a minimal interface ISkinTemplate to access the template path [seletz]

  • Fixed a bug where we did not tear down the tests correctly [seletz]

  • Fixed bug where the INewRequest event handler would call templates when checking for their existence [seletz]

0.1 (2008-09-25)

  • Initial release [malthe]

  • Added support to dynamically register templates if they are added to a registered template directory [seletz]

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

repoze.bfg.skins-0.15.tar.gz (21.7 kB view hashes)

Uploaded source

Supported by

AWS AWS Cloud computing Datadog Datadog Monitoring Facebook / Instagram Facebook / Instagram PSF Sponsor Fastly Fastly CDN Google Google Object Storage and Download Analytics Huawei Huawei PSF Sponsor Microsoft Microsoft PSF Sponsor NVIDIA NVIDIA PSF Sponsor Pingdom Pingdom Monitoring Salesforce Salesforce PSF Sponsor Sentry Sentry Error logging StatusPage StatusPage Status page