formsets.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. from __future__ import unicode_literals
  2. from django.core.exceptions import ValidationError
  3. from django.forms import Form
  4. from django.forms.fields import IntegerField, BooleanField
  5. from django.forms.utils import ErrorList
  6. from django.forms.widgets import HiddenInput
  7. from django.utils.encoding import python_2_unicode_compatible
  8. from django.utils.functional import cached_property
  9. from django.utils.safestring import mark_safe
  10. from django.utils import six
  11. from django.utils.six.moves import xrange
  12. from django.utils.translation import ungettext, ugettext as _
  13. __all__ = ('BaseFormSet', 'formset_factory', 'all_valid')
  14. # special field names
  15. TOTAL_FORM_COUNT = 'TOTAL_FORMS'
  16. INITIAL_FORM_COUNT = 'INITIAL_FORMS'
  17. MIN_NUM_FORM_COUNT = 'MIN_NUM_FORMS'
  18. MAX_NUM_FORM_COUNT = 'MAX_NUM_FORMS'
  19. ORDERING_FIELD_NAME = 'ORDER'
  20. DELETION_FIELD_NAME = 'DELETE'
  21. # default minimum number of forms in a formset
  22. DEFAULT_MIN_NUM = 0
  23. # default maximum number of forms in a formset, to prevent memory exhaustion
  24. DEFAULT_MAX_NUM = 1000
  25. class ManagementForm(Form):
  26. """
  27. ``ManagementForm`` is used to keep track of how many form instances
  28. are displayed on the page. If adding new forms via javascript, you should
  29. increment the count field of this form as well.
  30. """
  31. def __init__(self, *args, **kwargs):
  32. self.base_fields[TOTAL_FORM_COUNT] = IntegerField(widget=HiddenInput)
  33. self.base_fields[INITIAL_FORM_COUNT] = IntegerField(widget=HiddenInput)
  34. # MIN_NUM_FORM_COUNT and MAX_NUM_FORM_COUNT are output with the rest of
  35. # the management form, but only for the convenience of client-side
  36. # code. The POST value of them returned from the client is not checked.
  37. self.base_fields[MIN_NUM_FORM_COUNT] = IntegerField(required=False, widget=HiddenInput)
  38. self.base_fields[MAX_NUM_FORM_COUNT] = IntegerField(required=False, widget=HiddenInput)
  39. super(ManagementForm, self).__init__(*args, **kwargs)
  40. @python_2_unicode_compatible
  41. class BaseFormSet(object):
  42. """
  43. A collection of instances of the same Form class.
  44. """
  45. def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
  46. initial=None, error_class=ErrorList):
  47. self.is_bound = data is not None or files is not None
  48. self.prefix = prefix or self.get_default_prefix()
  49. self.auto_id = auto_id
  50. self.data = data or {}
  51. self.files = files or {}
  52. self.initial = initial
  53. self.error_class = error_class
  54. self._errors = None
  55. self._non_form_errors = None
  56. def __str__(self):
  57. return self.as_table()
  58. def __iter__(self):
  59. """Yields the forms in the order they should be rendered"""
  60. return iter(self.forms)
  61. def __getitem__(self, index):
  62. """Returns the form at the given index, based on the rendering order"""
  63. return self.forms[index]
  64. def __len__(self):
  65. return len(self.forms)
  66. def __bool__(self):
  67. """All formsets have a management form which is not included in the length"""
  68. return True
  69. def __nonzero__(self): # Python 2 compatibility
  70. return type(self).__bool__(self)
  71. @property
  72. def management_form(self):
  73. """Returns the ManagementForm instance for this FormSet."""
  74. if self.is_bound:
  75. form = ManagementForm(self.data, auto_id=self.auto_id, prefix=self.prefix)
  76. if not form.is_valid():
  77. raise ValidationError(
  78. _('ManagementForm data is missing or has been tampered with'),
  79. code='missing_management_form',
  80. )
  81. else:
  82. form = ManagementForm(auto_id=self.auto_id, prefix=self.prefix, initial={
  83. TOTAL_FORM_COUNT: self.total_form_count(),
  84. INITIAL_FORM_COUNT: self.initial_form_count(),
  85. MIN_NUM_FORM_COUNT: self.min_num,
  86. MAX_NUM_FORM_COUNT: self.max_num
  87. })
  88. return form
  89. def total_form_count(self):
  90. """Returns the total number of forms in this FormSet."""
  91. if self.is_bound:
  92. # return absolute_max if it is lower than the actual total form
  93. # count in the data; this is DoS protection to prevent clients
  94. # from forcing the server to instantiate arbitrary numbers of
  95. # forms
  96. return min(self.management_form.cleaned_data[TOTAL_FORM_COUNT], self.absolute_max)
  97. else:
  98. initial_forms = self.initial_form_count()
  99. total_forms = max(initial_forms, self.min_num) + self.extra
  100. # Allow all existing related objects/inlines to be displayed,
  101. # but don't allow extra beyond max_num.
  102. if initial_forms > self.max_num >= 0:
  103. total_forms = initial_forms
  104. elif total_forms > self.max_num >= 0:
  105. total_forms = self.max_num
  106. return total_forms
  107. def initial_form_count(self):
  108. """Returns the number of forms that are required in this FormSet."""
  109. if self.is_bound:
  110. return self.management_form.cleaned_data[INITIAL_FORM_COUNT]
  111. else:
  112. # Use the length of the initial data if it's there, 0 otherwise.
  113. initial_forms = len(self.initial) if self.initial else 0
  114. return initial_forms
  115. @cached_property
  116. def forms(self):
  117. """
  118. Instantiate forms at first property access.
  119. """
  120. # DoS protection is included in total_form_count()
  121. forms = [self._construct_form(i) for i in xrange(self.total_form_count())]
  122. return forms
  123. def _construct_form(self, i, **kwargs):
  124. """
  125. Instantiates and returns the i-th form instance in a formset.
  126. """
  127. defaults = {
  128. 'auto_id': self.auto_id,
  129. 'prefix': self.add_prefix(i),
  130. 'error_class': self.error_class,
  131. }
  132. if self.is_bound:
  133. defaults['data'] = self.data
  134. defaults['files'] = self.files
  135. if self.initial and 'initial' not in kwargs:
  136. try:
  137. defaults['initial'] = self.initial[i]
  138. except IndexError:
  139. pass
  140. # Allow extra forms to be empty, unless they're part of
  141. # the minimum forms.
  142. if i >= self.initial_form_count() and i >= self.min_num:
  143. defaults['empty_permitted'] = True
  144. defaults.update(kwargs)
  145. form = self.form(**defaults)
  146. self.add_fields(form, i)
  147. return form
  148. @property
  149. def initial_forms(self):
  150. """Return a list of all the initial forms in this formset."""
  151. return self.forms[:self.initial_form_count()]
  152. @property
  153. def extra_forms(self):
  154. """Return a list of all the extra forms in this formset."""
  155. return self.forms[self.initial_form_count():]
  156. @property
  157. def empty_form(self):
  158. form = self.form(
  159. auto_id=self.auto_id,
  160. prefix=self.add_prefix('__prefix__'),
  161. empty_permitted=True,
  162. )
  163. self.add_fields(form, None)
  164. return form
  165. @property
  166. def cleaned_data(self):
  167. """
  168. Returns a list of form.cleaned_data dicts for every form in self.forms.
  169. """
  170. if not self.is_valid():
  171. raise AttributeError("'%s' object has no attribute 'cleaned_data'" % self.__class__.__name__)
  172. return [form.cleaned_data for form in self.forms]
  173. @property
  174. def deleted_forms(self):
  175. """
  176. Returns a list of forms that have been marked for deletion.
  177. """
  178. if not self.is_valid() or not self.can_delete:
  179. return []
  180. # construct _deleted_form_indexes which is just a list of form indexes
  181. # that have had their deletion widget set to True
  182. if not hasattr(self, '_deleted_form_indexes'):
  183. self._deleted_form_indexes = []
  184. for i in range(0, self.total_form_count()):
  185. form = self.forms[i]
  186. # if this is an extra form and hasn't changed, don't consider it
  187. if i >= self.initial_form_count() and not form.has_changed():
  188. continue
  189. if self._should_delete_form(form):
  190. self._deleted_form_indexes.append(i)
  191. return [self.forms[i] for i in self._deleted_form_indexes]
  192. @property
  193. def ordered_forms(self):
  194. """
  195. Returns a list of form in the order specified by the incoming data.
  196. Raises an AttributeError if ordering is not allowed.
  197. """
  198. if not self.is_valid() or not self.can_order:
  199. raise AttributeError("'%s' object has no attribute 'ordered_forms'" % self.__class__.__name__)
  200. # Construct _ordering, which is a list of (form_index, order_field_value)
  201. # tuples. After constructing this list, we'll sort it by order_field_value
  202. # so we have a way to get to the form indexes in the order specified
  203. # by the form data.
  204. if not hasattr(self, '_ordering'):
  205. self._ordering = []
  206. for i in range(0, self.total_form_count()):
  207. form = self.forms[i]
  208. # if this is an extra form and hasn't changed, don't consider it
  209. if i >= self.initial_form_count() and not form.has_changed():
  210. continue
  211. # don't add data marked for deletion to self.ordered_data
  212. if self.can_delete and self._should_delete_form(form):
  213. continue
  214. self._ordering.append((i, form.cleaned_data[ORDERING_FIELD_NAME]))
  215. # After we're done populating self._ordering, sort it.
  216. # A sort function to order things numerically ascending, but
  217. # None should be sorted below anything else. Allowing None as
  218. # a comparison value makes it so we can leave ordering fields
  219. # blank.
  220. def compare_ordering_key(k):
  221. if k[1] is None:
  222. return (1, 0) # +infinity, larger than any number
  223. return (0, k[1])
  224. self._ordering.sort(key=compare_ordering_key)
  225. # Return a list of form.cleaned_data dicts in the order specified by
  226. # the form data.
  227. return [self.forms[i[0]] for i in self._ordering]
  228. @classmethod
  229. def get_default_prefix(cls):
  230. return 'form'
  231. def non_form_errors(self):
  232. """
  233. Returns an ErrorList of errors that aren't associated with a particular
  234. form -- i.e., from formset.clean(). Returns an empty ErrorList if there
  235. are none.
  236. """
  237. if self._non_form_errors is None:
  238. self.full_clean()
  239. return self._non_form_errors
  240. @property
  241. def errors(self):
  242. """
  243. Returns a list of form.errors for every form in self.forms.
  244. """
  245. if self._errors is None:
  246. self.full_clean()
  247. return self._errors
  248. def total_error_count(self):
  249. """
  250. Returns the number of errors across all forms in the formset.
  251. """
  252. return len(self.non_form_errors()) +\
  253. sum(len(form_errors) for form_errors in self.errors)
  254. def _should_delete_form(self, form):
  255. """
  256. Returns whether or not the form was marked for deletion.
  257. """
  258. return form.cleaned_data.get(DELETION_FIELD_NAME, False)
  259. def is_valid(self):
  260. """
  261. Returns True if every form in self.forms is valid.
  262. """
  263. if not self.is_bound:
  264. return False
  265. # We loop over every form.errors here rather than short circuiting on the
  266. # first failure to make sure validation gets triggered for every form.
  267. forms_valid = True
  268. # This triggers a full clean.
  269. self.errors
  270. for i in range(0, self.total_form_count()):
  271. form = self.forms[i]
  272. if self.can_delete:
  273. if self._should_delete_form(form):
  274. # This form is going to be deleted so any of its errors
  275. # should not cause the entire formset to be invalid.
  276. continue
  277. forms_valid &= form.is_valid()
  278. return forms_valid and not bool(self.non_form_errors())
  279. def full_clean(self):
  280. """
  281. Cleans all of self.data and populates self._errors and
  282. self._non_form_errors.
  283. """
  284. self._errors = []
  285. self._non_form_errors = self.error_class()
  286. if not self.is_bound: # Stop further processing.
  287. return
  288. for i in range(0, self.total_form_count()):
  289. form = self.forms[i]
  290. self._errors.append(form.errors)
  291. try:
  292. if (self.validate_max and
  293. self.total_form_count() - len(self.deleted_forms) > self.max_num) or \
  294. self.management_form.cleaned_data[TOTAL_FORM_COUNT] > self.absolute_max:
  295. raise ValidationError(ungettext(
  296. "Please submit %d or fewer forms.",
  297. "Please submit %d or fewer forms.", self.max_num) % self.max_num,
  298. code='too_many_forms',
  299. )
  300. if (self.validate_min and
  301. self.total_form_count() - len(self.deleted_forms) < self.min_num):
  302. raise ValidationError(ungettext(
  303. "Please submit %d or more forms.",
  304. "Please submit %d or more forms.", self.min_num) % self.min_num,
  305. code='too_few_forms')
  306. # Give self.clean() a chance to do cross-form validation.
  307. self.clean()
  308. except ValidationError as e:
  309. self._non_form_errors = self.error_class(e.error_list)
  310. def clean(self):
  311. """
  312. Hook for doing any extra formset-wide cleaning after Form.clean() has
  313. been called on every form. Any ValidationError raised by this method
  314. will not be associated with a particular form; it will be accessible
  315. via formset.non_form_errors()
  316. """
  317. pass
  318. def has_changed(self):
  319. """
  320. Returns true if data in any form differs from initial.
  321. """
  322. return any(form.has_changed() for form in self)
  323. def add_fields(self, form, index):
  324. """A hook for adding extra fields on to each form instance."""
  325. if self.can_order:
  326. # Only pre-fill the ordering field for initial forms.
  327. if index is not None and index < self.initial_form_count():
  328. form.fields[ORDERING_FIELD_NAME] = IntegerField(label=_('Order'), initial=index + 1, required=False)
  329. else:
  330. form.fields[ORDERING_FIELD_NAME] = IntegerField(label=_('Order'), required=False)
  331. if self.can_delete:
  332. form.fields[DELETION_FIELD_NAME] = BooleanField(label=_('Delete'), required=False)
  333. def add_prefix(self, index):
  334. return '%s-%s' % (self.prefix, index)
  335. def is_multipart(self):
  336. """
  337. Returns True if the formset needs to be multipart, i.e. it
  338. has FileInput. Otherwise, False.
  339. """
  340. if self.forms:
  341. return self.forms[0].is_multipart()
  342. else:
  343. return self.empty_form.is_multipart()
  344. @property
  345. def media(self):
  346. # All the forms on a FormSet are the same, so you only need to
  347. # interrogate the first form for media.
  348. if self.forms:
  349. return self.forms[0].media
  350. else:
  351. return self.empty_form.media
  352. def as_table(self):
  353. "Returns this formset rendered as HTML <tr>s -- excluding the <table></table>."
  354. # XXX: there is no semantic division between forms here, there
  355. # probably should be. It might make sense to render each form as a
  356. # table row with each field as a td.
  357. forms = ' '.join(form.as_table() for form in self)
  358. return mark_safe('\n'.join([six.text_type(self.management_form), forms]))
  359. def as_p(self):
  360. "Returns this formset rendered as HTML <p>s."
  361. forms = ' '.join(form.as_p() for form in self)
  362. return mark_safe('\n'.join([six.text_type(self.management_form), forms]))
  363. def as_ul(self):
  364. "Returns this formset rendered as HTML <li>s."
  365. forms = ' '.join(form.as_ul() for form in self)
  366. return mark_safe('\n'.join([six.text_type(self.management_form), forms]))
  367. def formset_factory(form, formset=BaseFormSet, extra=1, can_order=False,
  368. can_delete=False, max_num=None, validate_max=False,
  369. min_num=None, validate_min=False):
  370. """Return a FormSet for the given form class."""
  371. if min_num is None:
  372. min_num = DEFAULT_MIN_NUM
  373. if max_num is None:
  374. max_num = DEFAULT_MAX_NUM
  375. # hard limit on forms instantiated, to prevent memory-exhaustion attacks
  376. # limit is simply max_num + DEFAULT_MAX_NUM (which is 2*DEFAULT_MAX_NUM
  377. # if max_num is None in the first place)
  378. absolute_max = max_num + DEFAULT_MAX_NUM
  379. attrs = {'form': form, 'extra': extra,
  380. 'can_order': can_order, 'can_delete': can_delete,
  381. 'min_num': min_num, 'max_num': max_num,
  382. 'absolute_max': absolute_max, 'validate_min': validate_min,
  383. 'validate_max': validate_max}
  384. return type(form.__name__ + str('FormSet'), (formset,), attrs)
  385. def all_valid(formsets):
  386. """Returns true if every formset in formsets is valid."""
  387. valid = True
  388. for formset in formsets:
  389. if not formset.is_valid():
  390. valid = False
  391. return valid