form.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. import inspect
  2. import warnings
  3. from flask_admin.form import BaseForm, rules
  4. from flask_admin._compat import iteritems
  5. from wtforms.fields import HiddenField
  6. from wtforms.fields.core import UnboundField
  7. from wtforms.validators import InputRequired
  8. from .widgets import XEditableWidget
  9. def converts(*args):
  10. def _inner(func):
  11. func._converter_for = frozenset(args)
  12. return func
  13. return _inner
  14. def create_editable_list_form(form_base_class, form_class, widget=None):
  15. """
  16. Create a form class with all the fields wrapped in a FieldList.
  17. Wrapping each field in FieldList allows submitting POST requests
  18. in this format: ('<field_name>-<primary_key>', '<value>')
  19. Used in the editable list view.
  20. :param form_base_class:
  21. WTForms form class, by default `form_base_class` from base.
  22. :param form_class:
  23. WTForms form class generated by `form.get_form`.
  24. :param widget:
  25. WTForms widget class. Defaults to `XEditableWidget`.
  26. """
  27. if widget is None:
  28. widget = XEditableWidget()
  29. class ListForm(form_base_class):
  30. list_form_pk = HiddenField(validators=[InputRequired()])
  31. # iterate FormMeta to get unbound fields, replace widget, copy to ListForm
  32. for name, obj in iteritems(form_class.__dict__):
  33. if isinstance(obj, UnboundField):
  34. obj.kwargs['widget'] = widget
  35. setattr(ListForm, name, obj)
  36. if name == "list_form_pk":
  37. raise Exception('Form already has a list_form_pk column.')
  38. return ListForm
  39. class InlineBaseFormAdmin(object):
  40. """
  41. Settings for inline form administration.
  42. You can use this class to customize displayed form.
  43. For example::
  44. class MyUserInfoForm(InlineBaseFormAdmin):
  45. form_columns = ('name', 'email')
  46. """
  47. _defaults = ['form_base_class', 'form_columns', 'form_excluded_columns', 'form_args', 'form_extra_fields']
  48. def __init__(self, **kwargs):
  49. """
  50. Constructor
  51. :param kwargs:
  52. Additional options
  53. """
  54. for k in self._defaults:
  55. if not hasattr(self, k):
  56. setattr(self, k, None)
  57. for k, v in iteritems(kwargs):
  58. setattr(self, k, v)
  59. # Convert form rules
  60. form_rules = getattr(self, 'form_rules', None)
  61. if form_rules:
  62. self._form_rules = rules.RuleSet(self, form_rules)
  63. else:
  64. self._form_rules = None
  65. def get_form(self):
  66. """
  67. If you want to use completely custom form for inline field, you can override
  68. Flask-Admin form generation logic by overriding this method and returning your form.
  69. """
  70. return None
  71. def postprocess_form(self, form_class):
  72. """
  73. Post process form. Use this to contribute fields.
  74. For example::
  75. class MyInlineForm(InlineFormAdmin):
  76. def postprocess_form(self, form):
  77. form.value = StringField('value')
  78. return form
  79. class MyAdmin(ModelView):
  80. inline_models = (MyInlineForm(ValueModel),)
  81. """
  82. return form_class
  83. def on_model_change(self, form, model, is_created):
  84. """
  85. Called when inline model is about to be saved.
  86. :param form:
  87. Inline form
  88. :param model:
  89. Model
  90. :param is_created:
  91. Will be set to True if the model is being created, False if edited
  92. """
  93. pass
  94. def _on_model_change(self, form, model, is_created):
  95. """
  96. Compatibility helper.
  97. """
  98. try:
  99. self.on_model_change(form, model, is_created)
  100. except TypeError:
  101. msg = ('%s.on_model_change() now accepts third ' +
  102. 'parameter is_created. Please update your code') % self.model
  103. warnings.warn(msg)
  104. self.on_model_change(form, model)
  105. class InlineFormAdmin(InlineBaseFormAdmin):
  106. """
  107. Settings for inline form administration. Used by relational backends (SQLAlchemy, Peewee), where model
  108. class can not be inherited from the parent model definition.
  109. """
  110. def __init__(self, model, **kwargs):
  111. """
  112. Constructor
  113. :param model:
  114. Model class
  115. """
  116. self.model = model
  117. super(InlineFormAdmin, self).__init__(**kwargs)
  118. class ModelConverterBase(object):
  119. def __init__(self, converters=None, use_mro=True):
  120. self.use_mro = use_mro
  121. if not converters:
  122. converters = {}
  123. for name in dir(self):
  124. obj = getattr(self, name)
  125. if hasattr(obj, '_converter_for'):
  126. for classname in obj._converter_for:
  127. converters[classname] = obj
  128. self.converters = converters
  129. def get_converter(self, column):
  130. if self.use_mro:
  131. types = inspect.getmro(type(column.type))
  132. else:
  133. types = [type(column.type)]
  134. # Search by module + name
  135. for col_type in types:
  136. type_string = '%s.%s' % (col_type.__module__, col_type.__name__)
  137. if type_string in self.converters:
  138. return self.converters[type_string]
  139. # Search by name
  140. for col_type in types:
  141. if col_type.__name__ in self.converters:
  142. return self.converters[col_type.__name__]
  143. return None
  144. def get_form(self, model, base_class=BaseForm,
  145. only=None, exclude=None,
  146. field_args=None):
  147. raise NotImplementedError()
  148. class InlineModelConverterBase(object):
  149. form_admin_class = InlineFormAdmin
  150. def __init__(self, view):
  151. """
  152. Base constructor
  153. :param view:
  154. View class
  155. """
  156. self.view = view
  157. def get_label(self, info, name):
  158. """
  159. Get inline model field label
  160. :param info:
  161. Inline model info
  162. :param name:
  163. Field name
  164. """
  165. form_name = getattr(info, 'form_label', None)
  166. if form_name:
  167. return form_name
  168. column_labels = getattr(self.view, 'column_labels', None)
  169. if column_labels and name in column_labels:
  170. return column_labels[name]
  171. return None
  172. def get_info(self, p):
  173. """
  174. Figure out InlineFormAdmin information.
  175. :param p:
  176. Inline model. Can be one of:
  177. - ``tuple``, first value is related model instance,
  178. second is dictionary with options
  179. - ``InlineFormAdmin`` instance
  180. - Model class
  181. """
  182. if isinstance(p, tuple):
  183. return self.form_admin_class(p[0], **p[1])
  184. elif isinstance(p, self.form_admin_class):
  185. return p
  186. return None
  187. class FieldPlaceholder(object):
  188. """
  189. Field placeholder for model convertors.
  190. """
  191. def __init__(self, field):
  192. self.field = field