Ticket #13878: 01-formset-refactoring.diff

File 01-formset-refactoring.diff, 8.9 KB (added by Petr Marhoun <petr.marhoun@…>, 9 years ago)
  • django/forms/formsets.py

    diff --git a/django/forms/formsets.py b/django/forms/formsets.py
    a b  
    4242        self.initial = initial
    4343        self.error_class = error_class
    4444        self._errors = None
    45         self._non_form_errors = None
    4645        # construct the forms in the formset
    4746        self._construct_forms()
    4847
     
    152151        return [form.cleaned_data for form in self.forms]
    153152    cleaned_data = property(_get_cleaned_data)
    154153
     154    def _get_valid_forms(self):
     155        """
     156        Returns a list of valid and filled forms, possibly in the order
     157        specified by the incoming data (if ordering is allowed).
     158        """
     159        if self._errors is None:
     160            self.full_clean()
     161        return self._valid_forms
     162    valid_forms = property(_get_valid_forms)
     163
    155164    def _get_deleted_forms(self):
    156165        """
    157166        Returns a list of forms that have been marked for deletion. Raises an
    158167        AttributeError if deletion is not allowed.
    159168        """
    160         if not self.is_valid() or not self.can_delete:
    161             raise AttributeError("'%s' object has no attribute 'deleted_forms'" % self.__class__.__name__)
    162         # construct _deleted_form_indexes which is just a list of form indexes
    163         # that have had their deletion widget set to True
    164         if not hasattr(self, '_deleted_form_indexes'):
    165             self._deleted_form_indexes = []
    166             for i in range(0, self.total_form_count()):
    167                 form = self.forms[i]
    168                 # if this is an extra form and hasn't changed, don't consider it
    169                 if i >= self.initial_form_count() and not form.has_changed():
    170                     continue
    171                 if self._should_delete_form(form):
    172                     self._deleted_form_indexes.append(i)
    173         return [self.forms[i] for i in self._deleted_form_indexes]
     169        if self._errors is None:
     170            self.full_clean()
     171        return self._deleted_forms
    174172    deleted_forms = property(_get_deleted_forms)
    175173
    176174    def _get_ordered_forms(self):
    177175        """
    178         Returns a list of form in the order specified by the incoming data.
     176        Returns a list of forms in the order specified by the incoming data.
    179177        Raises an AttributeError if ordering is not allowed.
    180178        """
    181         if not self.is_valid() or not self.can_order:
    182             raise AttributeError("'%s' object has no attribute 'ordered_forms'" % self.__class__.__name__)
    183         # Construct _ordering, which is a list of (form_index, order_field_value)
    184         # tuples. After constructing this list, we'll sort it by order_field_value
    185         # so we have a way to get to the form indexes in the order specified
    186         # by the form data.
    187         if not hasattr(self, '_ordering'):
    188             self._ordering = []
    189             for i in range(0, self.total_form_count()):
    190                 form = self.forms[i]
    191                 # if this is an extra form and hasn't changed, don't consider it
    192                 if i >= self.initial_form_count() and not form.has_changed():
    193                     continue
    194                 # don't add data marked for deletion to self.ordered_data
    195                 if self.can_delete and self._should_delete_form(form):
    196                     continue
    197                 self._ordering.append((i, form.cleaned_data[ORDERING_FIELD_NAME]))
    198             # After we're done populating self._ordering, sort it.
    199             # A sort function to order things numerically ascending, but
    200             # None should be sorted below anything else. Allowing None as
    201             # a comparison value makes it so we can leave ordering fields
    202             # blamk.
    203             def compare_ordering_values(x, y):
    204                 if x[1] is None:
    205                     return 1
    206                 if y[1] is None:
    207                     return -1
    208                 return x[1] - y[1]
    209             self._ordering.sort(compare_ordering_values)
    210         # Return a list of form.cleaned_data dicts in the order spcified by
    211         # the form data.
    212         return [self.forms[i[0]] for i in self._ordering]
     179        if self._errors is None:
     180            self.full_clean()
     181        return self._ordered_forms
    213182    ordered_forms = property(_get_ordered_forms)
    214183
    215184    #@classmethod
     
    223192        form -- i.e., from formset.clean(). Returns an empty ErrorList if there
    224193        are none.
    225194        """
    226         if self._non_form_errors is not None:
    227             return self._non_form_errors
    228         return self.error_class()
     195        if self._errors is None:
     196            self.full_clean()
     197        return self._non_form_errors
    229198
    230199    def _get_errors(self):
    231200        """
     
    247216
    248217    def is_valid(self):
    249218        """
    250         Returns True if form.errors is empty for every form in self.forms.
     219        Returns True if form is valid.
    251220        """
    252         if not self.is_bound:
    253             return False
    254         # We loop over every form.errors here rather than short circuiting on the
    255         # first failure to make sure validation gets triggered for every form.
    256         forms_valid = True
    257         for i in range(0, self.total_form_count()):
    258             form = self.forms[i]
    259             if self.can_delete:
    260                 if self._should_delete_form(form):
    261                     # This form is going to be deleted so any of its errors
    262                     # should not cause the entire formset to be invalid.
    263                     continue
    264             if bool(self.errors[i]):
    265                 forms_valid = False
    266         return forms_valid and not bool(self.non_form_errors())
     221        if self._errors is None:
     222            self.full_clean()
     223        return self._is_valid
    267224
    268225    def full_clean(self):
    269226        """
    270         Cleans all of self.data and populates self._errors.
     227        Cleans all forms, populates self._errors, self._non_form_errors and
     228        self._is_valid. If valid, populates also self._valid_forms,
     229        self._ordered_forms (if can_order is True) and self._deleted_forms
     230        (if can_delete is True).
    271231        """
    272232        self._errors = []
     233        self._non_form_errors = self.error_class()
    273234        if not self.is_bound: # Stop further processing.
     235            self._is_valid = False
    274236            return
    275         for i in range(0, self.total_form_count()):
     237        # Loop over every form here.
     238        self._is_valid = True
     239        valid_form_indexes, deleted_form_indexes = [], []
     240        for i in range(self.total_form_count()):
    276241            form = self.forms[i]
     242            # Add errors from form.
    277243            self._errors.append(form.errors)
     244            # Consider only forms that are initial or changed.
     245            if i < self.initial_form_count() or form.has_changed():
     246                if self.can_delete and self._should_delete_form(form):
     247                    # Add index of form that should be deleted.
     248                    deleted_form_indexes.append(i)
     249                else:
     250                    if form.is_valid():
     251                        if self.can_order:
     252                            # Add index and order field value of valid form.
     253                            valid_form_indexes.append((i, form.cleaned_data[ORDERING_FIELD_NAME]))
     254                        else:
     255                            # Add index of valid form.
     256                            valid_form_indexes.append(i)
     257                    else:
     258                        # Mark formset as not valid but do not stop to make
     259                        # sure validation gets triggered for every form.
     260                        self._is_valid = False
    278261        # Give self.clean() a chance to do cross-form validation.
    279262        try:
    280263            self.clean()
    281264        except ValidationError, e:
    282265            self._non_form_errors = self.error_class(e.messages)
     266            self._is_valid = False
     267        # If formset is valid, populates self._valid_forms,
     268        # self._ordered_forms (if can_order is True) and self._deleted_forms
     269        # (if can_delete is True).
     270        if self._is_valid:
     271            if self.can_order:
     272                # A sort function to order things numerically ascending, but
     273                # None should be sorted below anything else. Allowing None as
     274                # a comparison value makes it so we can leave ordering fields
     275                # blank.
     276                def compare_ordering_values(x, y):
     277                    if x[1] is None:
     278                        return 1
     279                    if y[1] is None:
     280                        return -1
     281                    return x[1] - y[1]
     282                valid_form_indexes.sort(compare_ordering_values)
     283                self._valid_forms = [self.forms[i[0]] for i in valid_form_indexes]
     284                self._ordered_forms = self._valid_forms
     285            else:
     286                self._valid_forms = [self.forms[i] for i in valid_form_indexes]
     287            if self.can_delete:
     288                self._deleted_forms = [self.forms[i] for i in deleted_form_indexes]
    283289
    284290    def clean(self):
    285291        """
Back to Top