rules.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. from jinja2 import Markup
  2. from flask_admin._compat import string_types
  3. from flask_admin import helpers
  4. class BaseRule(object):
  5. """
  6. Base form rule. All form formatting rules should derive from `BaseRule`.
  7. """
  8. def __init__(self):
  9. self.parent = None
  10. self.rule_set = None
  11. def configure(self, rule_set, parent):
  12. """
  13. Configure rule and assign to rule set.
  14. :param rule_set:
  15. Rule set
  16. :param parent:
  17. Parent rule (if any)
  18. """
  19. self.parent = parent
  20. self.rule_set = rule_set
  21. return self
  22. @property
  23. def visible_fields(self):
  24. """
  25. A list of visible fields for the given rule.
  26. """
  27. return []
  28. def __call__(self, form, form_opts=None, field_args={}):
  29. """
  30. Render rule.
  31. :param form:
  32. Form object
  33. :param form_opts:
  34. Form options
  35. :param field_args:
  36. Optional arguments that should be passed to template or the field
  37. """
  38. raise NotImplementedError()
  39. class NestedRule(BaseRule):
  40. """
  41. Nested rule. Can contain child rules and render them.
  42. """
  43. def __init__(self, rules=[], separator=''):
  44. """
  45. Constructor.
  46. :param rules:
  47. Child rule list
  48. :param separator:
  49. Default separator between rules when rendering them.
  50. """
  51. super(NestedRule, self).__init__()
  52. self.rules = list(rules)
  53. self.separator = separator
  54. def configure(self, rule_set, parent):
  55. """
  56. Configure rule.
  57. :param rule_set:
  58. Rule set
  59. :param parent:
  60. Parent rule (if any)
  61. """
  62. self.rules = rule_set.configure_rules(self.rules, self)
  63. return super(NestedRule, self).configure(rule_set, parent)
  64. @property
  65. def visible_fields(self):
  66. """
  67. Return visible fields for all child rules.
  68. """
  69. visible_fields = []
  70. for rule in self.rules:
  71. for field in rule.visible_fields:
  72. visible_fields.append(field)
  73. return visible_fields
  74. def __iter__(self):
  75. """
  76. Return rules.
  77. """
  78. return self.rules
  79. def __call__(self, form, form_opts=None, field_args={}):
  80. """
  81. Render all children.
  82. :param form:
  83. Form object
  84. :param form_opts:
  85. Form options
  86. :param field_args:
  87. Optional arguments that should be passed to template or the field
  88. """
  89. result = []
  90. for r in self.rules:
  91. result.append(r(form, form_opts, field_args))
  92. return Markup(self.separator.join(result))
  93. class Text(BaseRule):
  94. """
  95. Render text (or HTML snippet) from string.
  96. """
  97. def __init__(self, text, escape=True):
  98. """
  99. Constructor.
  100. :param text:
  101. Text to render
  102. :param escape:
  103. Should text be escaped or not. Default is `True`.
  104. """
  105. super(Text, self).__init__()
  106. self.text = text
  107. self.escape = escape
  108. def __call__(self, form, form_opts=None, field_args={}):
  109. if self.escape:
  110. return self.text
  111. return Markup(self.text)
  112. class HTML(Text):
  113. """
  114. Shortcut for `Text` rule with `escape` set to `False`.
  115. """
  116. def __init__(self, html):
  117. super(HTML, self).__init__(html, escape=False)
  118. class Macro(BaseRule):
  119. """
  120. Render macro by its name from current Jinja2 context.
  121. """
  122. def __init__(self, macro_name, **kwargs):
  123. """
  124. Constructor.
  125. :param macro_name:
  126. Macro name
  127. :param kwargs:
  128. Default macro parameters
  129. """
  130. super(Macro, self).__init__()
  131. self.macro_name = macro_name
  132. self.default_args = kwargs
  133. def _resolve(self, context, name):
  134. """
  135. Resolve macro in a Jinja2 context
  136. :param context:
  137. Jinja2 context
  138. :param name:
  139. Macro name. May be full path (with dots)
  140. """
  141. parts = name.split('.')
  142. try:
  143. field = context.resolve(parts[0])
  144. except AttributeError:
  145. raise Exception('Your template is missing '
  146. '"{% set render_ctx = h.resolve_ctx() %}"')
  147. if not field:
  148. return None
  149. for p in parts[1:]:
  150. field = getattr(field, p, None)
  151. if not field:
  152. return field
  153. return field
  154. def __call__(self, form, form_opts=None, field_args={}):
  155. """
  156. Render macro rule.
  157. :param form:
  158. Form object
  159. :param form_opts:
  160. Form options
  161. :param field_args:
  162. Optional arguments that should be passed to the macro
  163. """
  164. context = helpers.get_render_ctx()
  165. macro = self._resolve(context, self.macro_name)
  166. if not macro:
  167. raise ValueError('Cannot find macro %s in current context.' % self.macro_name)
  168. opts = dict(self.default_args)
  169. opts.update(field_args)
  170. return macro(**opts)
  171. class Container(Macro):
  172. """
  173. Render container around child rule.
  174. """
  175. def __init__(self, macro_name, child_rule, **kwargs):
  176. """
  177. Constructor.
  178. :param macro_name:
  179. Macro name that will be used as a container
  180. :param child_rule:
  181. Child rule to be rendered inside of container
  182. :param kwargs:
  183. Container macro arguments
  184. """
  185. super(Container, self).__init__(macro_name, **kwargs)
  186. self.child_rule = child_rule
  187. def configure(self, rule_set, parent):
  188. """
  189. Configure rule.
  190. :param rule_set:
  191. Rule set
  192. :param parent:
  193. Parent rule (if any)
  194. """
  195. self.child_rule.configure(rule_set, self)
  196. return super(Container, self).configure(rule_set, parent)
  197. @property
  198. def visible_fields(self):
  199. return self.child_rule.visible_fields
  200. def __call__(self, form, form_opts=None, field_args={}):
  201. """
  202. Render container.
  203. :param form:
  204. Form object
  205. :param form_opts:
  206. Form options
  207. :param field_args:
  208. Optional arguments that should be passed to template or the field
  209. """
  210. context = helpers.get_render_ctx()
  211. def caller(**kwargs):
  212. return context.call(self.child_rule, form, form_opts, kwargs)
  213. args = dict(field_args)
  214. args['caller'] = caller
  215. return super(Container, self).__call__(form, form_opts, args)
  216. class Field(Macro):
  217. """
  218. Form field rule.
  219. """
  220. def __init__(self, field_name, render_field='lib.render_field'):
  221. """
  222. Constructor.
  223. :param field_name:
  224. Field name to render
  225. :param render_field:
  226. Macro that will be used to render the field.
  227. """
  228. super(Field, self).__init__(render_field)
  229. self.field_name = field_name
  230. @property
  231. def visible_fields(self):
  232. return [self.field_name]
  233. def __call__(self, form, form_opts=None, field_args={}):
  234. """
  235. Render field.
  236. :param form:
  237. Form object
  238. :param form_opts:
  239. Form options
  240. :param field_args:
  241. Optional arguments that should be passed to template or the field
  242. """
  243. field = getattr(form, self.field_name, None)
  244. if field is None:
  245. raise ValueError('Form %s does not have field %s' % (form, self.field_name))
  246. opts = {}
  247. if form_opts:
  248. opts.update(form_opts.widget_args.get(self.field_name, {}))
  249. opts.update(field_args)
  250. params = {
  251. 'form': form,
  252. 'field': field,
  253. 'kwargs': opts
  254. }
  255. return super(Field, self).__call__(form, form_opts, params)
  256. class Header(Macro):
  257. """
  258. Render header text.
  259. """
  260. def __init__(self, text, header_macro='lib.render_header'):
  261. """
  262. Constructor.
  263. :param text:
  264. Text to render
  265. :param header_macro:
  266. Header rendering macro
  267. """
  268. super(Header, self).__init__(header_macro, text=text)
  269. class FieldSet(NestedRule):
  270. """
  271. Field set with header.
  272. """
  273. def __init__(self, rules, header=None, separator=''):
  274. """
  275. Constructor.
  276. :param rules:
  277. Child rules
  278. :param header:
  279. Header text
  280. :param separator:
  281. Child rule separator
  282. """
  283. if header:
  284. rule_set = [Header(header)] + list(rules)
  285. else:
  286. rule_set = list(rules)
  287. super(FieldSet, self).__init__(rule_set, separator=separator)
  288. class RuleSet(object):
  289. """
  290. Rule set.
  291. """
  292. def __init__(self, view, rules):
  293. """
  294. Constructor.
  295. :param view:
  296. Administrative view
  297. :param rules:
  298. Rule list
  299. """
  300. self.view = view
  301. self.rules = self.configure_rules(rules)
  302. @property
  303. def visible_fields(self):
  304. visible_fields = []
  305. for rule in self.rules:
  306. for field in rule.visible_fields:
  307. visible_fields.append(field)
  308. return visible_fields
  309. def convert_string(self, value):
  310. """
  311. Convert string to rule.
  312. Override this method to change default behavior.
  313. """
  314. return Field(value)
  315. def configure_rules(self, rules, parent=None):
  316. """
  317. Configure all rules recursively - bind them to current RuleSet and
  318. convert string references to `Field` rules.
  319. :param rules:
  320. Rule list
  321. :param parent:
  322. Parent rule (if any)
  323. """
  324. result = []
  325. for r in rules:
  326. if isinstance(r, string_types):
  327. result.append(self.convert_string(r).configure(self, parent))
  328. else:
  329. try:
  330. result.append(r.configure(self, parent))
  331. except AttributeError:
  332. raise TypeError('Could not convert "%s" to rule' % repr(r))
  333. return result
  334. def __iter__(self):
  335. """
  336. Iterate through registered rules.
  337. """
  338. for r in self.rules:
  339. yield r