layouts.py 13 KB

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