web/lib/django/forms/formsets.py
changeset 29 cc9b7e14412b
parent 0 0d40e90630ef
equal deleted inserted replaced
28:b758351d191f 29:cc9b7e14412b
     1 from forms import Form
     1 from forms import Form
       
     2 from django.core.exceptions import ValidationError
     2 from django.utils.encoding import StrAndUnicode
     3 from django.utils.encoding import StrAndUnicode
     3 from django.utils.safestring import mark_safe
     4 from django.utils.safestring import mark_safe
     4 from django.utils.translation import ugettext as _
     5 from django.utils.translation import ugettext as _
     5 from fields import IntegerField, BooleanField
     6 from fields import IntegerField, BooleanField
     6 from widgets import Media, HiddenInput
     7 from widgets import Media, HiddenInput
     7 from util import ErrorList, ErrorDict, ValidationError
     8 from util import ErrorList
     8 
     9 
     9 __all__ = ('BaseFormSet', 'all_valid')
    10 __all__ = ('BaseFormSet', 'all_valid')
    10 
    11 
    11 # special field names
    12 # special field names
    12 TOTAL_FORM_COUNT = 'TOTAL_FORMS'
    13 TOTAL_FORM_COUNT = 'TOTAL_FORMS'
    13 INITIAL_FORM_COUNT = 'INITIAL_FORMS'
    14 INITIAL_FORM_COUNT = 'INITIAL_FORMS'
       
    15 MAX_NUM_FORM_COUNT = 'MAX_NUM_FORMS'
    14 ORDERING_FIELD_NAME = 'ORDER'
    16 ORDERING_FIELD_NAME = 'ORDER'
    15 DELETION_FIELD_NAME = 'DELETE'
    17 DELETION_FIELD_NAME = 'DELETE'
    16 
    18 
    17 class ManagementForm(Form):
    19 class ManagementForm(Form):
    18     """
    20     """
    21     increment the count field of this form as well.
    23     increment the count field of this form as well.
    22     """
    24     """
    23     def __init__(self, *args, **kwargs):
    25     def __init__(self, *args, **kwargs):
    24         self.base_fields[TOTAL_FORM_COUNT] = IntegerField(widget=HiddenInput)
    26         self.base_fields[TOTAL_FORM_COUNT] = IntegerField(widget=HiddenInput)
    25         self.base_fields[INITIAL_FORM_COUNT] = IntegerField(widget=HiddenInput)
    27         self.base_fields[INITIAL_FORM_COUNT] = IntegerField(widget=HiddenInput)
       
    28         self.base_fields[MAX_NUM_FORM_COUNT] = IntegerField(required=False, widget=HiddenInput)
    26         super(ManagementForm, self).__init__(*args, **kwargs)
    29         super(ManagementForm, self).__init__(*args, **kwargs)
    27 
    30 
    28 class BaseFormSet(StrAndUnicode):
    31 class BaseFormSet(StrAndUnicode):
    29     """
    32     """
    30     A collection of instances of the same Form class.
    33     A collection of instances of the same Form class.
    53             if not form.is_valid():
    56             if not form.is_valid():
    54                 raise ValidationError('ManagementForm data is missing or has been tampered with')
    57                 raise ValidationError('ManagementForm data is missing or has been tampered with')
    55         else:
    58         else:
    56             form = ManagementForm(auto_id=self.auto_id, prefix=self.prefix, initial={
    59             form = ManagementForm(auto_id=self.auto_id, prefix=self.prefix, initial={
    57                 TOTAL_FORM_COUNT: self.total_form_count(),
    60                 TOTAL_FORM_COUNT: self.total_form_count(),
    58                 INITIAL_FORM_COUNT: self.initial_form_count()
    61                 INITIAL_FORM_COUNT: self.initial_form_count(),
       
    62                 MAX_NUM_FORM_COUNT: self.max_num
    59             })
    63             })
    60         return form
    64         return form
    61     management_form = property(_management_form)
    65     management_form = property(_management_form)
    62 
    66 
    63     def total_form_count(self):
    67     def total_form_count(self):
    64         """Returns the total number of forms in this FormSet."""
    68         """Returns the total number of forms in this FormSet."""
    65         if self.data or self.files:
    69         if self.data or self.files:
    66             return self.management_form.cleaned_data[TOTAL_FORM_COUNT]
    70             return self.management_form.cleaned_data[TOTAL_FORM_COUNT]
    67         else:
    71         else:
    68             total_forms = self.initial_form_count() + self.extra
    72             initial_forms = self.initial_form_count()
    69             if total_forms > self.max_num > 0:
    73             total_forms = initial_forms + self.extra
       
    74             # Allow all existing related objects/inlines to be displayed,
       
    75             # but don't allow extra beyond max_num.
       
    76             if initial_forms > self.max_num >= 0:
       
    77                 total_forms = initial_forms
       
    78             elif total_forms > self.max_num >= 0:
    70                 total_forms = self.max_num
    79                 total_forms = self.max_num
    71         return total_forms
    80         return total_forms
    72 
    81 
    73     def initial_form_count(self):
    82     def initial_form_count(self):
    74         """Returns the number of forms that are required in this FormSet."""
    83         """Returns the number of forms that are required in this FormSet."""
    75         if self.data or self.files:
    84         if self.data or self.files:
    76             return self.management_form.cleaned_data[INITIAL_FORM_COUNT]
    85             return self.management_form.cleaned_data[INITIAL_FORM_COUNT]
    77         else:
    86         else:
    78             # Use the length of the inital data if it's there, 0 otherwise.
    87             # Use the length of the inital data if it's there, 0 otherwise.
    79             initial_forms = self.initial and len(self.initial) or 0
    88             initial_forms = self.initial and len(self.initial) or 0
    80             if initial_forms > self.max_num > 0:
    89             if initial_forms > self.max_num >= 0:
    81                 initial_forms = self.max_num
    90                 initial_forms = self.max_num
    82         return initial_forms
    91         return initial_forms
    83 
    92 
    84     def _construct_forms(self):
    93     def _construct_forms(self):
    85         # instantiate all the forms and put them in self.forms
    94         # instantiate all the forms and put them in self.forms
   116     def _get_extra_forms(self):
   125     def _get_extra_forms(self):
   117         """Return a list of all the extra forms in this formset."""
   126         """Return a list of all the extra forms in this formset."""
   118         return self.forms[self.initial_form_count():]
   127         return self.forms[self.initial_form_count():]
   119     extra_forms = property(_get_extra_forms)
   128     extra_forms = property(_get_extra_forms)
   120 
   129 
       
   130     def _get_empty_form(self, **kwargs):
       
   131         defaults = {
       
   132             'auto_id': self.auto_id,
       
   133             'prefix': self.add_prefix('__prefix__'),
       
   134             'empty_permitted': True,
       
   135         }
       
   136         if self.data or self.files:
       
   137             defaults['data'] = self.data
       
   138             defaults['files'] = self.files
       
   139         defaults.update(kwargs)
       
   140         form = self.form(**defaults)
       
   141         self.add_fields(form, None)
       
   142         return form
       
   143     empty_form = property(_get_empty_form)
       
   144 
   121     # Maybe this should just go away?
   145     # Maybe this should just go away?
   122     def _get_cleaned_data(self):
   146     def _get_cleaned_data(self):
   123         """
   147         """
   124         Returns a list of form.cleaned_data dicts for every form in self.forms.
   148         Returns a list of form.cleaned_data dicts for every form in self.forms.
   125         """
   149         """
   142             for i in range(0, self.total_form_count()):
   166             for i in range(0, self.total_form_count()):
   143                 form = self.forms[i]
   167                 form = self.forms[i]
   144                 # if this is an extra form and hasn't changed, don't consider it
   168                 # if this is an extra form and hasn't changed, don't consider it
   145                 if i >= self.initial_form_count() and not form.has_changed():
   169                 if i >= self.initial_form_count() and not form.has_changed():
   146                     continue
   170                     continue
   147                 if form.cleaned_data[DELETION_FIELD_NAME]:
   171                 if self._should_delete_form(form):
   148                     self._deleted_form_indexes.append(i)
   172                     self._deleted_form_indexes.append(i)
   149         return [self.forms[i] for i in self._deleted_form_indexes]
   173         return [self.forms[i] for i in self._deleted_form_indexes]
   150     deleted_forms = property(_get_deleted_forms)
   174     deleted_forms = property(_get_deleted_forms)
   151 
   175 
   152     def _get_ordered_forms(self):
   176     def _get_ordered_forms(self):
   166                 form = self.forms[i]
   190                 form = self.forms[i]
   167                 # if this is an extra form and hasn't changed, don't consider it
   191                 # if this is an extra form and hasn't changed, don't consider it
   168                 if i >= self.initial_form_count() and not form.has_changed():
   192                 if i >= self.initial_form_count() and not form.has_changed():
   169                     continue
   193                     continue
   170                 # don't add data marked for deletion to self.ordered_data
   194                 # don't add data marked for deletion to self.ordered_data
   171                 if self.can_delete and form.cleaned_data[DELETION_FIELD_NAME]:
   195                 if self.can_delete and self._should_delete_form(form):
   172                     continue
   196                     continue
   173                 self._ordering.append((i, form.cleaned_data[ORDERING_FIELD_NAME]))
   197                 self._ordering.append((i, form.cleaned_data[ORDERING_FIELD_NAME]))
   174             # After we're done populating self._ordering, sort it.
   198             # After we're done populating self._ordering, sort it.
   175             # A sort function to order things numerically ascending, but
   199             # A sort function to order things numerically ascending, but
   176             # None should be sorted below anything else. Allowing None as
   200             # None should be sorted below anything else. Allowing None as
   210         if self._errors is None:
   234         if self._errors is None:
   211             self.full_clean()
   235             self.full_clean()
   212         return self._errors
   236         return self._errors
   213     errors = property(_get_errors)
   237     errors = property(_get_errors)
   214 
   238 
       
   239     def _should_delete_form(self, form):
       
   240         # The way we lookup the value of the deletion field here takes
       
   241         # more code than we'd like, but the form's cleaned_data will
       
   242         # not exist if the form is invalid.
       
   243         field = form.fields[DELETION_FIELD_NAME]
       
   244         raw_value = form._raw_value(DELETION_FIELD_NAME)
       
   245         should_delete = field.clean(raw_value)
       
   246         return should_delete
       
   247 
   215     def is_valid(self):
   248     def is_valid(self):
   216         """
   249         """
   217         Returns True if form.errors is empty for every form in self.forms.
   250         Returns True if form.errors is empty for every form in self.forms.
   218         """
   251         """
   219         if not self.is_bound:
   252         if not self.is_bound:
   222         # first failure to make sure validation gets triggered for every form.
   255         # first failure to make sure validation gets triggered for every form.
   223         forms_valid = True
   256         forms_valid = True
   224         for i in range(0, self.total_form_count()):
   257         for i in range(0, self.total_form_count()):
   225             form = self.forms[i]
   258             form = self.forms[i]
   226             if self.can_delete:
   259             if self.can_delete:
   227                 # The way we lookup the value of the deletion field here takes
   260                 if self._should_delete_form(form):
   228                 # more code than we'd like, but the form's cleaned_data will
       
   229                 # not exist if the form is invalid.
       
   230                 field = form.fields[DELETION_FIELD_NAME]
       
   231                 raw_value = form._raw_value(DELETION_FIELD_NAME)
       
   232                 should_delete = field.clean(raw_value)
       
   233                 if should_delete:
       
   234                     # This form is going to be deleted so any of its errors
   261                     # This form is going to be deleted so any of its errors
   235                     # should not cause the entire formset to be invalid.
   262                     # should not cause the entire formset to be invalid.
   236                     continue
   263                     continue
   237             if bool(self.errors[i]):
   264             if bool(self.errors[i]):
   238                 forms_valid = False
   265                 forms_valid = False
   250             self._errors.append(form.errors)
   277             self._errors.append(form.errors)
   251         # Give self.clean() a chance to do cross-form validation.
   278         # Give self.clean() a chance to do cross-form validation.
   252         try:
   279         try:
   253             self.clean()
   280             self.clean()
   254         except ValidationError, e:
   281         except ValidationError, e:
   255             self._non_form_errors = e.messages
   282             self._non_form_errors = self.error_class(e.messages)
   256 
   283 
   257     def clean(self):
   284     def clean(self):
   258         """
   285         """
   259         Hook for doing any extra formset-wide cleaning after Form.clean() has
   286         Hook for doing any extra formset-wide cleaning after Form.clean() has
   260         been called on every form. Any ValidationError raised by this method
   287         been called on every form. Any ValidationError raised by this method
   265 
   292 
   266     def add_fields(self, form, index):
   293     def add_fields(self, form, index):
   267         """A hook for adding extra fields on to each form instance."""
   294         """A hook for adding extra fields on to each form instance."""
   268         if self.can_order:
   295         if self.can_order:
   269             # Only pre-fill the ordering field for initial forms.
   296             # Only pre-fill the ordering field for initial forms.
   270             if index < self.initial_form_count():
   297             if index is not None and index < self.initial_form_count():
   271                 form.fields[ORDERING_FIELD_NAME] = IntegerField(label=_(u'Order'), initial=index+1, required=False)
   298                 form.fields[ORDERING_FIELD_NAME] = IntegerField(label=_(u'Order'), initial=index+1, required=False)
   272             else:
   299             else:
   273                 form.fields[ORDERING_FIELD_NAME] = IntegerField(label=_(u'Order'), required=False)
   300                 form.fields[ORDERING_FIELD_NAME] = IntegerField(label=_(u'Order'), required=False)
   274         if self.can_delete:
   301         if self.can_delete:
   275             form.fields[DELETION_FIELD_NAME] = BooleanField(label=_(u'Delete'), required=False)
   302             form.fields[DELETION_FIELD_NAME] = BooleanField(label=_(u'Delete'), required=False)
   300         # table row with each field as a td.
   327         # table row with each field as a td.
   301         forms = u' '.join([form.as_table() for form in self.forms])
   328         forms = u' '.join([form.as_table() for form in self.forms])
   302         return mark_safe(u'\n'.join([unicode(self.management_form), forms]))
   329         return mark_safe(u'\n'.join([unicode(self.management_form), forms]))
   303 
   330 
   304 def formset_factory(form, formset=BaseFormSet, extra=1, can_order=False,
   331 def formset_factory(form, formset=BaseFormSet, extra=1, can_order=False,
   305                     can_delete=False, max_num=0):
   332                     can_delete=False, max_num=None):
   306     """Return a FormSet for the given form class."""
   333     """Return a FormSet for the given form class."""
   307     attrs = {'form': form, 'extra': extra,
   334     attrs = {'form': form, 'extra': extra,
   308              'can_order': can_order, 'can_delete': can_delete,
   335              'can_order': can_order, 'can_delete': can_delete,
   309              'max_num': max_num}
   336              'max_num': max_num}
   310     return type(form.__name__ + 'FormSet', (formset,), attrs)
   337     return type(form.__name__ + 'FormSet', (formset,), attrs)