layouts.py 13 KB

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