core.py 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. from __future__ import unicode_literals
  2. try:
  3. from html import escape
  4. except ImportError:
  5. from cgi import escape
  6. from wtforms.compat import text_type, iteritems
  7. __all__ = (
  8. 'CheckboxInput', 'FileInput', 'HiddenInput', 'ListWidget', 'PasswordInput',
  9. 'RadioInput', 'Select', 'SubmitInput', 'TableWidget', 'TextArea',
  10. 'TextInput', 'Option'
  11. )
  12. def html_params(**kwargs):
  13. """
  14. Generate HTML attribute syntax from inputted keyword arguments.
  15. The output value is sorted by the passed keys, to provide consistent output
  16. each time this function is called with the same parameters. Because of the
  17. frequent use of the normally reserved keywords `class` and `for`, suffixing
  18. these with an underscore will allow them to be used.
  19. In order to facilitate the use of ``data-`` attributes, the first underscore
  20. behind the ``data``-element is replaced with a hyphen.
  21. >>> html_params(data_any_attribute='something')
  22. 'data-any_attribute="something"'
  23. In addition, the values ``True`` and ``False`` are special:
  24. * ``attr=True`` generates the HTML compact output of a boolean attribute,
  25. e.g. ``checked=True`` will generate simply ``checked``
  26. * ``attr=False`` will be ignored and generate no output.
  27. >>> html_params(name='text1', id='f', class_='text')
  28. 'class="text" id="f" name="text1"'
  29. >>> html_params(checked=True, readonly=False, name="text1", abc="hello")
  30. 'abc="hello" checked name="text1"'
  31. """
  32. params = []
  33. for k, v in sorted(iteritems(kwargs)):
  34. if k in ('class_', 'class__', 'for_'):
  35. k = k[:-1]
  36. elif k.startswith('data_'):
  37. k = k.replace('_', '-', 1)
  38. if v is True:
  39. params.append(k)
  40. elif v is False:
  41. pass
  42. else:
  43. params.append('%s="%s"' % (text_type(k), escape(text_type(v), quote=True)))
  44. return ' '.join(params)
  45. class HTMLString(text_type):
  46. """
  47. This is an "HTML safe string" class that is returned by WTForms widgets.
  48. For the most part, HTMLString acts like a normal unicode string, except
  49. in that it has a `__html__` method. This method is invoked by a compatible
  50. auto-escaping HTML framework to get the HTML-safe version of a string.
  51. Usage::
  52. HTMLString('<input type="text" value="hello">')
  53. """
  54. def __html__(self):
  55. """
  56. Give an HTML-safe string.
  57. This method actually returns itself, because it's assumed that
  58. whatever you give to HTMLString is a string with any unsafe values
  59. already escaped. This lets auto-escaping template frameworks
  60. know that this string is safe for HTML rendering.
  61. """
  62. return self
  63. class ListWidget(object):
  64. """
  65. Renders a list of fields as a `ul` or `ol` list.
  66. This is used for fields which encapsulate many inner fields as subfields.
  67. The widget will try to iterate the field to get access to the subfields and
  68. call them to render them.
  69. If `prefix_label` is set, the subfield's label is printed before the field,
  70. otherwise afterwards. The latter is useful for iterating radios or
  71. checkboxes.
  72. """
  73. def __init__(self, html_tag='ul', prefix_label=True):
  74. assert html_tag in ('ol', 'ul')
  75. self.html_tag = html_tag
  76. self.prefix_label = prefix_label
  77. def __call__(self, field, **kwargs):
  78. kwargs.setdefault('id', field.id)
  79. html = ['<%s %s>' % (self.html_tag, html_params(**kwargs))]
  80. for subfield in field:
  81. if self.prefix_label:
  82. html.append('<li>%s %s</li>' % (subfield.label, subfield()))
  83. else:
  84. html.append('<li>%s %s</li>' % (subfield(), subfield.label))
  85. html.append('</%s>' % self.html_tag)
  86. return HTMLString(''.join(html))
  87. class TableWidget(object):
  88. """
  89. Renders a list of fields as a set of table rows with th/td pairs.
  90. If `with_table_tag` is True, then an enclosing <table> is placed around the
  91. rows.
  92. Hidden fields will not be displayed with a row, instead the field will be
  93. pushed into a subsequent table row to ensure XHTML validity. Hidden fields
  94. at the end of the field list will appear outside the table.
  95. """
  96. def __init__(self, with_table_tag=True):
  97. self.with_table_tag = with_table_tag
  98. def __call__(self, field, **kwargs):
  99. html = []
  100. if self.with_table_tag:
  101. kwargs.setdefault('id', field.id)
  102. html.append('<table %s>' % html_params(**kwargs))
  103. hidden = ''
  104. for subfield in field:
  105. if subfield.type in ('HiddenField', 'CSRFTokenField'):
  106. hidden += text_type(subfield)
  107. else:
  108. html.append('<tr><th>%s</th><td>%s%s</td></tr>' % (text_type(subfield.label), hidden, text_type(subfield)))
  109. hidden = ''
  110. if self.with_table_tag:
  111. html.append('</table>')
  112. if hidden:
  113. html.append(hidden)
  114. return HTMLString(''.join(html))
  115. class Input(object):
  116. """
  117. Render a basic ``<input>`` field.
  118. This is used as the basis for most of the other input fields.
  119. By default, the `_value()` method will be called upon the associated field
  120. to provide the ``value=`` HTML attribute.
  121. """
  122. html_params = staticmethod(html_params)
  123. def __init__(self, input_type=None):
  124. if input_type is not None:
  125. self.input_type = input_type
  126. def __call__(self, field, **kwargs):
  127. kwargs.setdefault('id', field.id)
  128. kwargs.setdefault('type', self.input_type)
  129. if 'value' not in kwargs:
  130. kwargs['value'] = field._value()
  131. return HTMLString('<input %s>' % self.html_params(name=field.name, **kwargs))
  132. class TextInput(Input):
  133. """
  134. Render a single-line text input.
  135. """
  136. input_type = 'text'
  137. class PasswordInput(Input):
  138. """
  139. Render a password input.
  140. For security purposes, this field will not reproduce the value on a form
  141. submit by default. To have the value filled in, set `hide_value` to
  142. `False`.
  143. """
  144. input_type = 'password'
  145. def __init__(self, hide_value=True):
  146. self.hide_value = hide_value
  147. def __call__(self, field, **kwargs):
  148. if self.hide_value:
  149. kwargs['value'] = ''
  150. return super(PasswordInput, self).__call__(field, **kwargs)
  151. class HiddenInput(Input):
  152. """
  153. Render a hidden input.
  154. """
  155. field_flags = ('hidden', )
  156. input_type = 'hidden'
  157. class CheckboxInput(Input):
  158. """
  159. Render a checkbox.
  160. The ``checked`` HTML attribute is set if the field's data is a non-false value.
  161. """
  162. input_type = 'checkbox'
  163. def __call__(self, field, **kwargs):
  164. if getattr(field, 'checked', field.data):
  165. kwargs['checked'] = True
  166. return super(CheckboxInput, self).__call__(field, **kwargs)
  167. class RadioInput(Input):
  168. """
  169. Render a single radio button.
  170. This widget is most commonly used in conjunction with ListWidget or some
  171. other listing, as singular radio buttons are not very useful.
  172. """
  173. input_type = 'radio'
  174. def __call__(self, field, **kwargs):
  175. if field.checked:
  176. kwargs['checked'] = True
  177. return super(RadioInput, self).__call__(field, **kwargs)
  178. class FileInput(object):
  179. """
  180. Renders a file input chooser field.
  181. """
  182. def __call__(self, field, **kwargs):
  183. kwargs.setdefault('id', field.id)
  184. return HTMLString('<input %s>' % html_params(name=field.name, type='file', **kwargs))
  185. class SubmitInput(Input):
  186. """
  187. Renders a submit button.
  188. The field's label is used as the text of the submit button instead of the
  189. data on the field.
  190. """
  191. input_type = 'submit'
  192. def __call__(self, field, **kwargs):
  193. kwargs.setdefault('value', field.label.text)
  194. return super(SubmitInput, self).__call__(field, **kwargs)
  195. class TextArea(object):
  196. """
  197. Renders a multi-line text area.
  198. `rows` and `cols` ought to be passed as keyword args when rendering.
  199. """
  200. def __call__(self, field, **kwargs):
  201. kwargs.setdefault('id', field.id)
  202. return HTMLString('<textarea %s>%s</textarea>' % (
  203. html_params(name=field.name, **kwargs),
  204. escape(text_type(field._value()), quote=False)
  205. ))
  206. class Select(object):
  207. """
  208. Renders a select field.
  209. If `multiple` is True, then the `size` property should be specified on
  210. rendering to make the field useful.
  211. The field must provide an `iter_choices()` method which the widget will
  212. call on rendering; this method must yield tuples of
  213. `(value, label, selected)`.
  214. """
  215. def __init__(self, multiple=False):
  216. self.multiple = multiple
  217. def __call__(self, field, **kwargs):
  218. kwargs.setdefault('id', field.id)
  219. if self.multiple:
  220. kwargs['multiple'] = True
  221. html = ['<select %s>' % html_params(name=field.name, **kwargs)]
  222. for val, label, selected in field.iter_choices():
  223. html.append(self.render_option(val, label, selected))
  224. html.append('</select>')
  225. return HTMLString(''.join(html))
  226. @classmethod
  227. def render_option(cls, value, label, selected, **kwargs):
  228. if value is True:
  229. # Handle the special case of a 'True' value.
  230. value = text_type(value)
  231. options = dict(kwargs, value=value)
  232. if selected:
  233. options['selected'] = True
  234. return HTMLString('<option %s>%s</option>' % (html_params(**options), escape(text_type(label), quote=False)))
  235. class Option(object):
  236. """
  237. Renders the individual option from a select field.
  238. This is just a convenience for various custom rendering situations, and an
  239. option by itself does not constitute an entire field.
  240. """
  241. def __call__(self, field, **kwargs):
  242. return Select.render_option(field._value(), field.label.text, field.checked, **kwargs)