Registration ============ In the course of this narrative we will demonstrate different usage scenarios. The example test setup contains the following files:: ./skins/index.pt ./skins/main_template.pt ./skins/images/logo.png ./skins/about/index.pt ./skins/about/images/logo.png ↳ mount point .. -> output >>> import os >>> from pyramid_skins import tests >>> for filename in output.split('\n'): ... if filename.lstrip().startswith('.'): ... assert os.lstat( ... os.path.join(os.path.dirname(tests.__file__), filename.strip())) \ ... is not None To explain this setup, imagine that the ``index.pt`` template represents some page in the site (e.g. the *front page*); it uses ``main_template.pt`` as the :term:`o-wrap` template. The ``about`` directory represents some editorial about-section where ``about/index.pt`` is the index page. This section provides its own logo. We begin by registering the directory. This makes the files listed above available as skin components. The ZCML-directive ``skins`` makes registration easy: .. code-block:: xml .. -> configuration .. invisible-code-block: python xmlconfig(""" %(configuration)s """.strip() % locals()) from zope.component import getUtility from pyramid_skins.interfaces import ISkinObject getUtility(ISkinObject, name="index") The ``path`` parameter indicates a relative path which defines the mount point for the skin registration. Components ########## At this point the skin objects are available as utility components. This is the low-level interface:: from zope.component import getUtility from pyramid_skins.interfaces import ISkinObject index = getUtility(ISkinObject, name="index") .. -> code >>> exec(code) >>> assert index is not None The component name is available in the ``name`` attribute:: index.name .. -> expr >>> eval(expr) 'index' We now move up one layer and consider the skin components as objects. Objects ####### The ``SkinObject`` class itself wraps the low-level utility lookup:: from pyramid_skins import SkinObject FrontPage = SkinObject("index") .. -> code >>> exec(code) >>> FrontPage.__get__() is not None True This object is a callable which will render the template to a response (it could be an image, stylesheet or some other resource type). In the case of templates, the first two positional arguments (if given) are mapped to ``context`` and ``request``. These symbols are available for use in the template. :: response = FrontPage(u"Hello world!") .. -> code The index template simply inserts the ``context`` value into the body tag of the HTML document:: Hello world! .. -> output >>> exec(code) >>> response.body.replace('\n\n', '\n') == output True >>> response.content_type == 'text/html' True >>> response.charset == 'UTF-8' True The exact same approach works for the logo object:: from pyramid_skins import SkinObject logo = SkinObject("images/logo.png") .. -> code Calling the ``logo`` object returns an HTTP response:: 200 OK .. -> output >>> exec(code) >>> response = logo() >>> response.status == output.strip('\n') True >>> response.content_type == 'image/png' True >>> response.content_length == 2833 True >>> response.charset == None True >>> exec(code) >>> response.headers['content-type'] 'image/png' Request-specific skins ###################### Instead of global utility skin components, we can provide a request type: .. code-block:: xml .. -> configuration .. invisible-code-block: python _ = xmlconfig(""" %(configuration)s """.strip() % locals()) The skin component is now registered as a named adapter on the request: >>> from pyramid.testing import DummyRequest >>> request = DummyRequest() We use the ``getAdapter`` call: >>> from zope.component import getAdapter >>> getAdapter(request, ISkinObject, name="index") Views ##### The call method signature for skin templates is ``(context, request)``. This is the same as views. That is, we can use skin template objects directly as view callables:: .. -> config1 In Pyramid we can also define a view using a class which provides ``__init__`` and ``__call__``. The call method must return a response. With skin objects, we can express it this way:: class FrontPageView(object): __call__ = SkinObject("index") def __init__(self, context, request): self.context = context self.request = request .. -> code >>> exec(code) When the ``__call__`` attribute is accessed (as a descriptor), a view callable is returned, bound to the view's instance dictionary (which in this case has the symbols ``context`` and ``request``)::
Search

${context}

Note that methods are not bound. .. -> config2 .. we run these two view configurations. >>> from pyramid_skins import tests >>> tests.FrontPage = FrontPage >>> tests.FrontPageView = FrontPageView >>> _ = xmlconfig(""" ... ... ... ... %(config1)s ... %(config2)s ... """.strip() % locals()) While the two patterns are equivalent, using a view allows you to prepare data for the template. Both yield the exact same output when passed ``'Hello world!'`` as the view context:: Hello world! .. -> output >>> from pyramid.view import render_view >>> from pyramid.testing import DummyRequest >>> frontpage1 = render_view('Hello world!', DummyRequest(), name="frontpage1") >>> frontpage2 = render_view('Hello world!', DummyRequest(), name="frontpage2") >>> frontpage1.replace('\n\n', '\n') == frontpage2.replace('\n\n', '\n') == output True Renderer ######## The package comes with a renderer factory for skin objects. It looks up a skin object based on view name. .. automodule:: pyramid_skins.renderer .. autofunction:: renderer_factory In your application setup, use the ``add_renderer`` method:: config.add_renderer('skin', renderer_factory) Example view:: @view_config(name='index', renderer='skin') def index(request): return {'title': 'My application'} Discovery ######### In some scenarios, it's useful to be able to discover skin objects at run-time. An example is when you use skins to publish editorial content which is added to the file system. The ``discovery`` parameter takes a boolean argument, e.g. ``True``: .. code-block:: xml .. -> configuration Let's add a new skin template with the source: .. code-block:: xml
Hello world!
.. -> source .. invisible-code-block: python import os import imp import sys import tempfile f = tempfile.NamedTemporaryFile(suffix=".py") try: path, suffix = os.path.splitext(f.name) module = os.path.basename(path) imp.load_module(module, open(f.name), path, (suffix, "r", imp.PY_SOURCE)) finally: f.close() # make skins directory dir = os.path.join(os.path.dirname(path), "skins") if not os.path.exists(dir): os.mkdir(dir) g = None try: # register skin directory _ = xmlconfig(""" %(configuration)s """.strip() % locals()) # Wait a while for the discoverer to start up import time time.sleep(0.5) # add new file for discovery g = tempfile.NamedTemporaryFile(dir=dir, suffix=".pt") try: g.write(source) g.flush() # sleep for a short while to discover the new file import time time.sleep(0.5) name = os.path.splitext(os.path.basename(g.name))[0] # verify existence from zope.component import queryUtility from pyramid_skins.interfaces import ISkinObject template = queryUtility(ISkinObject, name=name) assert template is not None, "Template does not exist: " + name if template: output = template() finally: g.close() finally: os.removedirs(dir) >>> print output 200 OK Content-Length: 24 Content-Type: text/html; charset=UTF-8
Hello world!
Compatibility ------------- - Mac OS X 10.5+ (requires the ``MacFSEvents`` library) - Linux 2.6.13+ with Libc >= 2.4 (requires ``pyinotify`` library) Imperative configuration ======================== If you prefer imperative configuration over declarative you can use the ``pyramid_skins.configuration.register_path`` method for configuration: .. automodule:: pyramid_skins.configuration .. autofunction:: register_path Example:: from pyramid.config import Configurator config = Configurator() from pyramid_skins.configuration import register_path register_path(config, path)