layouts.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. from UserDict import IterableUserDict
  2. from recaptcha.client.captcha import displayhtml
  3. from django.utils import formats
  4. from misago.conf import settings
  5. class FormLayout(object):
  6. """
  7. Conglomelate of fields and fieldsets describing form structure
  8. """
  9. def __init__(self, form, fieldsets=False):
  10. scaffold_fields = FormFields(form)
  11. scaffold_fieldsets = FormFieldsets(form, scaffold_fields.fields, fieldsets)
  12. self.multipart_form = scaffold_fields.multipart_form
  13. self.fieldsets = scaffold_fieldsets.fieldsets
  14. self.hidden = scaffold_fields.hidden
  15. # Extract fields definitions from form layout
  16. if self.fieldsets:
  17. self.fields = {}
  18. for fieldset in self.fieldsets:
  19. for field in fieldset['fields']:
  20. if field['nested']:
  21. for nested in field['nested']:
  22. self.fields[nested['id']] = nested
  23. else:
  24. self.fields[field['id']] = field
  25. else:
  26. self.fields = scaffold_fields.fields
  27. class FormFields(object):
  28. """
  29. Hydrator that builds fields list from form and blueprint
  30. """
  31. def __init__(self, form):
  32. self.multipart_form = False
  33. self.fields = {}
  34. self.hidden = []
  35. # Extract widgets from meta
  36. self.meta_widgets = {}
  37. try:
  38. self.meta_widgets = form.Meta.widgets
  39. except AttributeError:
  40. pass
  41. # Find out field input types
  42. for field in form.fields.keys():
  43. widget = self._get_widget(field, form.fields[field])
  44. widget_name = widget.__class__.__name__
  45. bound_field = form[field]
  46. blueprint = {
  47. 'attrs': {
  48. 'id': bound_field.auto_id,
  49. 'name': bound_field.html_name,
  50. },
  51. 'endrow': False,
  52. 'errors': [],
  53. 'has_value': bound_field.value() != None,
  54. 'help_text': bound_field.help_text,
  55. 'hidden': widget.is_hidden,
  56. 'html_id': bound_field.auto_id,
  57. 'html_name': bound_field.html_name,
  58. 'id': field,
  59. 'initial': bound_field.field.initial,
  60. 'label': bound_field.label,
  61. 'last': False,
  62. 'nested': [],
  63. 'required': bound_field.field.widget.is_required,
  64. 'show_hidden_initial': bound_field.field.show_hidden_initial,
  65. 'value': bound_field.value(),
  66. 'width': 100,
  67. 'widget': '',
  68. 'choices': [],
  69. }
  70. # Set multipart form
  71. if widget.needs_multipart_form:
  72. self.multipart_form = True
  73. # Get errors?
  74. if form.is_bound:
  75. for error in bound_field._errors():
  76. blueprint['errors'].append(error)
  77. try:
  78. for error in form.errors[field]:
  79. if not error in blueprint['errors']:
  80. blueprint['errors'].append(error)
  81. except KeyError:
  82. pass
  83. # Use clean value instead?
  84. try:
  85. if field in form.cleaned_data:
  86. blueprint['value'] = form.cleaned_data[field]
  87. except AttributeError:
  88. pass
  89. # TextInput
  90. if widget_name in ['TextInput', 'PasswordInput', 'Textarea']:
  91. blueprint['widget'] = 'text'
  92. blueprint['attrs']['type'] = 'text'
  93. try:
  94. blueprint['attrs']['maxlength'] = bound_field.field.max_length
  95. except AttributeError:
  96. pass
  97. # PasswordInput
  98. if widget_name == 'PasswordInput':
  99. blueprint['attrs']['type'] = 'password'
  100. # Textarea
  101. if widget_name == 'Textarea':
  102. blueprint['widget'] = 'textarea'
  103. # ReCaptcha
  104. if widget_name == 'ReCaptchaWidget':
  105. blueprint['widget'] = 'recaptcha'
  106. blueprint['attrs'] = {'html': displayhtml(
  107. settings.recaptcha_public,
  108. settings.recaptcha_ssl,
  109. bound_field.field.api_error,
  110. )}
  111. # HiddenInput
  112. if widget_name == 'HiddenInput':
  113. blueprint['widget'] = 'hidden'
  114. # MultipleHiddenInput
  115. if widget_name == 'MultipleHiddenInput':
  116. blueprint['widget'] = 'multiple_hidden'
  117. blueprint['attrs'] = {
  118. 'choices': widget.choices
  119. }
  120. # FileInput
  121. if widget_name == 'FileInput':
  122. blueprint['widget'] = 'file'
  123. # ClearableFileInput
  124. if widget_name == 'ClearableFileInput':
  125. blueprint['widget'] = 'file_clearable'
  126. # DateInput
  127. if widget_name == 'DateInput':
  128. blueprint['widget'] = 'date'
  129. try:
  130. blueprint['value'] = blueprint['value'].strftime('%Y-%m-%d')
  131. except AttributeError as e:
  132. pass
  133. # DateTimeInput
  134. if widget_name == 'DateTimeInput':
  135. blueprint['widget'] = 'datetime'
  136. try:
  137. blueprint['value'] = blueprint['value'].strftime('%Y-%m-%d %H:%M')
  138. except AttributeError as e:
  139. pass
  140. # TimeInput
  141. if widget_name == 'TimeInput':
  142. blueprint['widget'] = 'time'
  143. try:
  144. blueprint['value'] = blueprint['value'].strftime('%H:%M')
  145. except AttributeError as e:
  146. pass
  147. # CheckboxInput
  148. if widget_name == 'CheckboxInput':
  149. blueprint['widget'] = 'checkbox'
  150. # Select, NullBooleanSelect, SelectMultiple, RadioSelect, CheckboxSelectMultiple
  151. if widget_name in ['Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect', 'CheckboxSelectMultiple']:
  152. blueprint['choices'] = widget.choices
  153. # Yes-no radio select
  154. if widget_name == 'YesNoSwitch':
  155. blueprint['widget'] = 'yes_no_switch'
  156. # Select
  157. if widget_name == 'Select':
  158. blueprint['widget'] = 'select'
  159. if not blueprint['has_value']:
  160. blueprint['value'] = None
  161. # NullBooleanSelect
  162. if widget_name == 'NullBooleanSelect':
  163. blueprint['widget'] = 'null_boolean_select'
  164. # SelectMultiple
  165. if widget_name == 'SelectMultiple':
  166. blueprint['widget'] = 'select_multiple'
  167. # RadioSelect
  168. if widget_name == 'RadioSelect':
  169. blueprint['widget'] = 'radio_select'
  170. # CheckboxSelectMultiple
  171. if widget_name == 'CheckboxSelectMultiple':
  172. blueprint['widget'] = 'checkbox_select_multiple'
  173. # MultiWidget
  174. if widget_name == 'MultiWidget':
  175. blueprint['widget'] = 'multi'
  176. # SplitDateTimeWidget
  177. if widget_name == 'SplitDateTimeWidget':
  178. blueprint['widget'] = 'split_datetime'
  179. # SplitHiddenDateTimeWidget
  180. if widget_name == 'SplitHiddenDateTimeWidget':
  181. blueprint['widget'] = 'split_hidden_datetime'
  182. # SelectDateWidget
  183. if widget_name == 'SelectDateWidget':
  184. blueprint['widget'] = 'select_date'
  185. blueprint['years'] = widget.years
  186. # Store field in either of collections
  187. if blueprint['hidden']:
  188. blueprint['attrs']['type'] = 'hidden'
  189. self.hidden.append(blueprint)
  190. else:
  191. self.fields[field] = blueprint
  192. def _get_widget(self, name, field):
  193. if name in self.meta_widgets:
  194. return self.meta_widgets[name]
  195. return field.widget
  196. class FormFieldsets(object):
  197. """
  198. Hydrator that builds fieldset from form and blueprint
  199. """
  200. def __init__(self, form, fields, fieldsets=None):
  201. self.fieldsets = []
  202. # Use form layout
  203. if not fieldsets:
  204. try:
  205. fieldsets = form.layout
  206. except AttributeError:
  207. pass
  208. # Build fieldsets data
  209. if fieldsets:
  210. for blueprint in fieldsets:
  211. fieldset = {'legend': None, 'fields': [], 'help': None, 'last': False}
  212. fieldset['legend'] = blueprint[0]
  213. row_width = 0
  214. for field in blueprint[1]:
  215. try:
  216. if isinstance(field, basestring):
  217. fieldset['fields'].append(fields[field])
  218. elif field[0] == 'nested':
  219. subfields = {'label': None, 'help_text': None, 'nested': [], 'errors':[], 'endrow': False, 'last': False, 'width': 100}
  220. subfiels_ids = []
  221. try:
  222. subfields = field[2].update(subfields)
  223. except IndexError:
  224. pass
  225. for subfield in field[1]:
  226. if isinstance(subfield, basestring):
  227. subfiels_ids.append(subfield)
  228. subfields['nested'].append(fields[subfield])
  229. for error in fields[subfield]['errors']:
  230. if not error in subfields['errors']:
  231. subfields['errors'].append(error)
  232. else:
  233. subfiels_ids.append(subfield[0])
  234. try:
  235. subfield[1]['attrs'] = dict(fields[subfield[0]]['attrs'], **subfield[1]['attrs'])
  236. except KeyError:
  237. pass
  238. subfields['nested'].append(dict(fields[subfield[0]], **subfield[1]))
  239. for error in fields[subfield[0]]['errors']:
  240. if not error in subfields['errors']:
  241. subfields['errors'].append(error)
  242. if not subfields['label']:
  243. subfields['label'] = subfields['nested'][0]['label']
  244. if not subfields['help_text']:
  245. subfields['help_text'] = subfields['nested'][0]['help_text']
  246. try:
  247. subfields['errors'] = form.errors["_".join(subfiels_ids)]
  248. except KeyError:
  249. pass
  250. fieldset['fields'].append(subfields)
  251. else:
  252. try:
  253. field[1]['attrs'] = dict(fields[field[0]]['attrs'], **field[1]['attrs'])
  254. except KeyError:
  255. pass
  256. fieldset['fields'].append(dict(fields[field[0]], **field[1]))
  257. row_width += fieldset['fields'][-1]['width']
  258. if row_width >= 100:
  259. fieldset['fields'][-1]['endrow'] = True
  260. row_width = 0
  261. except (AttributeError, IndexError, KeyError):
  262. pass
  263. if fieldset['fields']:
  264. fieldset['fields'][-1]['endrow'] = True
  265. fieldset['fields'][-1]['last'] = True
  266. try:
  267. fieldset['help'] = blueprint[2]
  268. except IndexError:
  269. pass
  270. # Append complete fieldset
  271. if fieldset['fields']:
  272. self.fieldsets.append(fieldset)
  273. self.fieldsets[-1]['last'] = True