Source code for flask_admin.form.rules

from markupsafe import Markup

from flask_admin._compat import string_types
from flask_admin import helpers


[docs]class BaseRule(object): """ Base form rule. All form formatting rules should derive from `BaseRule`. """
[docs] 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 @property def visible_fields(self): """ A list of visible fields for the given rule. """ return [] 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) @property def visible_fields(self): """ Return visible fields for all child rules. """ visible_fields = [] for rule in self.rules: for field in rule.visible_fields: visible_fields.append(field) return visible_fields 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`. """
[docs] 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('.') try: field = context.resolve(parts[0]) except AttributeError: raise Exception('Your template is missing ' '"{% set render_ctx = h.resolve_ctx() %}"') 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) @property def visible_fields(self): return self.child_rule.visible_fields 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
@property def visible_fields(self): return [self.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 Row(NestedRule): def __init__(self, *columns, **kw): super(Row, self).__init__() self.rules = columns def __call__(self, form, form_opts=None, field_args={}): cols = [] for col in self.rules: if col.visible_fields: w_args = form_opts.widget_args.setdefault(col.visible_fields[0], {}) w_args.setdefault('column_class', 'col') cols.append(col(form, form_opts, field_args)) return Markup('<div class="form-row">%s</div>' % ''.join(cols)) class Group(Macro): def __init__(self, field_name, prepend=None, append=None, **kwargs): ''' Bootstrap Input group. ''' render_field = kwargs.get('render_field', 'lib.render_field') super(Group, self).__init__(render_field) self.field_name = field_name self._addons = [] if prepend: if not isinstance(prepend, (tuple, list)): prepend = [prepend] for cnf in prepend: if isinstance(cnf, str): self._addons.append({ 'pos': 'prepend', 'type': 'text', 'text': cnf }) continue if cnf['type'] in ('field', 'html', 'text'): cnf['pos'] = 'prepend' self._addons.append(cnf) if append: if not isinstance(append, (tuple, list)): append = [append] for cnf in append: if isinstance(cnf, str): self._addons.append({ 'pos': 'append', 'type': 'text', 'text': cnf }) continue if cnf['type'] in ('field', 'html', 'text'): cnf['pos'] = 'append' self._addons.append(cnf) print(self._addons) @property def visible_fields(self): fields = [self.field_name] for cnf in self._addons: if cnf['type'] == 'field': fields.append(cnf['name']) return fields 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)) if form_opts: widget_args = form_opts.widget_args else: widget_args = {} opts = {} prepend = [] append = [] for cnf in self._addons: ctn = None typ = cnf['type'] if typ == 'field': name = cnf['name'] fld = form._fields.get(name, None) if fld: w_args = widget_args.setdefault(name, {}) if fld.type in ('BooleanField', 'RadioField'): w_args.setdefault('class', 'form-check-input') else: w_args.setdefault('class', 'form-control') ctn = fld(**w_args) elif typ == 'text': ctn = '<span class="input-group-text">%s</span>' % cnf['text'] elif typ == 'html': ctn = cnf['html'] if ctn: if cnf['pos'] == 'prepend': prepend.append(ctn) else: append.append(ctn) if prepend: opts['prepend'] = Markup(''.join(prepend)) if append: opts['append'] = Markup(''.join(append)) opts.update(widget_args.get(self.field_name, {})) opts.update(field_args) params = { 'form': form, 'field': field, 'kwargs': opts } return super(Group, self).__call__(form, form_opts, params) 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) @property def visible_fields(self): visible_fields = [] for rule in self.rules: for field in rule.visible_fields: visible_fields.append(field) return visible_fields 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, string_types): result.append(self.convert_string(r).configure(self, parent)) elif isinstance(r, (tuple, list)): row = Row(*r) result.append(row.configure(self, parent)) else: try: result.append(r.configure(self, parent)) except AttributeError: raise TypeError('Could not convert "%s" to rule' % repr(r)) return result def __iter__(self): """ Iterate through registered rules. """ for r in self.rules: yield r