fields.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. import itertools
  2. from wtforms.validators import ValidationError
  3. from wtforms.fields import FieldList, FormField, SelectFieldBase
  4. try:
  5. from wtforms.fields import _unset_value as unset_value
  6. except ImportError:
  7. from wtforms.utils import unset_value
  8. from flask_admin._compat import iteritems
  9. from .widgets import (InlineFieldListWidget, InlineFormWidget,
  10. AjaxSelect2Widget)
  11. class InlineFieldList(FieldList):
  12. widget = InlineFieldListWidget()
  13. def __init__(self, *args, **kwargs):
  14. super(InlineFieldList, self).__init__(*args, **kwargs)
  15. def __call__(self, **kwargs):
  16. # Create template
  17. meta = getattr(self, 'meta', None)
  18. if meta:
  19. template = self.unbound_field.bind(form=None, name='', _meta=meta)
  20. else:
  21. template = self.unbound_field.bind(form=None, name='')
  22. # Small hack to remove separator from FormField
  23. if isinstance(template, FormField):
  24. template.separator = ''
  25. template.process(None)
  26. return self.widget(self,
  27. template=template,
  28. check=self.display_row_controls,
  29. **kwargs)
  30. def display_row_controls(self, field):
  31. return True
  32. def process(self, formdata, data=unset_value):
  33. res = super(InlineFieldList, self).process(formdata, data)
  34. # Postprocess - contribute flag
  35. if formdata:
  36. for f in self.entries:
  37. key = 'del-%s' % f.id
  38. f._should_delete = key in formdata
  39. return res
  40. def validate(self, form, extra_validators=tuple()):
  41. """
  42. Validate this FieldList.
  43. Note that FieldList validation differs from normal field validation in
  44. that FieldList validates all its enclosed fields first before running any
  45. of its own validators.
  46. """
  47. self.errors = []
  48. # Run validators on all entries within
  49. for subfield in self.entries:
  50. if not self.should_delete(subfield) and not subfield.validate(form):
  51. self.errors.append(subfield.errors)
  52. chain = itertools.chain(self.validators, extra_validators)
  53. self._run_validation_chain(form, chain)
  54. return len(self.errors) == 0
  55. def should_delete(self, field):
  56. return getattr(field, '_should_delete', False)
  57. def populate_obj(self, obj, name):
  58. values = getattr(obj, name, None)
  59. try:
  60. ivalues = iter(values)
  61. except TypeError:
  62. ivalues = iter([])
  63. candidates = itertools.chain(ivalues, itertools.repeat(None))
  64. _fake = type(str('_fake'), (object, ), {})
  65. output = []
  66. for field, data in zip(self.entries, candidates):
  67. if not self.should_delete(field):
  68. fake_obj = _fake()
  69. fake_obj.data = data
  70. field.populate_obj(fake_obj, 'data')
  71. output.append(fake_obj.data)
  72. setattr(obj, name, output)
  73. class InlineFormField(FormField):
  74. """
  75. Inline version of the ``FormField`` widget.
  76. """
  77. widget = InlineFormWidget()
  78. class InlineModelFormField(FormField):
  79. """
  80. Customized ``FormField``.
  81. Excludes model primary key from the `populate_obj` and
  82. handles `should_delete` flag.
  83. """
  84. widget = InlineFormWidget()
  85. def __init__(self, form_class, pk, form_opts=None, **kwargs):
  86. super(InlineModelFormField, self).__init__(form_class, **kwargs)
  87. self._pk = pk
  88. self.form_opts = form_opts
  89. def get_pk(self):
  90. return getattr(self.form, self._pk).data
  91. def populate_obj(self, obj, name):
  92. for name, field in iteritems(self.form._fields):
  93. if name != self._pk:
  94. field.populate_obj(obj, name)
  95. class AjaxSelectField(SelectFieldBase):
  96. """
  97. Ajax Model Select Field
  98. """
  99. widget = AjaxSelect2Widget()
  100. separator = ','
  101. def __init__(self, loader, label=None, validators=None, allow_blank=False, blank_text=u'', **kwargs):
  102. super(AjaxSelectField, self).__init__(label, validators, **kwargs)
  103. self.loader = loader
  104. self.allow_blank = allow_blank
  105. self.blank_text = blank_text
  106. def _get_data(self):
  107. if self._formdata:
  108. model = self.loader.get_one(self._formdata)
  109. if model is not None:
  110. self._set_data(model)
  111. return self._data
  112. def _set_data(self, data):
  113. self._data = data
  114. self._formdata = None
  115. data = property(_get_data, _set_data)
  116. def _format_item(self, item):
  117. value = self.loader.format(self.data)
  118. return (value[0], value[1], True)
  119. def process_formdata(self, valuelist):
  120. if valuelist:
  121. if self.allow_blank and valuelist[0] == u'__None':
  122. self.data = None
  123. else:
  124. self._data = None
  125. self._formdata = valuelist[0]
  126. def pre_validate(self, form):
  127. if not self.allow_blank and self.data is None:
  128. raise ValidationError(self.gettext(u'Not a valid choice'))
  129. class AjaxSelectMultipleField(AjaxSelectField):
  130. """
  131. Ajax-enabled model multi-select field.
  132. """
  133. widget = AjaxSelect2Widget(multiple=True)
  134. def __init__(self, loader, label=None, validators=None, default=None, **kwargs):
  135. if default is None:
  136. default = []
  137. super(AjaxSelectMultipleField, self).__init__(loader, label, validators, default=default, **kwargs)
  138. self._invalid_formdata = False
  139. def _get_data(self):
  140. formdata = self._formdata
  141. if formdata:
  142. data = []
  143. # TODO: Optimize?
  144. for item in formdata:
  145. model = self.loader.get_one(item) if item else None
  146. if model:
  147. data.append(model)
  148. else:
  149. self._invalid_formdata = True
  150. self._set_data(data)
  151. return self._data
  152. def _set_data(self, data):
  153. self._data = data
  154. self._formdata = None
  155. data = property(_get_data, _set_data)
  156. def process_formdata(self, valuelist):
  157. self._formdata = set()
  158. for field in valuelist:
  159. for n in field.split(self.separator):
  160. self._formdata.add(n)
  161. def pre_validate(self, form):
  162. if self._invalid_formdata:
  163. raise ValidationError(self.gettext(u'Not a valid choice'))