Package turbogears :: Package view :: Module base

Source Code for Module turbogears.view.base

  1  """Template processing for TurboGears view layer. 
  2   
  3  The template engines are configured and loaded here and this module provides 
  4  the generic template rendering function "render", which selects the template 
  5  engine to use and the appropriate output format, headers and encoding based 
  6  on the given arguments and the application configuration. 
  7   
  8  Also defines the functions and variables that will be available in the 
  9  template scope and provides a hook for adding additional template variables. 
 10   
 11  """ 
 12   
 13  import sys 
 14  import re 
 15  import logging 
 16   
 17  from itertools import chain, imap 
 18  from itertools import cycle as icycle 
 19  from urllib import quote_plus 
 20   
 21  import cherrypy 
 22  import genshi 
 23  import pkg_resources 
 24   
 25  import turbogears 
 26  from turbogears import identity, config 
 27   
 28  # the gettext imported here can be whatever function returned 
 29  # by the i18n module depending on the user's configuration 
 30  # all the functions will implement the same interface so we don't care 
 31  from turbogears.i18n import get_locale, gettext 
 32   
 33  from turbogears.util import (Bunch, adapt_call, get_template_encoding_default, 
 34      get_mime_type_for_format, mime_type_has_charset) 
 35   
 36  try: 
 37      from turbogears.i18n.kidutils import i18n_filter as kid_i18n_filter 
 38  except ImportError: 
 39      kid_i18n_filter = None 
 40   
 41  try: 
 42      from genshi.filters import Translator 
43 - def _genshi_translator_cb(template):
44 """Callback to add a gettext Translator filter to Genshi templates. 45 46 Private module function not intended to for public use. 47 48 This is called by the Genshi template loader and adds Translator filter 49 as the first in the list of filters as is required for translation to 50 work properly. 51 52 """ 53 template.filters.insert(0, Translator(gettext))
54 from genshisupport import TGGenshiTemplatePlugin 55 except ImportError: 56 _genshi_translator_cb = None 57 TGGenshiTemplatePlugin = None 58 59 60 log = logging.getLogger("turbogears.view") 61 62 baseTemplates = [] 63 variable_providers = [] 64 root_variable_providers = [] 65 engines = dict() 66 67
68 -def _choose_engine(template):
69 """Return template engine for given template name. 70 71 Parses template name from the expose 'template' argument. If the @expose 72 decorator did not contain a template argument, we fetch the default engine 73 info from the configuration file. 74 75 @param template: a template string as seen in the expose decorator. 76 This can be something like "kid:myproj.templates.welcome" or just 77 "myproj.templates.welcome". 78 If a colon is found, then we try to get the engine name from the 79 template string. Else we try to search it from the default engine in 80 the configuration file via the `tg.defaultview` setting. 81 The template name may also be just the name of the template engine, 82 as in @expose("json"). 83 @type template: basestring or None 84 85 """ 86 if isinstance(template, basestring): 87 # if a template arg was given we try to find the engine declaration 88 # in it by 89 colon = template.find(":") 90 if colon > -1: 91 enginename = template[:colon] 92 template = template[colon+1:] 93 94 else: 95 engine = engines.get(template, None) 96 if engine: 97 return engine, None, template 98 enginename = config.get("tg.defaultview", "genshi") 99 100 else: 101 enginename = config.get("tg.defaultview", "genshi") 102 103 engine = engines.get(enginename, None) 104 105 if not engine: 106 raise KeyError, \ 107 "Template engine %s is not installed" % enginename 108 109 return engine, template, enginename
110 111
112 -def render(info, template=None, format=None, headers=None, fragment=False, 113 **options):
114 """Renders data in the desired format. 115 116 @param info: the data itself 117 @type info: dict 118 119 @param template: name of the template to use 120 @type template: string 121 122 @param format: "html", "xml", "text" or "json" 123 @type format: string 124 125 @param headers: for response headers, primarily the content type 126 @type headers: dict 127 128 @param fragment: passed through to tell the template if only a 129 fragment of a page is desired. This is a way to allow 130 xml template engines to generate non valid html/xml 131 because you warn them to not bother about it. 132 @type fragment: bool 133 134 All additional keyword arguments are passed as keyword args to the render 135 method of the template engine. 136 137 """ 138 environ = getattr(cherrypy.request, 'wsgi_environ', {}) 139 if environ.get('paste.testing', False): 140 cherrypy.request.wsgi_environ['paste.testing_variables']['raw'] = info 141 142 template = format == 'json' and 'json' or info.pop( 143 "tg_template", template) 144 145 if not info.has_key("tg_flash"): 146 if config.get("tg.empty_flash", True): 147 info["tg_flash"] = None 148 149 engine, template, enginename = _choose_engine(template) 150 151 if format: 152 if format == 'plain': 153 if enginename == 'genshi': 154 format = 'text' 155 156 elif format == 'text': 157 if enginename == 'kid': 158 format = 'plain' 159 160 else: 161 format = enginename == 'json' and 'json' or config.get( 162 "%s.outputformat" % enginename, 163 config.get("%s.default_format" % enginename, 'html')) 164 165 if isinstance(headers, dict): 166 # Determine the proper content type and charset for the response. 167 # We simply derive the content type from the format here 168 # and use the charset specified in the configuration setting. 169 # This could be improved by also examining the engine and the output. 170 content_type = headers.get('Content-Type') 171 if not content_type: 172 if format: 173 content_format = format 174 if isinstance(content_format, (tuple, list)): 175 content_format = content_format[0] 176 177 if isinstance(content_format, str): 178 content_format = content_format.split( 179 )[0].split('-' , 1)[0].lower() 180 181 else: 182 content_format = 'html' 183 184 else: 185 content_format = 'html' 186 187 content_type = get_mime_type_for_format(content_format) 188 189 if mime_type_has_charset( 190 content_type) and '; charset=' not in content_type: 191 charset = options.get('encoding', 192 get_template_encoding_default(enginename)) 193 194 if charset: 195 content_type += '; charset=' + charset 196 197 headers['Content-Type'] = content_type 198 199 args, kw = adapt_call(engine.render, args=[], kw=dict( 200 info=info, format=format, fragment=fragment, template=template, 201 **options), start=1) 202 203 return engine.render(**kw)
204 205
206 -def transform(info, template):
207 """Create ElementTree representation of the output.""" 208 engine, template, enginename = _choose_engine(template) 209 return engine.transform(info, template)
210 211
212 -def loadBaseTemplates():
213 """Load base templates for use by other templates. 214 215 By listing templates in turbogears.view.baseTemplates, 216 these templates will automatically be loaded so that 217 the "import" statement in a template will work. 218 219 """ 220 log.debug("Loading base templates") 221 for template in baseTemplates: 222 engine, template, enginename = _choose_engine(template) 223 if sys.modules.has_key(template): 224 del sys.modules[template] 225 engine.load_template(template)
226 227
228 -class cycle:
229 """Loops forever over an iterator. 230 231 Wraps the itertools.cycle method, but provides a way to get the current 232 value via the 'value' attribute. 233 234 """ 235 value = None 236
237 - def __init__(self, iterable):
238 self._cycle = icycle(iterable)
239
240 - def __str__(self):
241 return self.value.__str__()
242
243 - def __repr__(self):
244 return self.value.__repr__()
245
246 - def next(self):
247 self.value = self._cycle.next() 248 return self.value
249 250
251 -def selector(expression):
252 """If the expression is true, return the string 'selected'. 253 254 Useful for HTML <option>s. 255 256 """ 257 if expression: 258 return "selected" 259 else: 260 return None
261 262
263 -def checker(expression):
264 """If the expression is true, return the string "checked". 265 266 This is useful for checkbox inputs. 267 268 """ 269 if expression: 270 return "checked" 271 else: 272 return None
273 274
275 -def ipeek(iterable):
276 """Lets you look at the first item in an iterator. 277 278 This is a good way to verify that the iterator actually contains something. 279 This is useful for cases where you will choose not to display a list or 280 table if there is no data present. 281 282 """ 283 iterable = iter(iterable) 284 try: 285 item = iterable.next() 286 return chain([item], iterable) 287 except StopIteration: 288 return None
289 290
291 -class UserAgent:
292 """Representation of the user's browser. 293 294 Provides information about the type of browser, browser version, etc. 295 This currently contains only the information needed for work thus far 296 (msie, firefox, safari browser types, plus safari version info). 297 298 """ 299 300 _re_safari = re.compile(r"Safari/(\d+)") 301
302 - def __init__(self, useragent=None):
303 self.majorVersion = None 304 self.minorVersion = None 305 if not useragent: 306 useragent = "unknown" 307 if useragent.find("MSIE") > -1: 308 self.browser = "msie" 309 elif useragent.find("Firefox") > -1: 310 self.browser = "firefox" 311 else: 312 isSafari = self._re_safari.search(useragent) 313 if isSafari: 314 self.browser = "safari" 315 build = int(isSafari.group(1)) 316 # this comes from: 317 # http://developer.apple.com/internet/safari/uamatrix.html 318 if build >= 412: 319 self.majorVersion = "2" 320 self.minorVersion = "0" 321 elif build >= 312: 322 self.majorVersion = "1" 323 self.minorVersion = "3" 324 elif build >= 125: 325 self.majorVersion = "1" 326 self.minorVersion = "2" 327 elif build >= 85: 328 self.majorVersion = "1" 329 self.minorVersion = "0" 330 elif useragent == "unknown" or useragent is None: 331 self.browser = "unknown" 332 else: 333 self.browser = "unknown: %s" % useragent
334 335
336 -def genshi_et(element):
337 """If this is an ElementTree element, convert it to a markup stream. 338 339 If this is a list, apply this function recursively and chain everything. 340 341 """ 342 if hasattr(element, 'tag'): 343 return genshi.input.ET(element) 344 345 elif isinstance(element, list): 346 return chain(*imap(genshi_et, element)) 347 348 else: 349 return element
350 351
352 -def stdvars():
353 """Create a Bunch of variables that should be available in all templates. 354 355 These variables are: 356 357 checker 358 the checker function 359 config 360 the cherrypy config get function 361 cycle 362 cycle through a set of values 363 errors 364 validation errors 365 identity 366 the current visitor's identity information 367 inputs 368 input values from a form 369 ipeek 370 the ipeek function 371 locale 372 the default locale 373 quote_plus 374 the urllib quote_plus function 375 request 376 the cherrypy request 377 selector 378 the selector function 379 session 380 the current cherrypy.session if the session_filter.on it set 381 in the app.cfg configuration file. If it is not set then session 382 will be None. 383 tg_js 384 the url path to the JavaScript libraries 385 tg_static 386 the url path to the TurboGears static files 387 tg_toolbox 388 the url path to the TurboGears toolbox files 389 tg_version 390 the version number of the running TurboGears instance 391 url 392 the turbogears.url function for creating flexible URLs 393 useragent 394 a UserAgent object with information about the browser 395 396 Additionally, you can add a callable to turbogears.view.variable_providers 397 that can add more variables to this list. The callable will be called with 398 the vars Bunch after these standard variables have been set up. 399 400 """ 401 try: 402 useragent = cherrypy.request.headers['User-Agent'] 403 useragent = UserAgent(useragent) 404 except Exception: 405 useragent = UserAgent() 406 407 if config.get('session_filter.on', None): 408 session = cherrypy.session 409 else: 410 session = None 411 412 webpath = turbogears.startup.webpath or '' 413 tg_vars = Bunch( 414 checker = checker, 415 config = config.get, 416 cycle = cycle, 417 errors = getattr(cherrypy.request, 'validation_errors', {}), 418 identity = identity.current, 419 inputs = getattr(cherrypy.request, 'input_values', {}), 420 ipeek = ipeek, 421 locale = get_locale(), 422 quote_plus = quote_plus, 423 request = cherrypy.request, 424 selector = selector, 425 session = session, 426 tg_js = '/' + webpath + 'tg_js', 427 tg_static = '/' + webpath + 'tg_static', 428 tg_toolbox = '/' + webpath + 'tg_toolbox', 429 tg_version = turbogears.__version__, 430 url = turbogears.url, 431 useragent = useragent, 432 widgets = '/' + webpath + 'tg_widgets', 433 ) 434 for provider in variable_providers: 435 provider(tg_vars) 436 root_vars = dict() 437 root_vars['_'] = gettext 438 for provider in root_variable_providers: 439 provider(root_vars) 440 root_vars['tg'] = tg_vars 441 root_vars['ET'] = genshi_et 442 return root_vars
443 444
445 -def _get_plugin_options(plugin_name, defaults=None):
446 """Return all options from global config where the first part of the config 447 setting name matches the start of plugin_name. 448 449 Optionally, add default values from passed ``default`` dict where the first 450 part of the key (i.e. everything leading up to the first dot) matches the 451 start of ``plugin_name``. The defaults will be overwritten by the 452 corresponding config settings, if present. 453 454 """ 455 if defaults is not None: 456 options = dict((k, v) for k, v in defaults.items() 457 if plugin_name.startswith(k.split('.', 1)[0])) 458 else: 459 options = dict() 460 for k, v in config.config.configMap["global"].items(): 461 if plugin_name.startswith(k.split('.', 1)[0]): 462 options[k] = v 463 return options
464 465
466 -def load_engines():
467 """Load and initialize all templating engines. 468 469 This is called during startup after the configuration has been loaded. 470 You can call this earlier if you need the engines before startup; 471 the engines will then be reloaded with the custom configuration later. 472 473 """ 474 get = config.get 475 476 engine_defaults = { 477 "cheetah.importhooks": False, 478 "cheetah.precompiled": False, 479 "genshi.default_doctype": 480 dict(html='html-strict', xhtml='xhtml-strict', xml=None), 481 "genshi.default_encoding": "utf-8", 482 "genshi.lookup_errors": "strict", 483 "genshi.new_text_syntax": False, 484 "json.assume_encoding": "utf-8", 485 "json.check_circular": True, 486 "json.descent_bases": get("json.descent_bases", 487 get("turbojson.descent_bases", True)), 488 "json.encoding": "utf-8", 489 "json.ensure_ascii": False, 490 "json.sort_keys": False, 491 "kid.assume_encoding": "utf-8", 492 "kid.encoding": "utf-8", 493 "kid.precompiled": False, 494 "kid.sitetemplate": get("kid.sitetemplate", 495 get("tg.sitetemplate", "turbogears.view.templates.sitetemplate")), 496 "mako.directories": [''], 497 "mako.output_encoding": "utf-8" 498 } 499 500 # Check if the i18n filter is activated in configuration. 501 # If so we'll need to apply a translator to the Genshi filter list 502 # and/or add the Kid translation function. 503 if get("i18n.run_template_filter", False): 504 if _genshi_translator_cb: 505 callback = get("genshi.loader_callback", _genshi_translator_cb) 506 engine_defaults['genshi.loader_callback'] = callback 507 if kid_i18n_filter: 508 kid_filter = get("kid.i18n_filter", kid_i18n_filter) 509 engine_defaults["kid.i18n_filter"] = kid_filter 510 engine_defaults["kid.i18n.run_template_filter"] = True 511 512 for entrypoint in pkg_resources.iter_entry_points( 513 "python.templating.engines"): 514 engine = entrypoint.load() 515 plugin_name = entrypoint.name 516 engine_options = _get_plugin_options(plugin_name, engine_defaults) 517 log.debug("Using options for template engine '%s': %r", plugin_name, 518 engine_options) 519 # Replace genshi markup template engine with our own derrived class 520 # to support our extensions to the Buffet interface. 521 # This is only a temporary measure. We should try to push our extension 522 # upstream and then require a new Genshi version. 523 if plugin_name in ('genshi', 'genshi-markup') \ 524 and TGGenshiTemplatePlugin: 525 engines[plugin_name] = TGGenshiTemplatePlugin( 526 stdvars, engine_options) 527 else: 528 engines[plugin_name] = engine(stdvars, engine_options)
529