1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
|
"""This module integrates the Kajiki_ templating language into the
Pyramid_ web framework.
To enable it, add this to your Pyramid web app configuration::
config.include("kajiki.integration.pyramid")
Also add something like the following to the
application section of your Pyramid application's .ini file::
[app:yourapp]
# ... other stuff ...
pyramid.reload_templates = True
kajiki.extensions = .kajiki .genshi
# The Kajiki output mode can be "html5", "html" or "xml"
kajiki.mode = html5
Then configure your views just like the other Pyramid templating
languages, passing an asset specification to the ``renderer`` argument::
@view_config(route_name='faq', renderer='app:templates/faq-page.kajiki')
* TODO: Support text templates, too.
* TODO: i18n
.. _`Kajiki`: http://pypi.python.org/pypi/Kajiki/
.. _`Pyramid`: http://docs.pylonshq.com/
"""
from os import stat
from paste.deploy.converters import asbool
from pyramid.interfaces import IRenderer, ITemplateRenderer
from pyramid.resource import abspath_from_resource_spec
from zope.interface import implementer
from kajiki import XMLTemplate
from kajiki.loader import Loader
def includeme(config):
"""Sets up the Kajiki templating language for the configured
file extensions.
"""
if hasattr(config.registry, "kajiki_loader"):
return # Include only once per config
settings = config.get_settings()
extensions = settings.get("kajiki.extensions", ".kajiki").split()
for extension in extensions:
config.add_renderer(extension, renderer_factory)
config.registry.kajiki_loader = PyramidKajikiLoader(
auto_reload=asbool(settings.get("pyramid.reload_templates")),
mode=settings.get("kajiki.mode", "html5"),
)
@implementer(ITemplateRenderer)
@implementer(IRenderer)
class PyramidKajikiLoader(Loader):
"""Yet another template loader; this one specializes in resource specs."""
def implementation(self): # ITemplateRenderer implementation
return self
def __init__(self, auto_reload=False, mode="html5"): # noqa: FBT002
self.auto_reload = auto_reload
self.mode = mode
self._timestamps = {}
super().__init__()
def _load(self, name, **kw):
"""Called when the template actually needs to be (re)compiled."""
return XMLTemplate(source=None, filename=name, mode=self.mode, **kw)
def import_(self, name, **kw):
"""Overrides Loader.import_().
* Resolves the resource spec into an absolute path for the template.
* Checks the template modification time to decide whether to reload it.
"""
name = abspath_from_resource_spec(name)
if self.auto_reload and name in self.modules:
mtime = stat(name).st_mtime
if mtime > self._timestamps.get(name, 0):
del self.modules[name]
return super().import_(name, **kw)
def __call__(self, value, system, is_fragment=False): # noqa: FBT002
"""IRenderer implementation.
``value`` is the result of the view.
Returns a result (a string or unicode object useful as
a response body). Values computed by
the system are passed by the system in the ``system``
parameter, which is a dictionary. Keys in the dictionary
include: ``view`` (the view callable that returned the value),
``renderer_name`` (the template name or simple name of the
renderer), ``context`` (the context object passed to the
view), and ``request`` (the request object passed to the
view).
"""
name = system.get("renderer_name") or system["renderer_info"].name
template = self.import_(name, is_fragment=is_fragment)
try:
system.update(value)
except (TypeError, ValueError) as e:
msg = "The Kajiki template renderer was passed a non-dictionary as value."
raise ValueError(msg) from e
return template(system).render()
def fragment(self, renderer_name, dic, view=None, request=None):
"""Example usage from a class-based view:
html_fragment = request.registry.kajiki_loader.fragment(
'myapp:templates/fragment_template.kajiki',
a_dictionary_used_as_template_context,
view=self)
"""
return self(
is_fragment=True,
value=dic,
system={
"renderer_name": renderer_name,
"request": request or view.request,
"view": view,
"context": dic,
},
)
def renderer_factory(info):
"""*info* contains::
name = Attribute('The value passed by the user as the renderer name')
package = Attribute('The "current package" when the renderer '
'configuration statement was found')
type = Attribute('The renderer type name')
registry = Attribute('The "current" application registry when the '
'renderer was created')
settings = Attribute('The ISettings dictionary related to the '
'current app')
"""
return info.registry.kajiki_loader
|