fields.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. import time
  2. import datetime
  3. import json
  4. from wtforms import fields
  5. from flask_admin.babel import gettext
  6. from flask_admin._compat import text_type, as_unicode
  7. from . import widgets as admin_widgets
  8. """
  9. An understanding of WTForms's Custom Widgets is helpful for understanding this code:
  10. http://wtforms.simplecodes.com/docs/0.6.2/widgets.html#custom-widgets
  11. """
  12. __all__ = ['DateTimeField', 'TimeField', 'Select2Field', 'Select2TagsField',
  13. 'JSONField']
  14. class DateTimeField(fields.DateTimeField):
  15. """
  16. Allows modifying the datetime format of a DateTimeField using form_args.
  17. """
  18. widget = admin_widgets.DateTimePickerWidget()
  19. def __init__(self, label=None, validators=None, format=None, **kwargs):
  20. """
  21. Constructor
  22. :param label:
  23. Label
  24. :param validators:
  25. Field validators
  26. :param format:
  27. Format for text to date conversion. Defaults to '%Y-%m-%d %H:%M:%S'
  28. :param kwargs:
  29. Any additional parameters
  30. """
  31. super(DateTimeField, self).__init__(label, validators, **kwargs)
  32. self.format = format or '%Y-%m-%d %H:%M:%S'
  33. class TimeField(fields.Field):
  34. """
  35. A text field which stores a `datetime.time` object.
  36. Accepts time string in multiple formats: 20:10, 20:10:00, 10:00 am, 9:30pm, etc.
  37. """
  38. widget = admin_widgets.TimePickerWidget()
  39. def __init__(self, label=None, validators=None, formats=None,
  40. default_format=None, widget_format=None, **kwargs):
  41. """
  42. Constructor
  43. :param label:
  44. Label
  45. :param validators:
  46. Field validators
  47. :param formats:
  48. Supported time formats, as a enumerable.
  49. :param default_format:
  50. Default time format. Defaults to '%H:%M:%S'
  51. :param kwargs:
  52. Any additional parameters
  53. """
  54. super(TimeField, self).__init__(label, validators, **kwargs)
  55. self.formats = formats or ('%H:%M:%S', '%H:%M',
  56. '%I:%M:%S%p', '%I:%M%p',
  57. '%I:%M:%S %p', '%I:%M %p')
  58. self.default_format = default_format or '%H:%M:%S'
  59. def _value(self):
  60. if self.raw_data:
  61. return u' '.join(self.raw_data)
  62. elif self.data is not None:
  63. return self.data.strftime(self.default_format)
  64. else:
  65. return u''
  66. def process_formdata(self, valuelist):
  67. if valuelist:
  68. date_str = u' '.join(valuelist)
  69. if date_str.strip():
  70. for format in self.formats:
  71. try:
  72. timetuple = time.strptime(date_str, format)
  73. self.data = datetime.time(timetuple.tm_hour,
  74. timetuple.tm_min,
  75. timetuple.tm_sec)
  76. return
  77. except ValueError:
  78. pass
  79. raise ValueError(gettext('Invalid time format'))
  80. else:
  81. self.data = None
  82. class Select2Field(fields.SelectField):
  83. """
  84. `Select2 <https://github.com/ivaynberg/select2>`_ styled select widget.
  85. You must include select2.js, form-x.x.x.js and select2 stylesheet for it to
  86. work.
  87. """
  88. widget = admin_widgets.Select2Widget()
  89. def __init__(self, label=None, validators=None, coerce=text_type,
  90. choices=None, allow_blank=False, blank_text=None, **kwargs):
  91. super(Select2Field, self).__init__(
  92. label, validators, coerce, choices, **kwargs
  93. )
  94. self.allow_blank = allow_blank
  95. self.blank_text = blank_text or ' '
  96. def iter_choices(self):
  97. if self.allow_blank:
  98. yield (u'__None', self.blank_text, self.data is None)
  99. for value, label in self.choices:
  100. yield (value, label, self.coerce(value) == self.data)
  101. def process_data(self, value):
  102. if value is None:
  103. self.data = None
  104. else:
  105. try:
  106. self.data = self.coerce(value)
  107. except (ValueError, TypeError):
  108. self.data = None
  109. def process_formdata(self, valuelist):
  110. if valuelist:
  111. if valuelist[0] == '__None':
  112. self.data = None
  113. else:
  114. try:
  115. self.data = self.coerce(valuelist[0])
  116. except ValueError:
  117. raise ValueError(self.gettext(u'Invalid Choice: could not coerce'))
  118. def pre_validate(self, form):
  119. if self.allow_blank and self.data is None:
  120. return
  121. super(Select2Field, self).pre_validate(form)
  122. class Select2TagsField(fields.StringField):
  123. """`Select2 <http://ivaynberg.github.com/select2/#tags>`_ styled text field.
  124. You must include select2.js, form-x.x.x.js and select2 stylesheet for it to work.
  125. """
  126. widget = admin_widgets.Select2TagsWidget()
  127. def __init__(self, label=None, validators=None, save_as_list=False, coerce=text_type, **kwargs):
  128. """Initialization
  129. :param save_as_list:
  130. If `True` then populate ``obj`` using list else string
  131. """
  132. self.save_as_list = save_as_list
  133. self.coerce = coerce
  134. super(Select2TagsField, self).__init__(label, validators, **kwargs)
  135. def process_formdata(self, valuelist):
  136. if valuelist:
  137. if self.save_as_list:
  138. self.data = [self.coerce(v.strip()) for v in valuelist[0].split(',') if v.strip()]
  139. else:
  140. self.data = self.coerce(valuelist[0])
  141. def _value(self):
  142. if isinstance(self.data, (list, tuple)):
  143. return u','.join(as_unicode(v) for v in self.data)
  144. elif self.data:
  145. return as_unicode(self.data)
  146. else:
  147. return u''
  148. class JSONField(fields.TextAreaField):
  149. def _value(self):
  150. if self.raw_data:
  151. return self.raw_data[0]
  152. elif self.data:
  153. # prevent utf8 characters from being converted to ascii
  154. return as_unicode(json.dumps(self.data, ensure_ascii=False))
  155. else:
  156. return ''
  157. def process_formdata(self, valuelist):
  158. if valuelist:
  159. value = valuelist[0]
  160. # allow saving blank field as None
  161. if not value:
  162. self.data = None
  163. return
  164. try:
  165. self.data = json.loads(valuelist[0])
  166. except ValueError:
  167. raise ValueError(self.gettext('Invalid JSON'))