Ticket #5353: 5353-BaseFormSet.clean_with_simple_tests.patch

File 5353-BaseFormSet.clean_with_simple_tests.patch, 9.9 KB (added by Honza Král, 17 years ago)

added some tests and synced with 6245

  • django/newforms/formsets.py

    commit 62fad9020ac67500d0b5f4fe026aa47def91a7ba
    Author: Honza Král <Honza.Kral@gmail.com>
    Date:   Sat Sep 15 01:18:31 2007 +0200
    
        http://code.djangoproject.com/ticket/5353
    
    diff --git a/django/newforms/formsets.py b/django/newforms/formsets.py
    index 2a0a131..3d05877 100644
    a b class BaseFormSet(object):  
    9393    forms = property(_forms)
    9494
    9595    def full_clean(self):
    96         """Cleans all of self.data and populates self.__errors and self.cleaned_data."""
     96        """Cleans all of self.data and populates self._is_valid and self.non_form_errors if applicable."""
    9797        is_valid = True
    98         errors = []
     98        self.non_form_errors = []
    9999        if not self.is_bound: # Stop further processing.
    100             self.__errors = errors
    101100            return
    102         cleaned_data = []
    103         deleted_data = []
    104101       
    105102        # Process change forms
    106103        for form in self.change_forms:
    107             if form.is_valid():
    108                 if self.deletable and form.cleaned_data[DELETION_FIELD_NAME]:
    109                     deleted_data.append(form.cleaned_data)
    110                 else:
    111                     cleaned_data.append(form.cleaned_data)
    112             else:
     104            if not form.is_valid():
    113105                is_valid = False
    114             errors.append(form.errors)
    115106       
    116107        # Process add forms in reverse so we can easily tell when the remaining
    117108        # ones should be required.
    118109        required = False
    119         add_errors = []
    120110        for i in range(len(self.add_forms)-1, -1, -1):
    121111            form = self.add_forms[i]
    122112            # If an add form is empty, reset it so it won't have any errors
    123113            if form.is_empty([ORDERING_FIELD_NAME]) and not required:
    124114                form.reset()
    125                 continue
    126115            else:
    127116                required = True
    128                 if form.is_valid():
    129                     cleaned_data.append(form.cleaned_data)
    130                 else:
     117                if not form.is_valid():
    131118                    is_valid = False
    132             add_errors.append(form.errors)
    133         add_errors.reverse()
    134         errors.extend(add_errors)
    135119
    136         if self.orderable:
    137             cleaned_data.sort(lambda x,y: x[ORDERING_FIELD_NAME] - y[ORDERING_FIELD_NAME])
     120        try:
     121            self.clean()
     122        except ValidationError, e:
     123            self.non_form_errors.extend(e.messages)
     124            is_valid = False
    138125
    139         if is_valid:
    140             self.cleaned_data = cleaned_data
    141             self.deleted_data = deleted_data
    142         self.errors = errors
    143126        self._is_valid = is_valid
    144127
     128    def forms_to_add(self):
     129        return [f for f in self.add_forms if f.is_bound]
     130    forms_to_add = property(forms_to_add)
     131
     132    def deleted_forms(self):
     133        """return all the forms schedulet for deletion"""
     134        if not self.is_valid() or not self.deletable:
     135            raise AttributeError, "%r object has no attribute 'deleted_forms'" % self.__class__.__name__
     136        if not hasattr( self, '_deleted_forms' ):
     137            self._deleted_forms = [f for f in self.change_forms if f.cleaned_data[DELETION_FIELD_NAME]]
     138        return self._deleted_forms
     139    deleted_forms = property(deleted_forms)
     140
     141    def sorted_forms(self):
     142        """return a list of sorted forms, when orderable"""
     143        if not self.is_valid() or not self.orderable:
     144            raise AttributeError, "%r object has no attribute 'sorted_forms'" % self.__class__.__name__
     145        if not hasattr(self, '_sorted_forms'):
     146            if self.deletable:
     147                self._sorted_forms = [f for f in self.change_forms if not f.cleaned_data[DELETION_FIELD_NAME]] + self.forms_to_add
     148            else:
     149                self._sorted_forms = self.change_forms + self.forms_to_add
     150            self._sorted_forms.sort(lambda x,y: x.cleaned_data[ORDERING_FIELD_NAME] - y.cleaned_data[ORDERING_FIELD_NAME])
     151        return self._sorted_forms
     152    sorted_forms = property(sorted_forms)
     153
     154    def has_errors(self):
     155        return not self.is_valid()
     156
     157    def clean(self):
     158        pass
     159
    145160    def add_fields(self, form, index):
    146161        """A hook for adding extra fields on to each form instance."""
    147162        if self.orderable:
  • tests/regressiontests/forms/formsets.py

    diff --git a/tests/regressiontests/forms/formsets.py b/tests/regressiontests/forms/formsets.py
    index 5129c15..0118dba 100644
    a b errors will be a list of dicts rather than just a single dict.  
    4545>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
    4646>>> formset.is_valid()
    4747True
    48 >>> formset.cleaned_data
     48>>> formset.sorted_forms
     49Traceback (most recent call last):
     50...
     51AttributeError: 'ChoiceFormSet' object has no attribute 'sorted_forms'
     52>>> formset.deleted_forms
     53Traceback (most recent call last):
     54...
     55AttributeError: 'ChoiceFormSet' object has no attribute 'deleted_forms'
     56>>> [f.cleaned_data for f in formset.forms_to_add  ]
    4957[{'votes': 100, 'choice': u'Calexico'}]
    5058
    5159If a FormSet was not passed any data, its is_valid method should return False.
    any of the forms.  
    6573>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
    6674>>> formset.is_valid()
    6775False
    68 >>> formset.errors
     76>>> [f.errors for f in formset.forms_to_add]
    6977[{'votes': [u'This field is required.']}]
    7078
    71 Like a Form instance, cleaned_data won't exist if the formset wasn't validated.
    72 
    73 >>> formset.cleaned_data
    74 Traceback (most recent call last):
    75 ...
    76 AttributeError: 'ChoiceFormSet' object has no attribute 'cleaned_data'
    77 
    78 
    7979We can also prefill a FormSet with existing data by providing an ``initial``
    8080argument to the constructor. ``initial`` should be a list of dicts. By default,
    8181an extra blank form is included.
    Let's simulate what would happen if we submitted this form.  
    103103>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
    104104>>> formset.is_valid()
    105105True
    106 >>> formset.cleaned_data
     106>>> [f.cleaned_data for f in formset.change_forms + formset.forms_to_add   ]
    107107[{'votes': 100, 'choice': u'Calexico'}]
    108108
    109109But the second form was blank! Shouldn't we get some errors? No. If we display
    handle that later.  
    123123>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
    124124>>> formset.is_valid()
    125125False
    126 >>> formset.errors
    127 [{}, {'votes': [u'This field is required.']}]
     126>>> [f.errors for f in formset.forms_to_add]
     127[{'votes': [u'This field is required.']}]
    128128
    129129
    130130If we delete data that was pre-filled, we should get an error. Simply removing
    handle that case later.  
    142142>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
    143143>>> formset.is_valid()
    144144False
    145 >>> formset.errors
     145>>> [f.errors for f in formset.change_forms]
    146146[{'votes': [u'This field is required.'], 'choice': [u'This field is required.']}]
    147147
    148148
    number of forms to be completed.  
    180180>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
    181181>>> formset.is_valid()
    182182True
    183 >>> formset.cleaned_data
     183>>> formset.forms_to_add
    184184[]
    185185
    186186
    We can just fill out one of the forms.  
    199199>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
    200200>>> formset.is_valid()
    201201True
    202 >>> formset.cleaned_data
     202>>> [f.cleaned_data for f in formset.forms_to_add]
    203203[{'votes': 100, 'choice': u'Calexico'}]
    204204
    205205
    And once again, if we try to partially complete a form, validation will fail.  
    218218>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
    219219>>> formset.is_valid()
    220220False
    221 >>> formset.errors
     221>>> [f.errors for f in formset.forms_to_add]
    222222[{}, {'votes': [u'This field is required.']}]
    223223
    224224
    get an error.  
    254254...     'choices-3-votes': '',
    255255... }
    256256
    257 >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
     257>>> formset = ChoiceFormSet(data, prefix='choices')
    258258>>> formset.is_valid()
    259259False
    260 >>> formset.errors
     260>>> [f.errors for f in formset.forms_to_add]
    261261[{}, {'votes': [u'This field is required.'], 'choice': [u'This field is required.']}, {}]
    262262
    263263
    To delete something, we just need to set that form's special delete field to  
    303303>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
    304304>>> formset.is_valid()
    305305True
    306 >>> formset.cleaned_data
     306>>> [f.cleaned_data for f in formset.change_forms if not f in formset.deleted_forms]
    307307[{'votes': 100, 'DELETE': False, 'choice': u'Calexico'}]
    308 >>> formset.deleted_data
     308>>> [f.cleaned_data for f in formset.deleted_forms]
    309309[{'votes': 900, 'DELETE': True, 'choice': u'Fergie'}]
    310310
    311311# FormSets with ordering ######################################################
    something at the front of the list, you'd need to set it's order to 0.  
    350350>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
    351351>>> formset.is_valid()
    352352True
    353 >>> for cleaned_data in formset.cleaned_data:
    354 ...    print cleaned_data
     353>>> for f in formset.sorted_forms:
     354...    print f.cleaned_data
    355355{'votes': 500, 'ORDER': 0, 'choice': u'The Decemberists'}
    356356{'votes': 100, 'ORDER': 1, 'choice': u'Calexico'}
    357357{'votes': 900, 'ORDER': 2, 'choice': u'Fergie'}
    Let's delete Fergie, and put The Decemberists ahead of Calexico.  
    412412>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
    413413>>> formset.is_valid()
    414414True
    415 >>> for cleaned_data in formset.cleaned_data:
    416 ...    print cleaned_data
     415>>> for f in formset.sorted_forms:
     416...    print f.cleaned_data
    417417{'votes': 500, 'DELETE': False, 'ORDER': 0, 'choice': u'The Decemberists'}
    418418{'votes': 100, 'DELETE': False, 'ORDER': 1, 'choice': u'Calexico'}
    419 >>> formset.deleted_data
     419>>> [f.cleaned_data for f in formset.deleted_forms]
    420420[{'votes': 900, 'DELETE': True, 'ORDER': 2, 'choice': u'Fergie'}]
    421421
    422422
     423
     424>>> def clean(self):
     425...     from django import newforms as forms
     426...     raise forms.ValidationError, 'XXX'
     427>>> setattr(ChoiceFormSet, 'clean', clean)
     428>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
     429>>> formset.is_valid()
     430False
     431>>> formset.non_form_errors
     432[u'XXX']
     433
    423434"""
Back to Top