1 """Classes and methods for TurboGears controllers."""
2
3 import logging
4 import re
5 import urllib
6 import urlparse
7 import types
8 from itertools import izip
9 from dispatch import generic, strategy, functions
10 from inspect import isclass
11 import cherrypy
12 from cherrypy import request, response
13 import turbogears.util as tg_util
14 from turbogears import view, database, errorhandling, config
15 from turbogears.decorator import weak_signature_decorator
16 from turbogears.validators import Invalid
17 from turbogears.errorhandling import error_handler, exception_handler
18
19
20 log = logging.getLogger("turbogears.controllers")
21
22
23 if config.get("session_filter.on", None):
24 if config.get("session_filter.storage_type", None) == "PostgreSQL":
25 import psycopg2
26 config.update(
27 {'session_filter.get_db': psycopg2.connect(
28 psycopg2.get('sessions.postgres.dsn'))
29 })
30
31
32
33 -def _process_output(output, template, format, content_type,
34 mapping, fragment=False):
35 """Produce final output form from data returned from a controller method.
36
37 See the expose() arguments for more info since they are the same.
38
39 """
40 if isinstance(output, dict):
41 from turbogears.widgets import js_location
42
43 css = tg_util.setlike()
44 js = dict(izip(js_location, iter(tg_util.setlike, None)))
45 include_widgets = {}
46 include_widgets_lst = config.get("tg.include_widgets", [])
47
48 if config.get("tg.mochikit_all", False):
49 include_widgets_lst.insert(0, 'turbogears.mochikit')
50
51 for i in include_widgets_lst:
52 widget = tg_util.load_class(i)
53 if isclass(widget):
54 widget = widget()
55 include_widgets["tg_%s" % i.split(".")[-1]] = widget
56 for script in widget.retrieve_javascript():
57 if hasattr(script, "location"):
58 js[script.location].add(script)
59 else:
60 js[js_location.head].add(script)
61 css.add_all(widget.retrieve_css())
62
63 for value in output.itervalues():
64 if hasattr(value, "retrieve_css"):
65 retrieve = getattr(value, "retrieve_css")
66 if callable(retrieve):
67 css.add_all(value.retrieve_css())
68 if hasattr(value, "retrieve_javascript"):
69 retrieve = getattr(value, "retrieve_javascript")
70 if callable(retrieve):
71 for script in value.retrieve_javascript():
72 if hasattr(script, "location"):
73 js[script.location].add(script)
74 else:
75 js[js_location.head].add(script)
76 output.update(include_widgets)
77 output["tg_css"] = css
78 for location in iter(js_location):
79 output["tg_js_%s" % str(location)] = js[location]
80
81 tg_flash = _get_flash()
82 if tg_flash:
83 output["tg_flash"] = tg_flash
84
85 headers = {'Content-Type': content_type}
86 output = view.render(output, template=template, format=format,
87 mapping=mapping, headers=headers,
88 fragment=fragment)
89 content_type = headers['Content-Type']
90
91 if content_type:
92 response.headers["Content-Type"] = content_type
93 else:
94 content_type = response.headers.get("Content-Type", 'text/plain')
95
96 if content_type.startswith("text/"):
97 if isinstance(output, unicode):
98 output = output.encode(tg_util.get_template_encoding_default())
99
100 return output
101
102
105
106
110 """Validate input.
111
112 @param form: a form instance that must be passed throught the validation
113 process... you must give a the same form instance as the one that will
114 be used to post data on the controller you are putting the validate
115 decorator on.
116 @type form: a form instance
117
118 @param validators: individual validators to use for parameters.
119 If you use a schema for validation then the schema instance must
120 be the sole argument.
121 If you use simple validators, then you must pass a dictionary with
122 each value name to validate as a key of the dictionary and the validator
123 instance (eg: tg.validators.Int() for integer) as the value.
124 @type validators: dictionary or schema instance
125
126 @param failsafe_schema: a schema for handling failsafe values.
127 The default is 'none', but you can also use 'values', 'map_errors',
128 or 'defaults' to map erroneous inputs to values, corresponding exceptions
129 or method defaults.
130 @type failsafe_schema: errorhandling.FailsafeSchema
131
132 @param failsafe_values: replacements for erroneous inputs. You can either
133 define replacements for every parameter, or a single replacement value
134 for all parameters. This is only used when failsafe_schema is 'values'.
135 @type failsafe_values: a dictionary or a single value
136
137 @param state_factory: If this is None, the initial state for validation
138 is set to None, otherwise this must be a callable that returns the initial
139 state to be used for validation.
140 @type state_factory: callable or None
141
142 """
143 def entangle(func):
144 if callable(form) and not hasattr(form, "validate"):
145 init_form = lambda self: form(self)
146 else:
147 init_form = lambda self: form
148
149 def validate(func, *args, **kw):
150
151 if hasattr(request, 'validation_state'):
152 return func(*args, **kw)
153
154 form = init_form(args and args[0] or kw["self"])
155 args, kw = tg_util.to_kw(func, args, kw)
156
157 errors = {}
158 if state_factory is not None:
159 state = state_factory()
160 else:
161 state = None
162
163 if form:
164 value = kw.copy()
165 try:
166 kw.update(form.validate(value, state))
167 except Invalid, e:
168 errors = e.unpack_errors()
169 request.validation_exception = e
170 request.validated_form = form
171
172 if validators:
173 if isinstance(validators, dict):
174 for field, validator in validators.iteritems():
175 try:
176 kw[field] = validator.to_python(
177 kw.get(field, None), state)
178 except Invalid, error:
179 errors[field] = error
180 else:
181 try:
182 value = kw.copy()
183 kw.update(validators.to_python(value, state))
184 except Invalid, e:
185 errors = e.unpack_errors()
186 request.validation_exception = e
187 request.validation_errors = errors
188 request.input_values = kw.copy()
189 request.validation_state = state
190
191 if errors:
192 kw = errorhandling.dispatch_failsafe(failsafe_schema,
193 failsafe_values, errors, func, kw)
194 args, kw = tg_util.from_kw(func, args, kw)
195 return errorhandling.run_with_errors(errors, func, *args, **kw)
196
197 return validate
198 return weak_signature_decorator(entangle)
199
200
202
204 strict = [strategy.ordered_signatures, strategy.safe_methods]
205 cases = strategy.separate_qualifiers(
206 cases,
207 primary = strict,
208 )
209 primary = strategy.method_chain(cases.get('primary', []))
210 if type(primary) != types.FunctionType:
211 for i in primary:
212 for y in i:
213 return y[1]
214 return primary
215
216
217 -def _add_rule(_expose, found_default, as_format, accept_format, template,
218 rulefunc):
219 if as_format == "default":
220 if found_default:
221 colon = template.find(":")
222 if colon == -1:
223 as_format = template
224 else:
225 as_format = template[:colon]
226 else:
227 found_default = True
228 ruleparts = ['kw.get("tg_format", "default") == "%s"' % as_format]
229 if accept_format:
230 ruleparts.append('(accept == "%s" and kw.get("tg_format"'
231 ', "default") == "default")' % accept_format)
232 rule = " or ".join(ruleparts)
233 log.debug("Generated rule %s", rule)
234 _expose.when(rule)(rulefunc)
235
236 return found_default
237
238
240 [generic(CustomDispatch)]
241 def _expose(func, accept, allow_json, *args, **kw):
242 pass
243
244 if func._allow_json:
245 log.debug("Adding allow_json rule: "
246 'allow_json and (kw.get("tg_format", None) == "json"'
247 ' or accept in ("application/json", "text/javascript"))')
248 _expose.when('allow_json and (kw.get("tg_format", None) == "json"'
249 ' or accept in ("application/json", "text/javascript"))')(
250 lambda _func, accept, allow_json, *args, **kw:
251 _execute_func(_func, "json", "json", "application/json",
252 None, False, args, kw))
253
254 found_default = False
255 for ruleinfo in func._ruleinfo:
256 found_default = _add_rule(_expose, found_default, **ruleinfo)
257
258 func._expose = _expose
259
260
261 -def expose(template=None, validators=None, allow_json=None, html=None,
262 format=None, content_type=None, inputform=None, fragment=False,
263 as_format="default", mapping=None, accept_format=None):
264 """Exposes a method to the web.
265
266 By putting the expose decorator on a method, you tell TurboGears that
267 the method should be accessible via URL traversal. Additionally, expose
268 handles the output processing (turning a dictionary into finished
269 output) and is also responsible for ensuring that the request is
270 wrapped in a database transaction.
271
272 You can apply multiple expose decorators to a method, if
273 you'd like to support multiple output formats. The decorator that's
274 listed first in your code without as_format or accept_format is
275 the default that is chosen when no format is specifically asked for.
276 Any other expose calls that are missing as_format and accept_format
277 will have as_format implicitly set to the whatever comes before
278 the ":" in the template name (or the whole template name if there
279 is no ":". For example, <code>expose("json")</code>, if it's not
280 the default expose, will have as_format set to "json".
281
282 When as_format is set, passing the same value in the tg_format
283 parameter in a request will choose the options for that expose
284 decorator. Similarly, accept_format will watch for matching
285 Accept headers. You can also use both. expose("json", as_format="json",
286 accept_format="application/json") will choose JSON output for either
287 case: tg_format=json as a parameter or Accept: application/json as a
288 request header.
289
290 Passing allow_json=True to an expose decorator
291 is equivalent to adding the decorator just mentioned.
292
293 Each expose decorator has its own set of options, and each one
294 can choose a different template or even template engine (you can
295 use Kid for HTML output and Cheetah for plain text, for example).
296 See the other expose parameters below to learn about the options
297 you can pass to the template engine.
298
299 Take a look at the
300 <a href="tests/test_expose-source.html">test_expose.py</a> suite
301 for more examples.
302
303 @param template "templateengine:dotted.reference" reference along the
304 Python path for the template and the template engine. For
305 example, "kid:foo.bar" will have Kid render the bar template in
306 the foo package.
307 @keyparam format format for the template engine to output (if the
308 template engine can render different formats. Kid, for example,
309 can render "html", "xml" or "xhtml")
310 @keyparam content_type sets the content-type http header
311 @keyparam allow_json allow the function to be exposed as json
312 @keyparam fragment for template engines (like Kid) that generate
313 DOCTYPE declarations and the like, this is a signal to
314 just generate the immediate template fragment. Use this
315 if you're building up a page from multiple templates or
316 going to put something onto a page with .innerHTML.
317 @keyparam mapping mapping with options that are sent to the template
318 engine
319 @keyparam as_format designates which value of tg_format will choose
320 this expose.
321 @keyparam accept_format which value of an Accept: header will
322 choose this expose.
323 @keyparam html deprecated in favor of template
324 @keyparam validators deprecated. Maps argument names to validator
325 applied to that arg
326 @keyparam inputform deprecated. A form object that generates the
327 input to this method
328
329 """
330 if html:
331 template = html
332 if not template:
333 template = format
334 if format == "json" or (format is None and