Picture-Puzzle-website/venv/Lib/site-packages/flask_admin/form/rules.py

563 lines
15 KiB
Python

from markupsafe import Markup
from flask_admin._compat import string_types
from flask_admin import helpers
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
@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()
class NestedRule(BaseRule):
"""
Nested rule. Can contain child rules and render them.
"""
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))
class Text(BaseRule):
"""
Render text (or HTML snippet) from string.
"""
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)
class HTML(Text):
"""
Shortcut for `Text` rule with `escape` set to `False`.
"""
def __init__(self, html):
super(HTML, self).__init__(html, escape=False)
class Macro(BaseRule):
"""
Render macro by its name from current Jinja2 context.
"""
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)
class Container(Macro):
"""
Render container around child rule.
"""
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)
class Field(Macro):
"""
Form field rule.
"""
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)
class Header(Macro):
"""
Render header text.
"""
def __init__(self, text, header_macro='lib.render_header'):
"""
Constructor.
:param text:
Text to render
:param header_macro:
Header rendering macro
"""
super(Header, self).__init__(header_macro, text=text)
class FieldSet(NestedRule):
"""
Field set with header.
"""
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