1 """Metaclass for TurboGears widgets and support for external packages"""
2
3 __all__ = ['MetaWidget', 'load_template']
4
5 import copy
6 import threading
7 import re
8
9 from inspect import isclass
10 from itertools import count, ifilter
11 from new import instancemethod
12
13 from turbogears import validators
14 from turbogears.util import setlike
15 from formencode.schema import Schema
16
17 default_engine = 'genshi'
18 param_prefix = '_param_'
19
20
21
22
23
24
73
74
75
76
77
78
80 """Descriptor to support automatic callable support for widget params."""
81
84
86 if obj is None:
87
88
89 return getattr(typ, self.param_name)
90 param = getattr(obj, self.param_name)
91 if callable(param):
92 return param()
93 return param
94
96 setattr(obj, self.param_name, value)
97
98
109
110
174
175 return widget_init
176
177
178
179
180
181
182 _re_xmlns_py = re.compile(
183 r'^\s*(?:<\?xml*?\?>)?\s*(?:<!DOCTYPE.*?>)?(?:\s*<\?\w+.*?\?>)*'
184 r'\s*<\w+\b[^>]*?\bxmlns:py=["\'](.*?)["\']', re.DOTALL)
185
193
194
195 _template_loader = {}
196
198 """Get a template loader function for the given templating engine.
199
200 Raises an ImportError if the templating engine is not installed.
201
202 """
203 try:
204 return _template_loader[engine_name]
205 except KeyError:
206 if engine_name == 'genshi':
207 from genshi.template import MarkupTemplate as GenshiMarkupTemplate
208
209 def load_template(template, mod_name=None):
210 """Load Genshi template"""
211 return GenshiMarkupTemplate(template)
212 elif engine_name == 'kid':
213 from kid import load_template as load_kid_template
214
215
216
217 instance_serial = count()
218 def load_template(template, mod_name=None):
219 """Load Kid template"""
220 if not mod_name:
221 mod_name = 'instance_template_%d' % instance_serial.next()
222 return load_kid_template(template, name=mod_name).Template
223 else:
224 return None
225 _template_loader[engine_name] = load_template
226 return load_template
227
228
229 _re_mod_name = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*$")
230
232 """Load the template with the engine into the module name.
233
234 If engine_name is None, it will be derived from the template.
235 If the engine cannot be derived, the default_engine will be used.
236 If mod_name is None, an unique one will be generated.
237 Returns a tuple (template_class, template_text, engine_name).
238
239 """
240
241 if isinstance(template, basestring):
242 if template.startswith('genshi:') or template.startswith('kid:'):
243 engine_name, template = template.split(':', 1)
244 if _re_mod_name.match(template):
245 template_c, template = template, None
246 if not engine_name:
247 engine_name = default_engine
248 else:
249 if not engine_name:
250 engine_name = determine_template_engine(
251 template) or default_engine
252 try:
253 load_template = get_template_loader(engine_name)
254 except ImportError, error:
255 raise ImportError("%s\n\nCannot load the following template"
256 " because the %s engine is not installed:\n\n%s"
257 % (error, engine_name, template.strip()))
258 if load_template is None:
259 raise ValueError("TurboGears widgets do not support"
260 " %s templates." % engine_name)
261 template_c = load_template(template, mod_name)
262 else:
263 if isinstance(template, MarkupTemplate):
264 engine_name = 'genshi'
265 elif isinstance(template, type):
266 engine_name = 'kid'
267 else:
268 engine_name = default_engine
269 template_c, template = template, None
270 return template_c, template, engine_name
271
272
273
274
275
276
278 """A do-nothing validator.
279
280 Used as a placeholder for fields with no validator so they don't get
281 stripped by the Schema.
282
283 """
284
285 if_missing = None
286
287
289 """Recursively copy a schema."""
290 new_schema = copy.copy(schema)
291 new_schema.pre_validators = schema.pre_validators[:]
292 new_schema.chained_validators = schema.chained_validators[:]
293 fields = {}
294 for k, v in schema.fields.iteritems():
295 if isinstance(v, Schema):
296 v = copy_schema(v)
297 fields[k] = v
298 new_schema.fields = fields
299 return new_schema
300
301
303 """Recursively merge from_schema into to_schema
304
305 Takes care of leaving to_schema intact if inplace is False (default).
306 Returns a new Schema instance if inplace is False or to_schema is a Schema
307 class not an instance or the changed to_schema.
308
309 """
310
311
312 if isclass(to_schema) and issubclass(to_schema, validators.Schema):
313 to_schema = to_schema()
314 elif not inplace:
315 to_schema = copy_schema(to_schema)
316
317
318 is_schema = lambda f: isinstance(f[1], validators.Schema)
319 seen = set()
320 for k, v in ifilter(is_schema, to_schema.fields.iteritems()):
321 seen.add(k)
322 from_field = from_schema.fields.get(k)
323 if from_field:
324 v = merge_schemas(v, from_field)
325 to_schema.add_field(k, v)
326
327
328 can_add = lambda f: f[0] not in seen and can_add_field(to_schema, f[0])
329 for field in ifilter(can_add, from_schema.fields.iteritems()):
330 to_schema.add_field(*field)
331
332 return to_schema
333
334
336 """Add widget's validator if any to the given schema."""
337 name = widget.name
338 if widget.validator is not None:
339 if isinstance(widget.validator, validators.Schema):
340
341 if widget.name in schema.fields:
342 assert (isinstance(schema.fields[name], validators.Schema)
343 or issubclass(schema.fields[name], validators.Schema)
344 ), "Validator for '%s' should be a Schema subclass" % name
345 v = merge_schemas(schema.fields[name], widget.validator)
346 else:
347 v = widget.validator
348 schema.add_field(name, v)
349 elif can_add_field(schema, name):
350
351 schema.add_field(name, widget.validator)
352 elif can_add_field(schema, name):
353 schema.add_field(name, NullValidator())
354
355
357 """Generate or extend a copy of schema with all validators of all widgets.
358
359 schema may be a schema instance or class, widgets is a list of widgets
360 instances.
361 Returns the new schema instance.
362
363 """
364 if schema is None:
365 schema = validators.Schema()
366 elif isclass(schema) and issubclass(schema, validators.Schema):
367
368 schema = schema()
369 else:
370 schema = copy_schema(schema)
371 for widget in widgets:
372 if widget.is_named:
373 add_field_to_schema(schema, widget)
374 return schema
375
376
378 """Checks if we can safely add a field.
379
380 Makes sure we're not overriding any field in the Schema. NullValidators
381 are ok to override.
382
383 """
384 current_field = schema.fields.get(field_name)
385 return bool(current_field is None
386 or isinstance(current_field, NullValidator))
387