forms.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. from dominate import tags
  2. from dominate.util import raw
  3. from flask import current_app
  4. from markupsafe import Markup
  5. from visitor import Visitor
  6. def render_form(form, **kwargs):
  7. r = WTFormsRenderer(**kwargs)
  8. return Markup(r.visit(form))
  9. class WTFormsRenderer(Visitor):
  10. def __init__(self,
  11. action='',
  12. id=None,
  13. method='post',
  14. extra_classes=[],
  15. role='form',
  16. enctype=None):
  17. self.action = action
  18. self.form_id = id
  19. self.method = method
  20. self.extra_classes = extra_classes
  21. self.role = role
  22. self.enctype = enctype
  23. def _visited_file_field(self):
  24. if self._real_enctype is None:
  25. self._real_enctype = u'multipart/form-data'
  26. def _get_wrap(self, node, classes='form-group'):
  27. # add required class, which strictly speaking isn't bootstrap, but
  28. # a common enough customization
  29. if node.flags.required:
  30. classes += ' required'
  31. div = tags.div(_class=classes)
  32. if current_app.debug:
  33. div.add(tags.comment(' Field: {} ({}) '.format(
  34. node.name, node.__class__.__name__)))
  35. return div
  36. def _wrapped_input(self, node,
  37. type='text',
  38. classes=['form-control'], **kwargs):
  39. wrap = self._get_wrap(node)
  40. wrap.add(tags.label(node.label.text, _for=node.id))
  41. wrap.add(tags.input(type=type, _class=' '.join(classes), **kwargs))
  42. return wrap
  43. def visit_BooleanField(self, node):
  44. wrap = self._get_wrap(node, classes='checkbox')
  45. label = wrap.add(tags.label(_for=node.id))
  46. label.add(tags.input(type='checkbox'))
  47. label.add(node.label.text)
  48. return wrap
  49. def visit_DateField(self, node):
  50. return self._wrapped_input(node, 'date')
  51. def visit_DateTimeField(self, node):
  52. return self._wrapped_input(node, 'datetime-local')
  53. def visit_DecimalField(self, node):
  54. # FIXME: if range-validator is present, add limits?
  55. return self._wrapped_input(node, 'number')
  56. def visit_EmailField(self, node):
  57. # note: WTForms does not actually have an EmailField, this function
  58. # is called by visit_StringField based on which validators are enabled
  59. return self._wrapped_input(node, 'email')
  60. def visit_Field(self, node):
  61. # FIXME: add error class
  62. wrap = self._get_wrap(node)
  63. # add the label
  64. wrap.add(tags.label(node.label.text, _for=node.id))
  65. wrap.add(raw(node()))
  66. if node.description:
  67. wrap.add(tags.p(node.description, _class='help-block'))
  68. return wrap
  69. def visit_FileField(self, node):
  70. self._visited_file_field()
  71. return self._wrapped_input(node, 'file', classes=[])
  72. def visit_FloatField(self, node):
  73. # FIXME: if range-validator is present, add limits?
  74. return self._wrapped_input(node, 'number')
  75. def visit_Form(self, node):
  76. form = tags.form(_class=' '.join(['form'] + self.extra_classes))
  77. if self.action:
  78. form['action'] = self.action
  79. if self.form_id:
  80. form['id'] = self.form_id
  81. if self.method:
  82. form['method'] = self.method
  83. # prepare enctype, this will be auto-updated by file fields if
  84. # necessary
  85. self._real_enctype = self.enctype
  86. # render fields
  87. for field in node:
  88. elem = self.visit(field)
  89. form.add(elem)
  90. if self._real_enctype:
  91. form['enctype'] = self._real_enctype
  92. return form
  93. def visit_HiddenField(self, node):
  94. return raw(node())
  95. def visit_IntegerField(self, node):
  96. # FIXME: if range-validator is present, add limits?
  97. return self._wrapped_input(node, 'number', step=1)
  98. def visit_PasswordField(self, node):
  99. return self._wrapped_input(node, 'password')
  100. def visit_SubmitField(self, node):
  101. button = tags.button(node.label.text,
  102. _class='btn btn-default',
  103. type='submit')
  104. return button
  105. def visit_TextField(self, node):
  106. # legacy support for TextField, deprecated in WTForms 2.0
  107. return self.visit_StringField(node)
  108. def visit_StringField(self, node):
  109. for v in node.validators:
  110. if v.__class__.__name__ == 'Email':
  111. # render email fields differently
  112. return self.visit_EmailField(node)
  113. return self._wrapped_input(node, 'text')