1try:
2    from urllib import quote_plus
3except ImportError: #pragma: no cover
4    from urllib.parse import quote_plus
5
6from tg.support.converters import asbool
7from markupsafe import Markup
8
9import tg
10from tg import predicates
11from tg.util import Bunch
12
13
14class MissingRendererError(Exception):
15    def __init__(self, template_engine):
16        Exception.__init__(self,
17            ("The renderer for '%(template_engine)s' templates is missing. "
18            "Try adding the following line in you app_cfg.py:\n"
19            "\"base_config.renderers.append('%(template_engine)s')\"") % dict(
20            template_engine=template_engine))
21        self.template_engine = template_engine
22
23
24def _get_tg_vars():
25    """Create a Bunch of variables that should be available in all templates.
26
27    These variables are:
28
29    WARNING: This function should not be called from outside of the render()
30    code.  Please consider this function as private.
31
32    quote_plus
33        the urllib quote_plus function
34    url
35        the turbogears.url function for creating flexible URLs
36    identity
37        the current visitor's identity information
38    session
39        the current beaker.session if the session_filter.on it set
40        in the app.cfg configuration file. If it is not set then session
41        will be None.
42    locale
43        the default locale
44    inputs
45        input values from a form
46    errors
47        validation errors
48    request
49        the WebOb Request Object
50    config
51        the app's config object
52    auth_stack_enabled
53        A boolean that determines if the auth stack is present in the environment
54    predicates
55        The :mod:`tg.predicates` module.
56
57    """
58
59    tgl = tg.request_local.context._current_obj()
60    req = tgl.request
61    conf = tgl.config
62    tmpl_context = tgl.tmpl_context
63    app_globals = tgl.app_globals
64    translator = tgl.translator
65    response = tgl.response
66    session = tgl.session
67    helpers = conf['helpers']
68
69    try:
70        validation = req.validation
71    except AttributeError:
72        validation = {}
73
74    # TODO: Implement user_agent and other missing features.
75    tg_vars = Bunch(
76        config=tg.config,
77        flash_obj=tg.flash,
78        quote_plus=quote_plus,
79        url=tg.url,
80        # this will be None if no identity
81        identity = req.environ.get('repoze.who.identity'),
82        session = session,
83        locale = req.plain_languages,
84        errors = validation and validation.errors,
85        inputs = validation and validation.values,
86        request = req,
87        auth_stack_enabled = 'repoze.who.plugins' in req.environ,
88        predicates = predicates)
89
90    root_vars = Bunch(
91        c=tmpl_context,
92        tmpl_context=tmpl_context,
93        response=response,
94        request=req,
95        config=conf,
96        app_globals=app_globals,
97        g=app_globals,
98        session=session,
99        url=tg.url,
100        helpers=helpers,
101        h=helpers,
102        tg=tg_vars,
103        translator=translator,
104        ungettext=tg.i18n.ungettext,
105        _=tg.i18n.ugettext,
106        N_=tg.i18n.gettext_noop)
107
108    # If there is an identity, push it to the Pylons template context
109    tmpl_context.identity = tg_vars['identity']
110
111    # Allow users to provide a callable that defines extra vars to be
112    # added to the template namespace
113    variable_provider = conf.get('variable_provider', None)
114    if variable_provider:
115        root_vars.update(variable_provider())
116    return root_vars
117
118#Monkey patch pylons_globals for cases when pylons.templating is used
119#instead of tg.render to programmatically render templates.
120try: #pragma: no cover
121    import pylons
122    import pylons.templating
123    pylons.templating.pylons_globals = _get_tg_vars
124except ImportError:
125    pass
126# end monkeying around
127
128
129def render(template_vars, template_engine=None, template_name=None, **kwargs):
130    """Renders a specific template in current TurboGears context.
131
132    Permits to manually render any template like TurboGears would for
133    expositions. It also guarantees that the ``before_render_call`` and
134    ``after_render_call`` hooks are called in the process.
135
136    :param dict template_vars: This is the dictonary of variables that should
137                               become available to the template. Template
138                               vars can also include the ``tg_cache`` dictionary
139                               which enables template caching.
140    :param str template_engine: This is the template engine name, same as
141                                specified inside AppConfig.renderers.
142    :param str template_name: This is the template to render, can be specified
143                              both as a path or using dotted notation if available.
144
145    TurboGears injects some additional variables in the template context,
146    those include:
147
148        - tg.config -> like tg.config in controllers
149        - tg.flash_obj -> the flash object, call ``render`` on it to display it.
150        - tg.quote_plus -> function to perform percentage escaping (%xx)
151        - tg.url -> like tg.url in controllers
152        - tg.identity -> like tg.request.identity in controllers
153        - tg.session -> like tg.session in controllers
154        - tg.locale -> Languages of the current request
155        - tg.errors -> Validation errors
156        - tg.inputs -> Values submitted for validation
157        - tg.request -> like tg.request in controllers
158        - tg.auth_stack_enabled -> if authentication is enabled or not
159        - tg.predicates -> like tg.predicates in controllers
160
161        - tmpl_context -> like tg.tmpl_context in controllers
162        - response -> like tg.response in controllers
163        - request -> like tg.request in controllers
164        - config -> like tg.config in controllers
165        - app_globals -> like tg.app_globals in controllers
166        - session -> like tg.session in controllers
167        - url -> like tg.url in controllers
168        - h -> Your application helpers
169        - translator -> The current gettext translator
170        - _ -> like tg.i18n.ugettext
171
172    Additional variables can be added to every template by a
173    ``variable_provider`` function inside the application
174    configuration. This function is expected to return
175    a ``dict`` with any variable that should be added
176    the default template variables. It can even replace
177    existing variables.
178
179    """
180    config = tg.config._current_obj()
181
182    if template_engine is None:
183        template_engine = config['default_renderer']
184
185    render_function = config['render_functions'].get(template_engine)
186    if render_function is None:
187        # engine is not present in the engine list, warn developer
188        raise MissingRendererError(template_engine)
189
190    if not template_vars:
191        template_vars = {}
192
193    caching_options = template_vars.get('tg_cache', {})
194    kwargs['cache_key'] = caching_options.get('key')
195    kwargs['cache_expire'] = caching_options.get('expire')
196    kwargs['cache_type'] = caching_options.get('type')
197
198    tg.hooks.notify('before_render_call', (template_engine, template_name, template_vars, kwargs))
199
200    tg_vars = template_vars
201
202    engines_without_vars = config['rendering_engines_without_vars']
203    if template_engine not in engines_without_vars:
204        # Get the extra vars, and merge in the vars from the controller
205        tg_vars = _get_tg_vars()
206        tg_vars.update(template_vars)
207
208    kwargs['result'] = render_function(template_name, tg_vars, **kwargs)
209
210    tg.hooks.notify('after_render_call', (template_engine, template_name, template_vars, kwargs))
211    return kwargs['result']
212
213
214def cached_template(template_name, render_func, ns_options=(),
215                    cache_key=None, cache_type=None, cache_expire=None,
216                    **kwargs):
217    """Cache and render a template, took from Pylons
218
219    Cache a template to the namespace ``template_name``, along with a
220    specific key if provided.
221
222    Basic Options
223
224    ``template_name``
225        Name of the template, which is used as the template namespace.
226    ``render_func``
227        Function used to generate the template should it no longer be
228        valid or doesn't exist in the cache.
229    ``ns_options``
230        Tuple of strings, that should correspond to keys likely to be
231        in the ``kwargs`` that should be used to construct the
232        namespace used for the cache. For example, if the template
233        language supports the 'fragment' option, the namespace should
234        include it so that the cached copy for a template is not the
235        same as the fragment version of it.
236
237    Caching options (uses Beaker caching middleware)
238
239    ``cache_key``
240        Key to cache this copy of the template under.
241    ``cache_type``
242        Valid options are ``dbm``, ``file``, ``memory``, ``database``,
243        or ``memcached``.
244    ``cache_expire``
245        Time in seconds to cache this template with this ``cache_key``
246        for. Or use 'never' to designate that the cache should never
247        expire.
248
249    The minimum key required to trigger caching is
250    ``cache_expire='never'`` which will cache the template forever
251    seconds with no key.
252
253    """
254    # If one of them is not None then the user did set something
255    if cache_key is not None or cache_type is not None or cache_expire is not None:
256        get_cache_kw = {}
257        if cache_type is not None:
258            get_cache_kw['type'] = cache_type
259
260        if not cache_key:
261            cache_key = 'default'
262        if cache_expire == 'never':
263            cache_expire = None
264
265        namespace = template_name
266        for name in ns_options:
267            namespace += str(kwargs.get(name))
268
269        cache = tg.cache.get_cache(namespace, **get_cache_kw)
270        content = cache.get_value(cache_key, createfunc=render_func,
271            expiretime=cache_expire)
272        return content
273    else:
274        return render_func()
275
276