fields.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. from __future__ import unicode_literals
  2. import operator
  3. from packaging import version
  4. import wtforms
  5. from wtforms import widgets
  6. from wtforms.fields import Field, SelectField, SelectFieldBase
  7. from wtforms.validators import ValidationError
  8. IS_WTFORMS_LESS_THEN_3_1_0 = version.parse(wtforms.__version__) < version.parse("3.1.0")
  9. class AJAXSelectField(Field):
  10. """
  11. Simple class to convert primary key to ORM objects
  12. for SQLAlchemy and fab normal processing on add and update
  13. This WTF field class is prepared to be used in related views or directly on forms.
  14. :param label: The label to render on form
  15. :param validators: A list of form validators
  16. :param: datamodel: An initialized SQLAInterface with a model
  17. :param: col_name: The column that maps to the model
  18. :param: is_related:
  19. If the model column is a relationship or direct on
  20. this case use col_name with the pk
  21. """
  22. def __init__(
  23. self,
  24. label=None,
  25. validators=None,
  26. datamodel=None,
  27. col_name=None,
  28. is_related=True,
  29. **kwargs
  30. ):
  31. super(AJAXSelectField, self).__init__(label, validators, **kwargs)
  32. self.datamodel = datamodel
  33. self.col_name = col_name
  34. self.is_related = is_related
  35. def process_data(self, value):
  36. """
  37. Process the Python data applied to this field and store the result.
  38. This will be called during form construction by the form's `kwargs` or
  39. `obj` argument.
  40. Converting ORM object to primary key for client form.
  41. :param value: The python object containing the value to process.
  42. """
  43. if value:
  44. if self.is_related:
  45. self.data = self.datamodel.get_related_interface(
  46. self.col_name
  47. ).get_pk_value(value)
  48. else:
  49. self.data = self.datamodel.get(value)
  50. else:
  51. self.data = None
  52. def process_formdata(self, valuelist):
  53. """
  54. Process data received over the wire from a form.
  55. This will be called during form construction with data supplied
  56. through the `formdata` argument.
  57. Converting primary key to ORM for server processing.
  58. :param valuelist: A list of strings to process.
  59. """
  60. if valuelist:
  61. if self.is_related:
  62. self.data = self.datamodel.get_related_interface(self.col_name).get(
  63. valuelist[0]
  64. )
  65. else:
  66. self.data = self.datamodel.get(valuelist[0])
  67. class QuerySelectField(SelectFieldBase):
  68. """
  69. Based on WTForms QuerySelectField
  70. """
  71. widget = widgets.Select()
  72. def __init__(
  73. self,
  74. label=None,
  75. validators=None,
  76. query_func=None,
  77. get_pk_func=None,
  78. get_label=None,
  79. allow_blank=False,
  80. blank_text="",
  81. **kwargs
  82. ):
  83. super(QuerySelectField, self).__init__(label, validators, **kwargs)
  84. self.query_func = query_func
  85. self.get_pk_func = get_pk_func
  86. if get_label is None:
  87. self.get_label = lambda x: x
  88. elif isinstance(get_label, str):
  89. self.get_label = operator.attrgetter(get_label)
  90. else:
  91. self.get_label = get_label
  92. self.allow_blank = allow_blank
  93. self.blank_text = blank_text
  94. self._object_list = None
  95. def _get_data(self):
  96. if self._formdata is not None:
  97. for pk, obj in self._get_object_list():
  98. if pk == self._formdata:
  99. self._set_data(obj)
  100. break
  101. return self._data
  102. def _set_data(self, data):
  103. self._data = data
  104. self._formdata = None
  105. data = property(_get_data, _set_data)
  106. def _get_object_list(self):
  107. if self._object_list is None:
  108. objs = self.query_func()
  109. self._object_list = list((str(self.get_pk_func(obj)), obj) for obj in objs)
  110. return self._object_list
  111. def iter_choices(self):
  112. if self.allow_blank:
  113. if IS_WTFORMS_LESS_THEN_3_1_0:
  114. yield ("__None", self.blank_text, self.data is None)
  115. else:
  116. yield ("__None", self.blank_text, self.data is None, {})
  117. for pk, obj in self._get_object_list():
  118. if IS_WTFORMS_LESS_THEN_3_1_0:
  119. yield (pk, self.get_label(obj), obj == self.data)
  120. else:
  121. yield (pk, self.get_label(obj), obj == self.data, {})
  122. def process_formdata(self, valuelist):
  123. if valuelist:
  124. if self.allow_blank and valuelist[0] == "__None":
  125. self.data = None
  126. else:
  127. self._data = None
  128. self._formdata = valuelist[0]
  129. def pre_validate(self, form):
  130. data = self.data
  131. if data is not None:
  132. for pk, obj in self._get_object_list():
  133. if data == obj:
  134. break
  135. else:
  136. raise ValidationError(self.gettext("Not a valid choice"))
  137. elif self._formdata or not self.allow_blank:
  138. raise ValidationError(self.gettext("Not a valid choice"))
  139. class QuerySelectMultipleField(QuerySelectField):
  140. """
  141. Very similar to QuerySelectField with the difference that this will
  142. display a multiple select. The data property will hold a list with ORM
  143. model instances and will be an empty list when no value is selected.
  144. If any of the items in the data list or submitted form data cannot be
  145. found in the query, this will result in a validation error.
  146. """
  147. widget = widgets.Select(multiple=True)
  148. def __init__(self, label=None, validators=None, default=None, **kwargs):
  149. if default is None:
  150. default = []
  151. super(QuerySelectMultipleField, self).__init__(
  152. label, validators, default=default, **kwargs
  153. )
  154. if kwargs.get("allow_blank", False):
  155. import warnings
  156. warnings.warn(
  157. "allow_blank=True does not do anything for QuerySelectMultipleField."
  158. )
  159. self._invalid_formdata = False
  160. def _get_data(self):
  161. formdata = self._formdata
  162. if formdata is not None:
  163. data = []
  164. for pk, obj in self._get_object_list():
  165. if not formdata:
  166. break
  167. elif pk in formdata:
  168. formdata.remove(pk)
  169. data.append(obj)
  170. if formdata:
  171. self._invalid_formdata = True
  172. self._set_data(data)
  173. return self._data
  174. def _set_data(self, data):
  175. self._data = data
  176. self._formdata = None
  177. data = property(_get_data, _set_data)
  178. def iter_choices(self):
  179. for pk, obj in self._get_object_list():
  180. if IS_WTFORMS_LESS_THEN_3_1_0:
  181. yield (pk, self.get_label(obj), obj in self.data)
  182. else:
  183. yield (pk, self.get_label(obj), obj in self.data, {})
  184. def process_formdata(self, valuelist):
  185. self._formdata = set(valuelist)
  186. def pre_validate(self, form):
  187. if self._invalid_formdata:
  188. raise ValidationError(self.gettext("Not a valid choice"))
  189. elif self.data:
  190. obj_list = list(x[1] for x in self._get_object_list())
  191. if not isinstance(self.data, list):
  192. self.data = [self.data]
  193. for v in self.data:
  194. if v not in obj_list:
  195. raise ValidationError(self.gettext("Not a valid choice"))
  196. class EnumField(SelectField):
  197. """Selection field for Sqlalchemy Enum type.
  198. The meaning of enum_class and enums is the same as for
  199. attributes on sqlalchemy.types.Enum:
  200. :param enum_class: either None or a subclass of Python enum.Enum
  201. :param enums: a sequence of strings, if enum_class is not Null than it should be
  202. `list(enum_class.__members__)`
  203. """
  204. def __init__(
  205. self, enum_class, enums, label=None, validators=None, default=None, **kwargs
  206. ):
  207. self._enum_class = enum_class
  208. self._enums = enums
  209. # Column(Enum(enum.Enum)) case
  210. if enum_class is not None:
  211. labels = [
  212. str(enum_class.__members__[enum_member].value) for enum_member in enums
  213. ]
  214. def coerce(value):
  215. if value is None:
  216. return None
  217. elif isinstance(value, enum_class):
  218. return value
  219. else:
  220. return enum_class.__members__[value]
  221. # Column(Enum(*enums)) case
  222. else:
  223. labels = enums
  224. def coerce(value):
  225. if value is None:
  226. return None
  227. return str(value)
  228. choices = list(zip(enums, labels))
  229. super(EnumField, self).__init__(
  230. label=label,
  231. validators=validators,
  232. default=default,
  233. coerce=coerce,
  234. choices=choices,
  235. **kwargs
  236. )
  237. def pre_validate(self, form):
  238. for v, _ in self.choices:
  239. if self.data == self.coerce(v):
  240. break
  241. else:
  242. raise ValueError(self.gettext("Not a valid choice"))