from jinja2 import Markup
from flask.ext.admin._compat import string_types
from flask.ext.admin import helpers
[docs]class BaseRule(object):
"""
Base form rule. All form formatting rules should derive from `BaseRule`.
"""
def __init__(self):
self.parent = None
self.rule_set = None
def configure(self, rule_set, parent):
"""
Configure rule and assign to rule set.
:param rule_set:
Rule set
:param parent:
Parent rule (if any)
"""
self.parent = parent
self.rule_set = rule_set
return self
def __call__(self, form, form_opts=None, field_args={}):
"""
Render rule.
:param form:
Form object
:param form_opts:
Form options
:param field_args:
Optional arguments that should be passed to template or the field
"""
raise NotImplementedError()
[docs]class NestedRule(BaseRule):
"""
Nested rule. Can contain child rules and render them.
"""
[docs] def __init__(self, rules=[], separator=''):
"""
Constructor.
:param rules:
Child rule list
:param separator:
Default separator between rules when rendering them.
"""
super(NestedRule, self).__init__()
self.rules = list(rules)
self.separator = separator
def configure(self, rule_set, parent):
"""
Configure rule.
:param rule_set:
Rule set
:param parent:
Parent rule (if any)
"""
self.rules = rule_set.configure_rules(self.rules, self)
return super(NestedRule, self).configure(rule_set, parent)
def __iter__(self):
"""
Return rules.
"""
return self.rules
def __call__(self, form, form_opts=None, field_args={}):
"""
Render all children.
:param form:
Form object
:param form_opts:
Form options
:param field_args:
Optional arguments that should be passed to template or the field
"""
result = []
for r in self.rules:
result.append(r(form, form_opts, field_args))
return Markup(self.separator.join(result))
[docs]class Text(BaseRule):
"""
Render text (or HTML snippet) from string.
"""
[docs] def __init__(self, text, escape=True):
"""
Constructor.
:param text:
Text to render
:param escape:
Should text be escaped or not. Default is `True`.
"""
super(Text, self).__init__()
self.text = text
self.escape = escape
def __call__(self, form, form_opts=None, field_args={}):
if self.escape:
return self.text
return Markup(self.text)
[docs]class HTML(Text):
"""
Shortcut for `Text` rule with `escape` set to `False.
"""
def __init__(self, html):
super(HTML, self).__init__(html, escape=False)
[docs]class Macro(BaseRule):
"""
Render macro by its name from current Jinja2 context.
"""
[docs] def __init__(self, macro_name, **kwargs):
"""
Constructor.
:param macro_name:
Macro name
:param kwargs:
Default macro parameters
"""
super(Macro, self).__init__()
self.macro_name = macro_name
self.default_args = kwargs
def _resolve(self, context, name):
"""
Resolve macro in a Jinja2 context
:param context:
Jinja2 context
:param name:
Macro name. May be full path (with dots)
"""
parts = name.split('.')
field = context.resolve(parts[0])
if not field:
return None
for p in parts[1:]:
field = getattr(field, p, None)
if not field:
return field
return field
def __call__(self, form, form_opts=None, field_args={}):
"""
Render macro rule.
:param form:
Form object
:param form_opts:
Form options
:param field_args:
Optional arguments that should be passed to the macro
"""
context = helpers.get_render_ctx()
macro = self._resolve(context, self.macro_name)
if not macro:
raise ValueError('Cannot find macro %s in current context.' % self.macro_name)
opts = dict(self.default_args)
opts.update(field_args)
return macro(**opts)
[docs]class Container(Macro):
"""
Render container around child rule.
"""
[docs] def __init__(self, macro_name, child_rule, **kwargs):
"""
Constructor.
:param macro_name:
Macro name that will be used as a container
:param child_rule:
Child rule to be rendered inside of container
:param kwargs:
Container macro arguments
"""
super(Container, self).__init__(macro_name, **kwargs)
self.child_rule = child_rule
def configure(self, rule_set, parent):
"""
Configure rule.
:param rule_set:
Rule set
:param parent:
Parent rule (if any)
"""
self.child_rule.configure(rule_set, self)
return super(Container, self).configure(rule_set, parent)
def __call__(self, form, form_opts=None, field_args={}):
"""
Render container.
:param form:
Form object
:param form_opts:
Form options
:param field_args:
Optional arguments that should be passed to template or the field
"""
context = helpers.get_render_ctx()
def caller(**kwargs):
return context.call(self.child_rule, form, form_opts, kwargs)
args = dict(field_args)
args['caller'] = caller
return super(Container, self).__call__(form, form_opts, args)
[docs]class Field(Macro):
"""
Form field rule.
"""
[docs] def __init__(self, field_name, render_field='lib.render_field'):
"""
Constructor.
:param field_name:
Field name to render
:param render_field:
Macro that will be used to render the field.
"""
super(Field, self).__init__(render_field)
self.field_name = field_name
def __call__(self, form, form_opts=None, field_args={}):
"""
Render field.
:param form:
Form object
:param form_opts:
Form options
:param field_args:
Optional arguments that should be passed to template or the field
"""
field = getattr(form, self.field_name, None)
if field is None:
raise ValueError('Form %s does not have field %s' % (form, self.field_name))
opts = {}
if form_opts:
opts.update(form_opts.widget_args.get(self.field_name, {}))
opts.update(field_args)
params = {
'form': form,
'field': field,
'kwargs': opts
}
return super(Field, self).__call__(form, form_opts, params)
[docs]class FieldSet(NestedRule):
"""
Field set with header.
"""
[docs] def __init__(self, rules, header=None, separator=''):
"""
Constructor.
:param rules:
Child rules
:param header:
Header text
:param separator:
Child rule separator
"""
if header:
rule_set = [Header(header)] + list(rules)
else:
rule_set = list(rules)
super(FieldSet, self).__init__(rule_set, separator=separator)
class RuleSet(object):
"""
Rule set.
"""
def __init__(self, view, rules):
"""
Constructor.
:param view:
Administrative view
:param rules:
Rule list
"""
self.view = view
self.rules = self.configure_rules(rules)
def convert_string(self, value):
"""
Convert string to rule.
Override this method to change default behavior.
"""
return Field(value)
def configure_rules(self, rules, parent=None):
"""
Configure all rules recursively - bind them to current RuleSet and
convert string references to `Field` rules.
:param rules:
Rule list
:param parent:
Parent rule (if any)
"""
result = []
for r in rules:
if isinstance(r, BaseRule):
result.append(r.configure(self, parent))
elif isinstance(r, string_types):
result.append(self.convert_string(r).configure(self, parent))
else:
raise ValueError('Dont know how to convert %s' % repr(r))
return result
def __iter__(self):
"""
Iterate through registered rules.
"""
for r in self.rules:
yield r