"""Render functions and helpers, including legacy Buffet implementation Render functions and helpers ============================ :mod:`pylons.templating` includes several basic render functions, :func:`render_mako`, :func:`render_genshi` and :func:`render_jinja2` that render templates from the file-system with the assumption that variables intended for the will be attached to :data:`tmpl_context` (hereafter referred to by its short name of :data:`c` which it is commonly imported as). The default render functions work with the template language loader object that is setup on the :data:`app_globals` object in the project's :file:`config/environment.py`. Usage ----- Generally, one of the render functions will be imported in the controller. Variables intended for the template are attached to the :data:`c` object. The render functions return unicode (they actually return :class:`~webhelpers.html.literal` objects, a subclass of unicode). .. admonition :: Tip :data:`tmpl_context` (template context) is abbreviated to :data:`c` instead of its full name since it will likely be used extensively and it's much faster to use :data:`c`. Of course, for users that can't tolerate one-letter variables, feel free to not import :data:`tmpl_context` as :data:`c` since both names are available in templates as well. Example of rendering a template with some variables:: from pylons import tmpl_context as c from pylons.templating import render_mako as render from sampleproject.lib.base import BaseController class SampleController(BaseController): def index(self): c.first_name = "Joe" c.last_name = "Smith" return render('/some/template.mako') And the accompanying Mako template: .. code-block:: mako Hello ${c.first name}, I see your lastname is ${c.last_name}! Your controller will have additional default imports for commonly used functions. Template Globals ---------------- Templates rendered in Pylons should include the default Pylons globals as the :func:`render_mako`, :func:`render_genshi` and :func:`render_jinja2` functions. The full list of Pylons globals that are included in the template's namespace are: - :term:`c` -- Template context object - :term:`tmpl_context` -- Template context object - :data:`config` -- Pylons :class:`~pylons.configuration.PylonsConfig` object (acts as a dict) - :term:`g` -- Project application globals object - :term:`app_globals` -- Project application globals object - :term:`h` -- Project helpers module reference - :data:`request` -- Pylons :class:`~pylons.controllers.util.Request` object for this request - :data:`response` -- Pylons :class:`~pylons.controllers.util.Response` object for this request - :class:`session` -- Pylons session object (unless Sessions are removed) - :class:`url ` -- Routes url generator object - :class:`translator` -- Gettext translator object configured for current locale - :func:`ungettext` -- Unicode capable version of gettext's ngettext function (handles plural translations) - :func:`_` -- Unicode capable gettext translate function - :func:`N_` -- gettext no-op function to mark a string for translation, but doesn't actually translate Configuring the template language --------------------------------- The template engine is created in the projects ``config/environment.py`` and attached to the ``app_globals`` (g) instance. Configuration options can be directly passed into the template engine, and are used by the render functions. .. warning:: Don't change the variable name on :data:`app_globals` that the template loader is attached to if you want to use the render_* functions that :mod:`pylons.templating` comes with. The render_* functions look for the template loader to render the template. Legacy Buffet templating plugin and render functions ==================================================== The Buffet object is styled after the original Buffet module that implements template language neutral rendering for CherryPy. This version of Buffet also contains caching functionality that utilizes `Beaker middleware `_ to provide template language neutral caching functionality. A customized version of `BuffetMyghty `_ is included that provides a template API hook as the ``pylonsmyghty`` engine. This version of BuffetMyghty disregards some of the TurboGears API spec so that traditional Myghty template names can be used with ``/`` and file extensions. The render functions are intended as the primary user-visible rendering commands and hook into Buffet to make rendering content easy. """ import logging import os import warnings try: from cStringIO import StringIO except ImportError: from StringIO import StringIO import pkg_resources from webhelpers.html import literal import pylons import pylons.legacy __all__ = ['Buffet', 'MyghtyTemplatePlugin', 'render', 'render_genshi', 'render_jinja2', 'render_mako', 'render_response'] PYLONS_VARS = ['c', 'config', 'g', 'h', 'render', 'request', 'session', 'translator', 'ungettext', '_', 'N_'] log = logging.getLogger(__name__) def pylons_globals(): """Create and return a dictionary of global Pylons variables Render functions should call this to retrieve a list of global Pylons variables that should be included in the global template namespace if possible. Pylons variables that are returned in the dictionary: ``c``, ``g``, ``h``, ``_``, ``N_``, config, request, response, translator, ungettext, ``url`` If SessionMiddleware is being used, ``session`` will also be available in the template namespace. """ conf = pylons.config._current_obj() c = pylons.tmpl_context._current_obj() g = conf.get('pylons.app_globals') or conf['pylons.g'] pylons_vars = dict( c=c, tmpl_context=c, config=conf, app_globals=g, g=g, h=conf.get('pylons.h') or pylons.h._current_obj(), request=pylons.request._current_obj(), response=pylons.response._current_obj(), url=pylons.url._current_obj(), translator=pylons.translator._current_obj(), ungettext=pylons.i18n.ungettext, _=pylons.i18n._, N_=pylons.i18n.N_ ) # If the session was overriden to be None, don't populate the session # var econf = pylons.config['pylons.environ_config'] if 'beaker.session' in pylons.request.environ or \ ('session' in econf and econf['session'] in pylons.request.environ): pylons_vars['session'] = pylons.session._current_obj() log.debug("Created render namespace with pylons vars: %s", pylons_vars) return pylons_vars def cached_template(template_name, render_func, ns_options=(), cache_key=None, cache_type=None, cache_expire=None, **kwargs): """Cache and render a template Cache a template to the namespace ``template_name``, along with a specific key if provided. Basic Options ``template_name`` Name of the template, which is used as the template namespace. ``render_func`` Function used to generate the template should it no longer be valid or doesn't exist in the cache. ``ns_options`` Tuple of strings, that should correspond to keys likely to be in the ``kwargs`` that should be used to construct the namespace used for the cache. For example, if the template language supports the 'fragment' option, the namespace should include it so that the cached copy for a template is not the same as the fragment version of it. Caching options (uses Beaker caching middleware) ``cache_key`` Key to cache this copy of the template under. ``cache_type`` Valid options are ``dbm``, ``file``, ``memory``, ``database``, or ``memcached``. ``cache_expire`` Time in seconds to cache this template with this ``cache_key`` for. Or use 'never' to designate that the cache should never expire. The minimum key required to trigger caching is ``cache_expire='never'`` which will cache the template forever seconds with no key. """ # If one of them is not None then the user did set something if cache_key is not None or cache_expire is not None or cache_type \ is not None: if not cache_type: cache_type = 'dbm' if not cache_key: cache_key = 'default' if cache_expire == 'never': cache_expire = None namespace = template_name for name in ns_options: namespace += str(kwargs.get(name)) cache = pylons.cache.get_cache(namespace, type=cache_type) content = cache.get_value(cache_key, createfunc=render_func, expiretime=cache_expire) return content else: return render_func() def render_mako(template_name, extra_vars=None, cache_key=None, cache_type=None, cache_expire=None): """Render a template with Mako Accepts the cache options ``cache_key``, ``cache_type``, and ``cache_expire``. """ # Create a render callable for the cache function def render_template(): # Pull in extra vars if needed globs = extra_vars or {} # Second, get the globals globs.update(pylons_globals()) # Grab a template reference template = globs['app_globals'].mako_lookup.get_template(template_name) return literal(template.render_unicode(**globs)) return cached_template(template_name, render_template, cache_key=cache_key, cache_type=cache_type, cache_expire=cache_expire) def render_mako_def(template_name, def_name, cache_key=None, cache_type=None, cache_expire=None, **kwargs): """Render a def block within a Mako template Takes the template name, and the name of the def within it to call. If the def takes arguments, they should be passed in as keyword arguments. Example:: # To call the def 'header' within the 'layout.mako' template # with a title argument render_mako_def('layout.mako', 'header', title='Testing') Also accepts the cache options ``cache_key``, ``cache_type``, and ``cache_expire``. """ # Create a render callable for the cache function def render_template(): # Pull in extra vars if needed globs = kwargs or {} # Second, get the globals globs.update(pylons_globals()) # Grab a template reference template = globs['app_globals'].mako_lookup.get_template( template_name).get_def(def_name) return literal(template.render_unicode(**globs)) return cached_template(template_name, render_template, cache_key=cache_key, cache_type=cache_type, cache_expire=cache_expire) def render_genshi(template_name, extra_vars=None, cache_key=None, cache_type=None, cache_expire=None, method='xhtml'): """Render a template with Genshi Accepts the cache options ``cache_key``, ``cache_type``, and ``cache_expire`` in addition to method which are passed to Genshi's render function. """ # Create a render callable for the cache function def render_template(): # Pull in extra vars if needed globs = extra_vars or {} # Second, get the globals globs.update(pylons_globals()) # Grab a template reference template = globs['app_globals'].genshi_loader.load(template_name) return literal(template.generate(**globs).render(method=method, encoding=None)) return cached_template(template_name, render_template, cache_key=cache_key, cache_type=cache_type, cache_expire=cache_expire, ns_options=('method'), method=method) def render_jinja2(template_name, extra_vars=None, cache_key=None, cache_type=None, cache_expire=None): """Render a template with Jinja2 Accepts the cache options ``cache_key``, ``cache_type``, and ``cache_expire``. """ # Create a render callable for the cache function def render_template(): # Pull in extra vars if needed globs = extra_vars or {} # Second, get the globals globs.update(pylons_globals()) # Grab a template reference template = \ globs['app_globals'].jinja2_env.get_template(template_name) return literal(template.render(**globs)) return cached_template(template_name, render_template, cache_key=cache_key, cache_type=cache_type, cache_expire=cache_expire) class BuffetError(Exception): """Buffet Exception""" pass class Buffet(object): """Buffet style plug-in template rendering Buffet implements template language plug-in support modeled highly on the `Buffet Project `_ from which this class inherits its name. """ def __init__(self, default_engine=None, template_root=None, default_options=None, **config): """Initialize the Buffet renderer, and optionally set a default engine/options""" if default_options is None: default_options = {} self.default_engine = default_engine self.template_root = template_root self.default_options = default_options self.engines = {} log.debug("Initialized Buffet object") if self.default_engine: self.prepare(default_engine, template_root, **config) def prepare(self, engine_name, template_root=None, alias=None, **config): """Prepare a template engine for use This method must be run before the `render <#render>`_ method is called so that the ``template_root`` and options can be set. Template engines can also be aliased if you wish to use multiplate configurations of the same template engines, or prefer a shorter name when rendering a template with the engine of your choice. """ Engine = available_engines.get(engine_name, None) if not Engine: raise TemplateEngineMissing('Please install a plugin for ' '"%s" to use its functionality' % engine_name) engine_name = alias or engine_name extra_vars_func = config.pop(engine_name + '.extra_vars_func', None) self.engines[engine_name] = \ dict(engine=Engine(extra_vars_func=extra_vars_func, options=config), root=template_root) log.debug("Adding %s template language for use with Buffet", engine_name) def render(self, engine_name=None, template_name=None, include_pylons_variables=True, namespace=None, cache_key=None, cache_expire=None, cache_type=None, **options): """Render a template using a template engine plug-in To use templates it is expected that you will attach data to be used in the template to the ``c`` variable which is available in the controller and the template. When porting code from other projects it is sometimes easier to use an exisitng dictionary which can be specified with ``namespace``. ``engine_name`` The name of the template engine to use, which must be 'prepared' first. ``template_name`` Name of the template to render ``include_pylons_variables`` If a custom namespace is specified this determines whether Pylons variables are included in the namespace or not. Defaults to ``True``. ``namespace`` A custom dictionary of names and values to be substituted in the template. Caching options (uses Beaker caching middleware) ``cache_key`` Key to cache this copy of the template under. ``cache_type`` Valid options are ``dbm``, ``file``, ``memory``, or ``ext:memcached``. ``cache_expire`` Time in seconds to cache this template with this ``cache_key`` for. Or use 'never' to designate that the cache should never expire. The minimum key required to trigger caching is ``cache_expire='never'`` which will cache the template forever seconds with no key. All other keyword options are passed directly to the template engine used. """ if not engine_name and self.default_engine: engine_name = self.default_engine engine_config = self.engines.get(engine_name) if not engine_config: raise Exception("No engine with that name configured: %s" % \ engine_name) full_path = template_name if engine_name == 'pylonsmyghty': if namespace is None: namespace = {} # Reserved myghty keywords for key in ('output_encoding', 'encoding_errors', 'disable_unicode'): if key in namespace: options[key] = namespace.pop(key) if include_pylons_variables: namespace['_global_args'] = pylons_globals() else: namespace['_global_args'] = {} # If they passed in a variable thats listed in the global_args, # update the global args one instead of duplicating it interp = engine_config['engine'].interpreter for key in interp.global_args.keys() + \ interp.init_params.get('allow_globals', []): if key in namespace: namespace['_global_args'][key] = namespace.pop(key) else: if namespace is None: if not include_pylons_variables: raise BuffetError('You must specify ``namespace`` when ' '``include_pylons_variables`` is False') else: namespace = pylons_globals() elif include_pylons_variables: globs = pylons_globals() globs.update(namespace) namespace = globs if not full_path.startswith(os.path.sep) and not \ engine_name.startswith('pylons') and not \ engine_name.startswith('mako') and \ engine_config['root'] is not None: full_path = os.path.join(engine_config['root'], template_name) full_path = full_path.replace(os.path.sep, '.').lstrip('.') # Don't pass format into the template engine if it's None if 'format' in options and options['format'] is None: del options['format'] # If one of them is not None then the user did set something if cache_key is not None or cache_expire is not None or cache_type \ is not None: if not cache_type: cache_type = 'dbm' if not cache_key: cache_key = 'default' if cache_expire == 'never': cache_expire = None def content(): log.debug("Cached render running for %s", full_path) return engine_config['engine'].render(namespace, template=full_path, **options) tfile = full_path if options.get('fragment', False): tfile += '_frag' if options.get('format', False): tfile += options['format'] log.debug("Using render cache for %s", full_path) mycache = pylons.cache.get_cache(tfile) content = mycache.get_value(cache_key, createfunc=content, type=cache_type, expiretime=cache_expire) return content log.debug("Rendering template %s with engine %s", full_path, engine_name) return engine_config['engine'].render(namespace, template=full_path, **options) class TemplateEngineMissing(Exception): """Exception to toss when an engine is missing""" pass class MyghtyTemplatePlugin(object): """Myghty Template Plugin This Myghty Template Plugin varies from the official BuffetMyghty in that it will properly populate all the default Myghty variables needed and render fragments. """ extension = "myt" def __init__(self, extra_vars_func=None, options=None): """Initialize Myghty template engine""" if options is None: options = {} myt_opts = {} for k, v in options.iteritems(): if k.startswith('myghty.'): myt_opts[k[7:]] = v import myghty.interp self.extra_vars = extra_vars_func self.interpreter = myghty.interp.Interpreter(**myt_opts) def load_template(self, template_path): """Unused method for TG plug-in API compatibility""" pass def render(self, info, format="html", fragment=False, template=None, output_encoding=None, encoding_errors=None, disable_unicode=None): """Render the template indicated with info as the namespace and globals from the ``info['_global_args']`` key.""" buf = StringIO() global_args = info.pop('_global_args') if self.extra_vars: global_args.update(self.extra_vars()) optional_args = {} if fragment: optional_args['disable_wrapping'] = True if output_encoding: optional_args['output_encoding'] = output_encoding if encoding_errors: optional_args['encoding_errors'] = encoding_errors if disable_unicode: optional_args['disable_unicode'] = disable_unicode self.interpreter.execute(template, request_args=info, global_args=global_args, out_buffer=buf, **optional_args) return buf.getvalue() available_engines = {} for entry_point in \ pkg_resources.iter_entry_points('python.templating.engines'): try: Engine = entry_point.load() available_engines[entry_point.name] = Engine except: import sys from pkg_resources import DistributionNotFound # Warn when there's a problem loading a Buffet plugin unless it's # pylonsmyghty reporting there's no Myghty installed if not isinstance(sys.exc_info()[1], DistributionNotFound) or \ entry_point.name != 'pylonsmyghty': import traceback tb = StringIO() traceback.print_exc(file=tb) warnings.warn("Unable to load template engine entry point: '%s': " "%s" % (entry_point, tb.getvalue()), RuntimeWarning, 2) def render(*args, **kargs): """Render a template and return it as a string (possibly Unicode) Optionally takes 3 keyword arguments to use caching supplied by Buffet. Examples: .. code-block:: python content = render('/my/template.mako') print content content = render('/my/template2.myt', fragment=True) .. admonition:: Note Not all template languages support the concept of a fragment. In those template languages that do support the fragment option, this usually implies that the template will be rendered without extending or inheriting any site skin. """ fragment = kargs.pop('fragment', False) format = kargs.pop('format', None) args = list(args) template = args.pop() cache_args = dict(cache_expire=kargs.pop('cache_expire', None), cache_type=kargs.pop('cache_type', None), cache_key=kargs.pop('cache_key', None)) log.debug("Render called with %s args and %s keyword args", args, kargs) if args: engine = args.pop() return pylons.buffet.render(engine, template, fragment=fragment, format=format, namespace=kargs, **cache_args) return pylons.buffet.render(template_name=template, fragment=fragment, format=format, namespace=kargs, **cache_args) def render_response(*args, **kargs): """Returns the rendered response within a Response object See ``render`` for information on rendering. Example:: def view(self): return render_response('/my/template.mako') """ warnings.warn(pylons.legacy.render_response_warning, DeprecationWarning, 2) response = pylons.response._current_obj() content = render(*args, **kargs) if isinstance(content, unicode): response.unicode_body = content else: response.content = content output_encoding = kargs.get('output_encoding') encoding_errors = kargs.get('encoding_errors') if output_encoding: response.headers['Content-Type'] = '%s; charset=%s' % \ (pylons.response.default_content_type, output_encoding) if encoding_errors: response.encoding_errors = encoding_errors return '' render_response.__doc__ = 'Deprecated: %s.\n\n%s' % \ (pylons.legacy.render_response_warning, render_response.__doc__)