Django

Code

Changeset 7270

Show
Ignore:
Timestamp:
03/17/08 12:55:16 (1 year ago)
Author:
jkocherhans
Message:

newforms-admin: Cleaned up the implementation and APIs of all the formset classes. Backwards-incompatible.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/branches/newforms-admin/django/contrib/admin/options.py

    r7195 r7270  
    22from django import newforms as forms 
    33from django.newforms.formsets import all_valid 
     4from django.newforms.models import _modelform_factory, _inlineformset_factory 
    45from django.contrib.contenttypes.models import ContentType 
    56from django.contrib.admin import widgets 
     
    341342        else: 
    342343            fields = None 
    343         return forms.form_for_model(self.model, fields=fields, formfield_callback=self.formfield_for_dbfield) 
     344        return _modelform_factory(self.model, fields=fields, formfield_callback=self.formfield_for_dbfield) 
    344345 
    345346    def form_change(self, request, obj): 
     
    351352        else: 
    352353            fields = None 
    353         return forms.form_for_instance(obj, fields=fields, formfield_callback=self.formfield_for_dbfield) 
     354        return _modelform_factory(self.model, fields=fields, formfield_callback=self.formfield_for_dbfield) 
    354355 
    355356    def save_add(self, request, model, form, formsets, post_url_continue): 
     
    497498            form = ModelForm(request.POST, request.FILES) 
    498499            for FormSet in self.formsets_add(request): 
    499                 inline_formset = FormSet(obj, data=request.POST, files=request.FILES
     500                inline_formset = FormSet(data=request.POST, files=request.FILES, instance=obj
    500501                inline_formsets.append(inline_formset) 
    501502            if all_valid(inline_formsets) and form.is_valid(): 
     
    504505            form = ModelForm(initial=request.GET) 
    505506            for FormSet in self.formsets_add(request): 
    506                 inline_formset = FormSet(obj) 
     507                inline_formset = FormSet(instance=obj) 
    507508                inline_formsets.append(inline_formset) 
    508509 
     
    554555        inline_formsets = [] 
    555556        if request.method == 'POST': 
    556             form = ModelForm(request.POST, request.FILES
     557            form = ModelForm(request.POST, request.FILES, instance=obj
    557558            for FormSet in self.formsets_change(request, obj): 
    558                 inline_formset = FormSet(obj, request.POST, request.FILES
     559                inline_formset = FormSet(request.POST, request.FILES, instance=obj
    559560                inline_formsets.append(inline_formset) 
    560561 
     
    562563                return self.save_change(request, model, form, inline_formsets) 
    563564        else: 
    564             form = ModelForm(
     565            form = ModelForm(instance=obj
    565566            for FormSet in self.formsets_change(request, obj): 
    566                 inline_formset = FormSet(obj) 
     567                inline_formset = FormSet(instance=obj) 
    567568                inline_formsets.append(inline_formset) 
    568569 
     
    741742        else: 
    742743            fields = None 
    743         return forms.inline_formset(self.parent_model, self.model, fk_name=self.fk_name, fields=fields, formfield_callback=self.formfield_for_dbfield, extra=self.extra) 
     744        return _inlineformset_factory(self.parent_model, self.model, fk_name=self.fk_name, fields=fields, formfield_callback=self.formfield_for_dbfield, extra=self.extra) 
    744745 
    745746    def formset_change(self, request, obj): 
     
    749750        else: 
    750751            fields = None 
    751         return forms.inline_formset(self.parent_model, self.model, fk_name=self.fk_name, fields=fields, formfield_callback=self.formfield_for_dbfield, extra=self.extra) 
     752        return _inlineformset_factory(self.parent_model, self.model, fk_name=self.fk_name, fields=fields, formfield_callback=self.formfield_for_dbfield, extra=self.extra) 
    752753 
    753754    def fieldsets_add(self, request): 
    754755        if self.declared_fieldsets: 
    755756            return self.declared_fieldsets 
    756         form = self.formset_add(request).form_class 
     757        form = self.formset_add(request).form 
    757758        return [(None, {'fields': form.base_fields.keys()})] 
    758759 
     
    760761        if self.declared_fieldsets: 
    761762            return self.declared_fieldsets 
    762         form = self.formset_change(request, obj).form_class 
     763        form = self.formset_change(request, obj).form 
    763764        return [(None, {'fields': form.base_fields.keys()})] 
    764765 
     
    779780 
    780781    def __iter__(self): 
    781         for form, original in zip(self.formset.change_forms, self.formset.get_queryset()): 
     782        for form, original in zip(self.formset.initial_forms, self.formset.get_queryset()): 
    782783            yield InlineAdminForm(self.formset, form, self.fieldsets, self.opts.prepopulated_fields, original) 
    783         for form in self.formset.add_forms: 
     784        for form in self.formset.extra_forms: 
    784785            yield InlineAdminForm(self.formset, form, self.fieldsets, self.opts.prepopulated_fields, None) 
    785786 
    786787    def fields(self): 
    787788        for field_name in flatten_fieldsets(self.fieldsets): 
    788             yield self.formset.form_class.base_fields[field_name] 
     789            yield self.formset.form.base_fields[field_name] 
    789790 
    790791class InlineAdminForm(AdminForm): 
  • django/branches/newforms-admin/django/newforms/formsets.py

    r6419 r7270  
    11from forms import Form 
     2from django.utils.encoding import StrAndUnicode 
    23from fields import IntegerField, BooleanField 
    3 from widgets import HiddenInput, Media 
     4from widgets import HiddenInput, TextInput 
    45from util import ErrorList, ValidationError 
    56 
    6 __all__ = ('BaseFormSet', 'formset_for_form', 'all_valid') 
     7__all__ = ('BaseFormSet', 'all_valid') 
    78 
    89# special field names 
    9 FORM_COUNT_FIELD_NAME = 'COUNT' 
     10TOTAL_FORM_COUNT = 'TOTAL_FORMS' 
     11INITIAL_FORM_COUNT = 'INITIAL_FORMS' 
    1012ORDERING_FIELD_NAME = 'ORDER' 
    1113DELETION_FIELD_NAME = 'DELETE' 
     
    1820    """ 
    1921    def __init__(self, *args, **kwargs): 
    20         self.base_fields[FORM_COUNT_FIELD_NAME] = IntegerField(widget=HiddenInput) 
     22        self.base_fields[TOTAL_FORM_COUNT] = IntegerField(widget=HiddenInput) 
     23        self.base_fields[INITIAL_FORM_COUNT] = IntegerField(widget=HiddenInput) 
    2124        super(ManagementForm, self).__init__(*args, **kwargs) 
    2225 
    23 class BaseFormSet(object): 
    24     """A collection of instances of the same Form class.""" 
    25  
    26     def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,  
     26class BaseFormSet(StrAndUnicode): 
     27    """ 
     28    A collection of instances of the same Form class. 
     29    """ 
     30    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, 
    2731            initial=None, error_class=ErrorList): 
    2832        self.is_bound = data is not None or files is not None 
     
    3337        self.initial = initial 
    3438        self.error_class = error_class 
     39        self._errors = None 
     40        self._non_form_errors = None 
    3541        # initialization is different depending on whether we recieved data, initial, or nothing 
    3642        if data or files: 
    3743            self.management_form = ManagementForm(data, files, auto_id=self.auto_id, prefix=self.prefix) 
    3844            if self.management_form.is_valid(): 
    39                 self.total_forms = self.management_form.cleaned_data[FORM_COUNT_FIELD_NAME] 
    40                 self.required_forms = self.total_forms - self.num_extra 
    41                 self.change_form_count = self.total_forms - self.num_extra 
     45                self._total_form_count = self.management_form.cleaned_data[TOTAL_FORM_COUNT] 
     46                self._initial_form_count = self.management_form.cleaned_data[INITIAL_FORM_COUNT] 
    4247            else: 
    43                 # not sure that ValidationError is the best thing to raise here 
    4448                raise ValidationError('ManagementForm data is missing or has been tampered with') 
    4549        elif initial: 
    46             self.change_form_count = len(initial) 
    47             self.required_forms = len(initial) 
    48             self.total_forms = self.required_forms + self.num_extra 
    49             self.management_form = ManagementForm(initial={FORM_COUNT_FIELD_NAME: self.total_forms}, auto_id=self.auto_id, prefix=self.prefix) 
     50            self._initial_form_count = len(initial) 
     51            self._total_form_count = self._initial_form_count + self.extra 
    5052        else: 
    51             self.change_form_count = 0 
    52             self.required_forms = 0 
    53             self.total_forms = self.num_extra 
    54             self.management_form = ManagementForm(initial={FORM_COUNT_FIELD_NAME: self.total_forms}, auto_id=self.auto_id, prefix=self.prefix) 
    55  
    56     def _get_add_forms(self): 
    57         """Return a list of all the add forms in this ``FormSet``.""" 
    58         FormClass = self.form_class 
    59         if not hasattr(self, '_add_forms'): 
    60             add_forms = [] 
    61             for i in range(self.change_form_count, self.total_forms): 
    62                 kwargs = {'auto_id': self.auto_id, 'prefix': self.add_prefix(i)} 
    63                 if self.data: 
    64                     kwargs['data'] = self.data 
    65                 if self.files: 
    66                     kwargs['files'] = self.files 
    67                 add_form = FormClass(**kwargs) 
    68                 self.add_fields(add_form, i) 
    69                 add_forms.append(add_form) 
    70             self._add_forms = add_forms 
    71         return self._add_forms 
    72     add_forms = property(_get_add_forms) 
    73  
    74     def _get_change_forms(self): 
    75         """Return a list of all the change forms in this ``FormSet``.""" 
    76         FormClass = self.form_class 
    77         if not hasattr(self, '_change_forms'): 
    78             change_forms = [] 
    79             for i in range(0, self.change_form_count): 
    80                 kwargs = {'auto_id': self.auto_id, 'prefix': self.add_prefix(i)} 
    81                 if self.data: 
    82                     kwargs['data'] = self.data 
    83                 if self.files: 
    84                     kwargs['files'] = self.files 
    85                 if self.initial: 
    86                     kwargs['initial'] = self.initial[i] 
    87                 change_form = FormClass(**kwargs) 
    88                 self.add_fields(change_form, i) 
    89                 change_forms.append(change_form) 
    90             self._change_forms= change_forms 
    91         return self._change_forms 
    92     change_forms = property(_get_change_forms) 
    93  
    94     def _forms(self): 
    95         return self.change_forms + self.add_forms 
    96     forms = property(_forms) 
     53            self._initial_form_count = 0 
     54            self._total_form_count = self.extra 
     55        initial = {TOTAL_FORM_COUNT: self._total_form_count, INITIAL_FORM_COUNT: self._initial_form_count} 
     56        self.management_form = ManagementForm(initial=initial, auto_id=auto_id, prefix=prefix) 
     57 
     58        # instantiate all the forms and put them in self.forms 
     59        self.forms = [] 
     60        for i in range(self._total_form_count): 
     61            self.forms.append(self._construct_form(i)) 
     62 
     63    def __unicode__(self): 
     64        return self.as_table() 
     65 
     66    def _construct_form(self, i): 
     67        """ 
     68        Instantiates and returns the i-th form instance in a formset. 
     69        """ 
     70        kwargs = {'auto_id': self.auto_id, 'prefix': self.add_prefix(i)} 
     71        if self.data or self.files: 
     72            kwargs['data'] = self.data 
     73            kwargs['files'] = self.files 
     74        if self.initial: 
     75            try: 
     76                kwargs['initial'] = self.initial[i] 
     77            except IndexError: 
     78                pass 
     79        # Allow extra forms to be empty. 
     80        if i >= self._initial_form_count: 
     81            kwargs['empty_permitted'] = True 
     82        form = self.form(**kwargs) 
     83        self.add_fields(form, i) 
     84        return form 
     85 
     86    def _get_initial_forms(self): 
     87        """Return a list of all the intial forms in this formset.""" 
     88        return self.forms[:self._initial_form_count] 
     89    initial_forms = property(_get_initial_forms) 
     90 
     91    def _get_extra_forms(self): 
     92        """Return a list of all the extra forms in this formset.""" 
     93        return self.forms[self._initial_form_count:] 
     94    extra_forms = property(_get_extra_forms) 
     95 
     96    # Maybe this should just go away? 
     97    def _get_cleaned_data(self): 
     98        """ 
     99        Returns a list of form.cleaned_data dicts for every form in self.forms. 
     100        """ 
     101        if not self.is_valid(): 
     102            raise AttributeError("'%s' object has no attribute 'cleaned_data'" % self.__class__.__name__) 
     103        return [form.cleaned_data for form in self.forms] 
     104    cleaned_data = property(_get_cleaned_data) 
     105 
     106    def _get_deleted_forms(self): 
     107        """ 
     108        Returns a list of forms that have been marked for deletion. Raises an  
     109        AttributeError is deletion is not allowed. 
     110        """ 
     111        if not self.is_valid() or not self.can_delete: 
     112            raise AttributeError("'%s' object has no attribute 'deleted_forms'" % self.__class__.__name__) 
     113        # construct _deleted_form_indexes which is just a list of form indexes 
     114        # that have had their deletion widget set to True 
     115        if not hasattr(self, '_deleted_form_indexes'): 
     116            self._deleted_form_indexes = [] 
     117            for i in range(0, self._total_form_count): 
     118                form = self.forms[i] 
     119                # if this is an extra form and hasn't changed, don't consider it 
     120                if i >= self._initial_form_count and not form.has_changed(): 
     121                    continue 
     122                if form.cleaned_data[DELETION_FIELD_NAME]: 
     123                    self._deleted_form_indexes.append(i) 
     124        return [self.forms[i] for i in self._deleted_form_indexes] 
     125    deleted_forms = property(_get_deleted_forms) 
     126 
     127    def _get_ordered_forms(self): 
     128        """ 
     129        Returns a list of form in the order specified by the incoming data. 
     130        Raises an AttributeError is deletion is not allowed. 
     131        """ 
     132        if not self.is_valid() or not self.can_order: 
     133            raise AttributeError("'%s' object has no attribute 'ordered_forms'" % self.__class__.__name__) 
     134        # Construct _ordering, which is a list of (form_index, order_field_value) 
     135        # tuples. After constructing this list, we'll sort it by order_field_value 
     136        # so we have a way to get to the form indexes in the order specified 
     137        # by the form data. 
     138        if not hasattr(self, '_ordering'): 
     139            self._ordering = [] 
     140            for i in range(0, self._total_form_count): 
     141                form = self.forms[i] 
     142                # if this is an extra form and hasn't changed, don't consider it 
     143                if i >= self._initial_form_count and not form.has_changed(): 
     144                    continue 
     145                # don't add data marked for deletion to self.ordered_data 
     146                if self.can_delete and form.cleaned_data[DELETION_FIELD_NAME]: 
     147                    continue 
     148                # A sort function to order things numerically ascending, but 
     149                # None should be sorted below anything else. Allowing None as 
     150                # a comparison value makes it so we can leave ordering fields 
     151                # blamk. 
     152                def compare_ordering_values(x, y): 
     153                    if x[1] is None: 
     154                        return 1 
     155                    if y[1] is None: 
     156                        return -1 
     157                    return x[1] - y[1] 
     158                self._ordering.append((i, form.cleaned_data[ORDERING_FIELD_NAME])) 
     159            # After we're done populating self._ordering, sort it. 
     160            self._ordering.sort(compare_ordering_values) 
     161        # Return a list of form.cleaned_data dicts in the order spcified by 
     162        # the form data. 
     163        return [self.forms[i[0]] for i in self._ordering] 
     164    ordered_forms = property(_get_ordered_forms) 
    97165 
    98166    def non_form_errors(self): 
     
    102170        are none. 
    103171        """ 
    104         if hasattr(self, '_non_form_errors')
     172        if self._non_form_errors is not None
    105173            return self._non_form_errors 
    106174        return self.error_class() 
    107175 
     176    def _get_errors(self): 
     177        """ 
     178        Returns a list of form.errors for every form in self.forms. 
     179        """ 
     180        if self._errors is None: 
     181            self.full_clean() 
     182        return self._errors 
     183    errors = property(_get_errors) 
     184 
     185    def is_valid(self): 
     186        """ 
     187        Returns True if form.errors is empty for every form in self.forms. 
     188        """ 
     189        if not self.is_bound: 
     190            return False 
     191        # We loop over every form.errors here rather than short circuiting on the 
     192        # first failure to make sure validation gets triggered for every form. 
     193        forms_valid = True 
     194        for errors in self.errors: 
     195            if bool(errors): 
     196                forms_valid = False 
     197        return forms_valid and not bool(self.non_form_errors()) 
     198 
    108199    def full_clean(self): 
    109         """Cleans all of self.data and populates self.__errors and self.cleaned_data.""" 
    110         self._is_valid = True # Assume the formset is valid until proven otherwise. 
    111         errors = [] 
     200        """ 
     201        Cleans all of self.data and populates self._errors. 
     202        """ 
     203        self._errors = [] 
    112204        if not self.is_bound: # Stop further processing. 
    113             self.__errors = errors 
    114205            return 
    115         self.cleaned_data = [] 
    116         self.deleted_data = [] 
    117         # Process change forms 
    118         for form in self.change_forms: 
    119             if form.is_valid(): 
    120                 if self.deletable and form.cleaned_data[DELETION_FIELD_NAME]: 
    121                     self.deleted_data.append(form.cleaned_data) 
    122                 else: 
    123                     self.cleaned_data.append(form.cleaned_data) 
    124             else: 
    125                 self._is_valid = False 
    126             errors.append(form.errors) 
    127         # Process add forms in reverse so we can easily tell when the remaining 
    128         # ones should be required. 
    129         reamining_forms_required = False 
    130         add_errors = [] 
    131         for i in range(len(self.add_forms)-1, -1, -1): 
    132             form = self.add_forms[i] 
    133             # If an add form is empty, reset it so it won't have any errors 
    134             if form.is_empty([ORDERING_FIELD_NAME]) and not reamining_forms_required: 
    135                 form.reset() 
    136                 continue 
    137             else: 
    138                 reamining_forms_required = True 
    139                 if form.is_valid(): 
    140                     self.cleaned_data.append(form.cleaned_data) 
    141                 else: 
    142                     self._is_valid = False 
    143             add_errors.append(form.errors) 
    144         add_errors.reverse() 
    145         errors.extend(add_errors) 
    146         # Sort cleaned_data if the formset is orderable. 
    147         if self.orderable: 
    148             self.cleaned_data.sort(lambda x,y: x[ORDERING_FIELD_NAME] - y[ORDERING_FIELD_NAME]) 
    149         # Give self.clean() a chance to do validation 
     206        for i in range(0, self._total_form_count): 
     207            form = self.forms[i] 
     208            self._errors.append(form.errors) 
     209        # Give self.clean() a chance to do cross-form validation. 
    150210        try: 
    151             self.cleaned_data = self.clean() 
     211            self.clean() 
    152212        except ValidationError, e: 
    153213            self._non_form_errors = e.messages 
    154             self._is_valid = False 
    155         self.errors = errors 
    156         # If there were errors, be consistent with forms and remove the 
    157         # cleaned_data and deleted_data attributes. 
    158         if not self._is_valid: 
    159             delattr(self, 'cleaned_data') 
    160             delattr(self, 'deleted_data') 
    161214 
    162215    def clean(self): 
     
    167220        via formset.non_form_errors() 
    168221        """ 
    169         return self.cleaned_data 
     222        pass 
    170223 
    171224    def add_fields(self, form, index): 
    172225        """A hook for adding extra fields on to each form instance.""" 
    173         if self.orderable: 
    174             form.fields[ORDERING_FIELD_NAME] = IntegerField(label='Order', initial=index+1) 
    175         if self.deletable: 
     226        if self.can_order: 
     227            # Only pre-fill the ordering field for initial forms. 
     228            if index < self._initial_form_count: 
     229                form.fields[ORDERING_FIELD_NAME] = IntegerField(label='Order', initial=index+1, required=False) 
     230            else: 
     231                form.fields[ORDERING_FIELD_NAME] = IntegerField(label='Order', required=False) 
     232        if self.can_delete: 
    176233            form.fields[DELETION_FIELD_NAME] = BooleanField(label='Delete', required=False) 
    177234 
     
    179236        return '%s-%s' % (self.prefix, index) 
    180237 
    181     def is_valid(self): 
    182         if not self.is_bound: 
    183             return False 
    184         self.full_clean() 
    185         return self._is_valid 
     238    def is_multipart(self): 
     239        """ 
     240        Returns True if the formset needs to be multipart-encrypted, i.e. it 
     241        has FileInput. Otherwise, False. 
     242        """ 
     243        return self.forms[0].is_multipart() 
    186244 
    187245    def _get_media(self): 
    188         # All the forms on a FormSet are the same, so you only need to  
     246        # All the forms on a FormSet are the same, so you only need to 
    189247        # interrogate the first form for media. 
    190248        if self.forms: 
     
    193251            return Media() 
    194252    media = property(_get_media) 
    195      
    196 def formset_for_form(form, formset=BaseFormSet, num_extra=1, orderable=False, deletable=False): 
     253 
     254    def as_table(self): 
     255        "Returns this formset rendered as HTML <tr>s -- excluding the <table></table>." 
     256        # XXX: there is no semantic division between forms here, there 
     257        # probably should be. It might make sense to render each form as a 
     258        # table row with each field as a td. 
     259        forms = u' '.join([form.as_table() for form in self.forms]) 
     260        return u'\n'.join([unicode(self.management_form), forms]) 
     261 
     262# XXX: This API *will* change. Use at your own risk. 
     263def _formset_factory(form, formset=BaseFormSet, extra=1, can_order=False, can_delete=False): 
    197264    """Return a FormSet for the given form class.""" 
    198     attrs = {'form_class': form, 'num_extra': num_extra, 'orderable': orderable, 'deletable': deletable} 
     265    attrs = {'form': form, 'extra': extra, 'can_order': can_order, 'can_delete': can_delete} 
    199266    return type(form.__name__ + 'FormSet', (formset,), attrs) 
    200267 
  • django/branches/newforms-admin/django/newforms/forms.py

    r7233 r7270  
    7070    # class, not to the Form class. 
    7171    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, 
    72                  initial=None, error_class=ErrorList, label_suffix=':'): 
     72                 initial=None, error_class=ErrorList, label_suffix=':', 
     73                 empty_permitted=False): 
    7374        self.is_bound = data is not None or files is not None 
    7475        self.data = data or {} 
     
    7980        self.error_class = error_class 
    8081        self.label_suffix = label_suffix 
     82        self.empty_permitted = empty_permitted 
    8183        self._errors = None # Stores the errors after clean() has been called. 
    8284 
     
    190192        return self.errors.get(NON_FIELD_ERRORS, self.error_class()) 
    191193 
    192     def is_empty(self, exceptions=None): 
    193         """ 
    194         Returns True if this form has been bound and all fields that aren't 
    195         listed in exceptions are empty. 
    196         """ 
    197         # TODO: This could probably use some optimization 
    198         exceptions = exceptions or [] 
    199         for name, field in self.fields.items(): 
    200             if name in exceptions: 
    201                 continue 
    202             # value_from_datadict() gets the data from the data dictionaries. 
    203             # Each widget type knows how to retrieve its own data, because some 
    204             # widgets split data over several HTML fields. 
    205             value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name)) 
    206             if not field.widget.is_empty(value): 
    207                 return False 
    208         return True 
    209  
    210194    def full_clean(self): 
    211195        """ 
     
    217201            return 
    218202        self.cleaned_data = {} 
     203        # If the form is permitted to be empty, and none of the form data has 
     204        # changed from the initial data, short circuit any validation. 
     205        if self.empty_permitted and not self.has_changed(): 
     206            return 
    219207        for name, field in self.fields.items(): 
    220208            # value_from_datadict() gets the data from the data dictionaries. 
     
    252240        return self.cleaned_data 
    253241 
    254     def reset(self): 
    255         """Return this form to the state it was in before data was passed to it.""" 
    256         self.data = {} 
    257         self.is_bound = False 
    258         self.__errors = None 
     242    def has_changed(self): 
     243        """ 
     244        Returns True if data differs from initial. 
     245        """ 
     246        # XXX: For now we're asking the individual widgets whether or not the 
     247        # data has changed. It would probably be more efficient to hash the 
     248        # initial data, store it in a hidden field, and compare a hash of the 
     249        # submitted data, but we'd need a way to easily get the string value 
     250        # for a given field. Right now, that logic is embedded in the render 
     251        # method of each widget. 
     252        for name, field in self.fields.items(): 
     253            prefixed_name = self.add_prefix(name) 
     254            data_value = field.widget.value_from_datadict(self.data, self.files, prefixed_name) 
     255            initial_value = self.initial.get(name, field.initial) 
     256            if field.widget._has_changed(initial_value, data_value): 
     257                #print field 
     258                return True 
     259        return False 
    259260 
    260261    def _get_media(self): 
  • django/branches/newforms-admin/django/newforms/models.py

    r7121 r7270  
    1414from forms import BaseForm, get_declared_fields 
    1515from fields import Field, ChoiceField, IntegerField, EMPTY_VALUES 
    16 from formsets import BaseFormSet, formset_for_form, DELETION_FIELD_NAME 
    1716from widgets import Select, SelectMultiple, HiddenInput, MultipleHiddenInput 
     17from formsets import BaseFormSet, _formset_factory, DELETION_FIELD_NAME 
    1818 
    1919__all__ = ( 
    2020    'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model', 
    2121    'save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields', 
    22     'formset_for_model', 'inline_formset', 
    2322    'ModelChoiceField', 'ModelMultipleChoiceField', 
    2423) 
     
    246245    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, 
    247246                 initial=None, error_class=ErrorList, label_suffix=':', 
    248                  instance=None): 
     247                 empty_permitted=False, instance=None): 
    249248        opts = self._meta 
    250249        if instance is None: 
     
    258257        if initial is not None: 
    259258            object_data.update(initial) 
    260         BaseForm.__init__(self, data, files, auto_id, prefix, object_data, error_class, label_suffix) 
     259        BaseForm.__init__(self, data, files, auto_id, prefix, object_data, 
     260                          error_class, label_suffix, empty_permitted) 
    261261 
    262262    def save(self, commit=True): 
     
    277277    __metaclass__ = ModelFormMetaclass 
    278278 
    279  
    280 # Fields ##################################################################### 
    281  
    282 class QuerySetIterator(object): 
    283     def __init__(self, queryset, empty_label, cache_choices): 
     279# XXX: This API *will* change. Use at your own risk. 
     280def _modelform_factory(model, form=BaseForm, fields=None, exclude=None, 
     281                       formfield_callback=lambda f: f.formfield()): 
     282    # HACK: we should be able to construct a ModelForm without creating 
     283    # and passing in a temporary inner class 
     284    class Meta: 
     285        pass 
     286    setattr(Meta, 'model', model) 
     287    setattr(Meta, 'fields', fields) 
     288    setattr(Meta, 'exclude', exclude) 
     289    class_name = model.__name__ + 'Form' 
     290    return ModelFormMetaclass(class_name, (ModelForm,), {'Meta': Meta}, 
     291                              formfield_callback=formfield_callback) 
     292 
     293 
     294# ModelFormSets ############################################################## 
     295 
     296class BaseModelFormSet(BaseFormSet): 
     297    """ 
     298    A ``FormSet`` for editing a queryset and/or adding new objects to it. 
     299    """ 
     300    model = None 
     301 
     302    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, queryset=None): 
    284303        self.queryset = queryset 
    285         self.empty_label = empty_label 
    286         self.cache_choices = cache_choices 
    287  
    288     def __iter__(self): 
    289         if self.empty_label is not None: 
    290             yield (u"", self.empty_label) 
    291         for obj in self.queryset: 
    292             yield (obj.pk, smart_unicode(obj)) 
    293         # Clear the QuerySet cache if required. 
    294         if not self.cache_choices: 
    295             self.queryset._result_cache = None 
    296  
    297 class ModelChoiceField(ChoiceField): 
    298     """A ChoiceField whose choices are a model QuerySet.""" 
    299     # This class is a subclass of ChoiceField for purity, but it doesn't 
    300     # actually use any of ChoiceField's implementation. 
    301     default_error_messages = { 
    302         'invalid_choice': _(u'Select a valid choice. That choice is not one of' 
    303                             u' the available choices.'), 
    304     } 
    305  
    306     def __init__(self, queryset, empty_label=u"---------", cache_choices=False, 
    307                  required=True, widget=Select, label=None, initial=None, 
    308                  help_text=None, *args, **kwargs): 
    309         self.empty_label = empty_label 
    310         self.cache_choices = cache_choices 
    311         # Call Field instead of ChoiceField __init__() because we don't need 
    312         # ChoiceField.__init__(). 
    313         Field.__init__(self, required, widget, label, initial, help_text, 
    314                        *args, **kwargs) 
    315         self.queryset = queryset 
    316  
    317     def _get_queryset(self): 
    318         return self._queryset 
    319  
    320     def _set_queryset(self, queryset): 
    321         self._queryset = queryset 
    322         self.widget.choices = self.choices 
    323  
    324     queryset = property(_get_queryset, _set_queryset) 
    325  
    326     def _get_choices(self): 
    327         # If self._choices is set, then somebody must have manually set 
    328         # the property self.choices. In this case, just return self._choices. 
    329         if hasattr(self, '_choices'): 
    330             return self._choices 
    331         # Otherwise, execute the QuerySet in self.queryset to determine the 
    332         # choices dynamically. Return a fresh QuerySetIterator that has not 
    333         # been consumed. Note that we're instantiating a new QuerySetIterator 
    334         # *each* time _get_choices() is called (and, thus, each time 
    335         # self.choices is accessed) so that we can ensure the QuerySet has not 
    336         # been consumed. 
    337         return QuerySetIterator(self.queryset, self.empty_label, 
    338                                 self.cache_choices) 
    339  
    340     def _set_choices(self, value): 
    341         # This method is copied from ChoiceField._set_choices(). It's necessary 
    342         # because property() doesn't allow a subclass to overwrite only 
    343         # _get_choices without implementing _set_choices. 
    344         self._choices = self.widget.choices = list(value) 
    345  
    346     choices = property(_get_choices, _set_choices) 
    347  
    348     def clean(self, value): 
    349         Field.clean(self, value) 
    350         if value in EMPTY_VALUES: 
    351             return None 
    352         try: 
    353             value = self.queryset.get(pk=value) 
    354         except self.queryset.model.DoesNotExist: 
    355             raise ValidationError(self.error_messages['invalid_choice']) 
    356         return value 
    357  
    358 class ModelMultipleChoiceField(ModelChoiceField): 
    359     """A MultipleChoiceField whose choices are a model QuerySet.""" 
    360     hidden_widget = MultipleHiddenInput 
    361     default_error_messages = { 
    362         'list': _(u'Enter a list of values.'), 
    363         'invalid_choice': _(u'Select a valid choice. %s is not one of the' 
    364                             u' available choices.'), 
    365     } 
    366  
    367     def __init__(self, queryset, cache_choices=False, required=True, 
    368                  widget=SelectMultiple, label=None, initial=None, 
    369                  help_text=None, *args, **kwargs): 
    370         super(ModelMultipleChoiceField, self).__init__(queryset, None, 
    371             cache_choices, required, widget, label, initial, help_text, 
    372             *args, **kwargs) 
    373  
    374     def clean(self, value): 
    375         if self.required and not value: 
    376             raise ValidationError(self.error_messages['required']) 
    377         elif not self.required and not value: 
    378             return [] 
    379         if not isinstance(value, (list, tuple)): 
    380             raise ValidationError(self.error_messages['list']) 
    381         final_values = [] 
    382         for val in value: 
    383             try: 
    384                 obj = self.queryset.get(pk=val) 
    385             except self.queryset.model.DoesNotExist: 
    386                 raise ValidationError(self.error_messages['invalid_choice'] % val) 
    387             else: 
    388                 final_values.append(obj) 
    389         return final_values 
    390  
    391 # Model-FormSet integration ################################################### 
    392  
    393 def initial_data(instance, fields=None): 
    394     """ 
    395     Return a dictionary from data in ``instance`` that is suitable for 
    396     use as a ``Form`` constructor's ``initial`` argument. 
    397  
    398     Provide ``fields`` to specify the names of specific fields to return. 
    399     All field values in the instance will be returned if ``fields`` is not 
    400     provided. 
    401     """ 
    402     # avoid a circular import 
    403     from django.db.models.fields.related import ManyToManyField 
    404     opts = instance._meta 
    405     initial = {} 
    406     for f in opts.fields + opts.many_to_many: 
    407         if not f.editable: 
    408             continue 
    409         if fields and not f.name in fields: 
    410             continue 
    411         if isinstance(f, ManyToManyField): 
    412             # MultipleChoiceWidget needs a list of ints, not object instances. 
    413             initial[f.name] = [obj.pk for obj in f.value_from_object(instance)] 
    414         else: 
    415             initial[f.name] = f.value_from_object(instance) 
    416     return initial 
    417  
    418 class BaseModelFormSet(BaseFormSet): 
    419     """ 
    420     A ``FormSet`` for editing a queryset and/or adding new objects to it. 
    421     """ 
    422     model = None 
    423     queryset = None 
    424  
    425     def __init__(self, qs, data=None, files=None, auto_id='id_%s', prefix=None): 
    426304        kwargs = {'data': data, 'files': files, 'auto_id': auto_id, 'prefix': prefix} 
    427         self.queryset = qs 
    428         kwargs['initial'] = [initial_data(obj) for obj in qs] 
     305        kwargs['initial'] = [model_to_dict(obj) for obj in self.get_queryset()] 
    429306        super(BaseModelFormSet, self).__init__(**kwargs) 
     307 
     308    def get_queryset(self): 
     309        if self.queryset is not None: 
     310            return self.queryset 
     311        return self.model._default_manager.get_query_set() 
    430312 
    431313    def save_new(self, form, commit=True): 
     
    433315        return save_instance(form, self.model(), commit=commit) 
    434316 
    435     def save_instance(self, form, instance, commit=True): 
     317    def save_existing(self, form, instance, commit=True): 
    436318        """Saves and returns an existing model instance for the given form.""" 
    437319        return save_instance(form, instance, commit=commit) 
     
    444326 
    445327    def save_existing_objects(self, commit=True): 
    446         if not self.queryset
     328        if not self.get_queryset()
    447329            return [] 
    448330        # Put the objects from self.get_queryset into a dict so they are easy to lookup by pk 
    449331        existing_objects = {} 
    450         for obj in self.queryset
     332        for obj in self.get_queryset()
    451333            existing_objects[obj.pk] = obj 
    452334        saved_instances = [] 
    453         for form in self.change_forms: 
     335        for form in self.initial_forms: 
    454336            obj = existing_objects[form.cleaned_data[self.model._meta.pk.attname]] 
    455             if self.deletable and form.cleaned_data[DELETION_FIELD_NAME]: 
     337            if self.can_delete and form.cleaned_data[DELETION_FIELD_NAME]: 
    456338                obj.delete() 
    457339            else: 
    458                 saved_instances.append(self.save_instance(form, obj, commit=commit)) 
     340                saved_instances.append(self.save_existing(form, obj, commit=commit)) 
    459341        return saved_instances 
    460342 
    461343    def save_new_objects(self, commit=True): 
    462344        new_objects = [] 
    463         for form in self.add_forms: 
    464             if form.is_empty(): 
     345        for form in self.extra_forms: 
     346            if not form.has_changed(): 
    465347                continue 
    466348            # If someone has marked an add form for deletion, don't save the 
    467349            # object. At some point it would be nice if we didn't display 
    468350            # the deletion widget for add forms. 
    469             if self.deletable and form.cleaned_data[DELETION_FIELD_NAME]: 
     351            if self.can_delete and form.cleaned_data[DELETION_FIELD_NAME]: 
    470352                continue 
    471353            new_objects.append(self.save_new(form, commit=commit)) 
     
    478360        super(BaseModelFormSet, self).add_fields(form, index) 
    479361 
    480 def formset_for_model(model, form=BaseForm, formfield_callback=lambda f: f.formfield(), 
    481                       formset=BaseModelFormSet, extra=1, orderable=False, deletable=False, fields=None): 
    482     """ 
    483     Returns a FormSet class for the given Django model class. This FormSet 
    484     will contain change forms for every instance of the given model as well 
    485     as the number of add forms specified by ``extra``. 
    486      
    487     This is essentially the same as ``formset_for_queryset``, but automatically 
    488     uses the model's default manager to determine the queryset. 
    489     """ 
    490     form = form_for_model(model, form=form, fields=fields, formfield_callback=formfield_callback) 
    491     FormSet = formset_for_form(form, formset, extra, orderable, deletable) 
     362# XXX: Use at your own risk. This API *will* change. 
     363def _modelformset_factory(model, form=BaseModelForm, formfield_callback=lambda f: f.formfield(), 
     364                          formset=BaseModelFormSet, 
     365                          extra=1, can_delete=False, can_order=False, 
     366                          fields=None, exclude=None): 
     367    """ 
     368    Returns a FormSet class for the given Django model class. 
     369    """ 
     370    form = _modelform_factory(model, form=form, fields=fields, exclude=exclude, 
     371                              formfield_callback=formfield_callback) 
     372    FormSet = _formset_factory(form, formset, extra=extra, can_order=can_order, can_delete=can_delete) 
    492373    FormSet.model = model 
    493374    return FormSet 
    494375 
    495 class InlineFormset(BaseModelFormSet): 
     376 
     377# InlineFormSets ############################################################# 
     378 
     379class BaseInlineFormset(BaseModelFormSet): 
    496380    """A formset for child objects related to a parent.""" 
    497     def __init__(self, instance, data=None, files=None): 
     381    def __init__(self, data=None, files=None, instance=None): 
    498382        from django.db.models.fields.related import RelatedObject 
    499383        self.instance = instance 
    500384        # is there a better way to get the object descriptor? 
    501385        self.rel_name = RelatedObject(self.fk.rel.to, self.model, self.fk).get_accessor_name() 
    502         qs = self.get_queryset() 
    503         super(InlineFormset, self).__init__(qs, data, files, prefix=self.rel_name) 
     386        super(BaseInlineFormset, self).__init__(data, files, prefix=self.rel_name) 
    504387 
    505388    def get_queryset(self): 
     
    516399        return save_instance(form, new_obj, commit=commit) 
    517400 
    518 def get_foreign_key(parent_model, model, fk_name=None): 
     401def _get_foreign_key(parent_model, model, fk_name=None): 
    519402    """ 
    520403    Finds and returns the ForeignKey from model to parent if there is one. 
     
    543426    return fk 
    544427 
    545 def inline_formset(parent_model, model, fk_name=None, fields=None, extra=3, orderable=False, deletable=True, formfield_callback=lambda f: f.formfield()): 
     428 
     429# XXX: This API *will* change. Use at your own risk. 
     430def _inlineformset_factory(parent_model, model, form=BaseModelForm, fk_name=None, 
     431                           fields=None, exclude=None, 
     432                           extra=3, can_order=False, can_delete=True, 
     433                           formfield_callback=lambda f: f.formfield()): 
    546434    """ 
    547435    Returns an ``InlineFormset`` for the given kwargs. 
     
    550438    to ``parent_model``. 
    551439    """ 
    552     fk = get_foreign_key(parent_model, model, fk_name=fk_name) 
     440    fk = _get_foreign_key(parent_model, model, fk_name=fk_name) 
    553441    # let the formset handle object deletion by default 
    554     FormSet = formset_for_model(model, formset=InlineFormset, fields=fields, 
    555                                 formfield_callback=formfield_callback, 
    556                                 extra=extra, orderable=orderable, 
    557                                 deletable=deletable) 
    558     # HACK: remove the ForeignKey to the parent from every form 
    559     # This should be done a line above before we pass 'fields' to formset_for_model 
    560     # an 'omit' argument would be very handy here 
    561     try: 
    562         del FormSet.form_class.base_fields[fk.name] 
    563     except KeyError: 
    564         pass 
     442     
     443    if exclude is not None: 
     444        exclude.append(fk.name) 
     445    else: 
     446        exclude = [fk.name] 
     447    FormSet = _modelformset_factory(model, form=form, 
     448                                    formfield_callback=formfield_callback, 
     449                                    formset=BaseInlineFormset, 
     450                                    extra=extra, can_delete=can_delete, can_order=can_order, 
     451                                    fields=fields, exclude=exclude) 
    565452    FormSet.fk = fk 
    566453    return FormSet 
     454 
     455 
     456# Fields ##################################################################### 
     457 
     458class QuerySetIterator(object): 
     459    def __init__(self, queryset, empty_label, cache_choices): 
     460        self.queryset = queryset 
     461        self.empty_label = empty_label 
     462        self.cache_choices = cache_choices 
     463 
     464    def __iter__(self): 
     465        if self.empty_label is not None: 
     466            yield (u"", self.empty_label) 
     467        for obj in self.queryset: 
     468            yield (obj.pk, smart_unicode(obj)) 
     469        # Clear the QuerySet cache if required. 
     470        if not self.cache_choices: 
     471            self.queryset._result_cache = None 
     472 
     473class ModelChoiceField(ChoiceField): 
     474    """A ChoiceField whose choices are a model QuerySet.""" 
     475    # This class is a subclass of ChoiceField for purity, but it doesn't 
     476    # actually use any of ChoiceField's implementation. 
     477    default_error_messages = { 
     478        'invalid_choice': _(u'Select a valid choice. That choice is not one of' 
     479                            u' the available choices.'), 
     480    } 
     481 
     482    def __init__(self, queryset, empty_label=u"---------", cache_choices=False, 
     483                 required=True, widget=Select, label=None, initial=None, 
     484                 help_text=None, *args, **kwargs): 
     485        self.empty_label = empty_label 
     486        self.cache_choices = cache_choices 
     487        # Call Field instead of ChoiceField __init__() because we don't need 
     488        # ChoiceField.__init__(). 
     489        Field.__init__(self, required, widget, label, initial, help_text, 
     490                       *args, **kwargs) 
     491        self.queryset = queryset 
     492 
     493    def _get_queryset(self): 
     494        return self._queryset 
     495 
     496    def _set_queryset(self, queryset): 
     497        self._queryset = queryset 
     498        self.widget.choices = self.choices 
     499 
     500    queryset = property(_get_queryset, _set_queryset) 
     501 
     502    def _get_choices(self): 
     503        # If self._choices is set, then somebody must have manually set 
     504        # the property self.choices. In this case, just return self._choices. 
     505        if hasattr(self, '_choices'): 
     506            return self._choices 
     507        # Otherwise, execute the QuerySet in self.queryset to determine the 
     508        # choices dynamically. Return a fresh QuerySetIterator that has not 
     509        # been consumed. Note that we're instantiating a new QuerySetIterator 
     510        # *each* time _get_choices() is called (and, thus, each time 
     511        # self.choices is accessed) so that we can ensure the QuerySet has not 
     512        # been consumed. 
     513        return QuerySetIterator(self.queryset, self.empty_label, 
     514                                self.cache_choices) 
     515 
     516    def _set_choices(self, value): 
     517        # This method is copied from ChoiceField._set_choices(). It's necessary 
     518        # because property() doesn't allow a subclass to overwrite only 
     519        # _get_choices without implementing _set_choices. 
     520        self._choices = self.widget.choices = list(value) 
     521 
     522    choices = property(_get_choices, _set_choices) 
     523 
     524    def clean(self, value): 
     525        Field.clean(self, value) 
     526        if value in EMPTY_VALUES: 
     527            return None 
     528        try: 
     529            value = self.queryset.get(pk=value) 
     530        except self.queryset.model.DoesNotExist: 
     531            raise ValidationError(self.error_messages['invalid_choice']) 
     532        return value 
     533 
     534class ModelMultipleChoiceField(ModelChoiceField): 
     535    """A MultipleChoiceField whose choices are a model QuerySet.""" 
     536    hidden_widget = MultipleHiddenInput 
     537    default_error_messages = { 
     538        'list': _(u'Enter a list of values.'), 
     539        'invalid_choice': _(u'Select a valid choice. %s is not one of the' 
     540                            u' available choices.'), 
     541    } 
     542 
     543    def __init__(self, queryset, cache_choices=False, required=True, 
     544                 widget=SelectMultiple, label=None, initial=None, 
     545                 help_text=None, *args, **kwargs): 
     546        super(ModelMultipleChoiceField, self).__init__(queryset, None, 
     547            cache_choices, required, widget, label, initial, help_text, 
     548            *args, **kwargs) 
     549 
     550    def clean(self, value): 
     551        if self.required and not value: 
     552            raise ValidationError(self.error_messages['required']) 
     553        elif not self.required and not value: 
     554            return [] 
     555        if not isinstance(value, (list, tuple)): 
     556            raise ValidationError(self.error_messages['list']) 
     557        final_values = [] 
     558        for val in value: 
     559            try: 
     560                obj = self.queryset.get(pk=val) 
     561            except self.queryset.model.DoesNotExist: 
     562                raise ValidationError(self.error_messages['invalid_choice'] % val) 
     563            else: 
     564                final_values.append(obj) 
     565        return final_values 
  • django/branches/newforms-admin/django/newforms/widgets.py

    r7121 r7270  
    165165        """ 
    166166        return data.get(name, None) 
    167      
    168     def is_empty(self, value): 
    169         """ 
    170         Given a dictionary of data and this widget's name, return True if the 
    171         widget data is empty or False when not empty. 
    172         """ 
    173         if value not in (None, ''): 
    174             return False 
    175         return True 
     167 
     168    def _has_changed(self, initial, data): 
     169        """ 
     170        Return True if data differs from initial. 
     171        """ 
     172        # For purposes of seeing whether something has changed, None is 
     173        # the same as an empty string, if the data or inital value we get 
     174        # is None, replace it w/ u''. 
     175        data_value = data or u'' 
     176        initial_value = initial or u'' 
     177        if force_unicode(initial_value) != force_unicode(data_value): 
     178            return True 
     179        return False 
    176180 
    177181    def id_for_label(self, id_): 
     
    310314            return False 
    311315        return super(CheckboxInput, self).value_from_datadict(data, files, name) 
    312      
    313     def is_empty(self, value): 
    314         # this widget will always either be True or False, so always return the 
    315         # opposite value so False values will make the form empty 
    316         return not value 
     316 
     317    def _has_changed(self, initial, data): 
     318        # Sometimes data or initial could be None or u'' which should be the 
     319        # same thing as False. 
     320        return bool(initial) != bool(data) 
    317321 
    318322class Select(Widget): 
     
    357361        value = data.get(name, None) 
    358362        return {u'2': True, u'3': False, True: True, False: False}.get(value, None) 
    359      
    360     def is_empty(self, value): 
    361         # this widget will always either be True, False or None, so always 
    362         # return the opposite value so False and None values will make the 
    363         # form empty. 
    364         return not value 
     363 
     364    def _has_changed(self, initial, data): 
     365        # Sometimes data or initial could be None or u'' which should be the 
     366        # same thing as False. 
     367        return bool(initial) != bool(data) 
    365368 
    366369class SelectMultiple(Widget): 
     
    560563        return [widget.value_from_datadict(data, files, name + '_%s' % i) for i, widget in enumerate(self.widgets)] 
    561564     
    562     def is_empty(self, value): 
    563         for widget, val in zip(self.widgets, value): 
    564             if not widget.is_empty(val): 
     565    def _has_changed(self, initial, data): 
     566        if initial is None: 
     567            initial = [u'' for x in range(0, len(data))] 
     568        for widget, initial, data in zip(self.widgets, initial, data): 
     569            if not widget._has_changed(initial, data): 
    565570                return False 
    566571        return True 
  • django/branches/newforms-admin/tests/modeltests/model_formsets/models.py

    r6839 r7270  
    1717__test__ = {'API_TESTS': """ 
    1818 
    19 >>> from django.newforms.models import formset_for_model 
     19>>> from django.newforms.models import _modelformset_factory 
    2020 
    2121>>> qs = Author.objects.all() 
    22 >>> AuthorFormSet = formset_for_model(Author, extra=3) 
    23  
    24 >>> formset = AuthorFormSet(qs) 
     22>>> AuthorFormSet = _modelformset_factory(Author, extra=3) 
     23 
     24>>> formset = AuthorFormSet(queryset=qs) 
    2525>>> for form in formset.forms: 
    2626...     print form.as_p() 
     
    3030 
    3131>>> data = { 
    32 ...     'form-COUNT': '3', 
     32...     'form-TOTAL_FORMS': '3', # the number of forms rendered 
     33...     'form-INITIAL_FORMS': '0', # the number of forms with initial data 
    3334...     'form-0-name': 'Charles Baudelaire', 
    3435...     'form-1-name': 'Arthur Rimbaud', 
     
    3637... } 
    3738 
    38 >>> formset = AuthorFormSet(qs, data=data
     39>>> formset = AuthorFormSet(data=data, queryset=qs
    3940>>> formset.is_valid() 
    4041True 
     
    5556 
    5657>>> qs = Author.objects.order_by('name') 
    57 >>> AuthorFormSet = formset_for_model(Author, extra=1, deletable=False) 
    58  
    59 >>> formset = AuthorFormSet(qs) 
     58>>> AuthorFormSet = _modelformset_factory(Author, extra=1, can_delete=False) 
     59 
     60>>> formset = AuthorFormSet(queryset=qs) 
    6061>>> for form in formset.forms: 
    6162...     print form.as_p() 
     
    6667 
    6768>>> data = { 
    68 ...     'form-COUNT': '3', 
     69...     'form-TOTAL_FORMS': '3', # the number of forms rendered 
     70...     'form-INITIAL_FORMS': '2', # the number of forms with initial data 
    6971...     'form-0-id': '2', 
    7072...     'form-0-name': 'Arthur Rimbaud', 
     
    7476... } 
    7577 
    76 >>> formset = AuthorFormSet(qs, data=data
     78>>> formset = AuthorFormSet(data=data, queryset=qs
    7779>>> formset.is_valid() 
    7880True 
     
    9294 
    9395>>> qs = Author.objects.order_by('name') 
    94 >>> AuthorFormSet = formset_for_model(Author, extra=1, deletable=True) 
    95  
    96 >>> formset = AuthorFormSet(qs) 
     96>>> AuthorFormSet = _modelformset_factory(Author, extra=1, can_delete=True) 
     97 
     98>>> formset = AuthorFormSet(queryset=qs) 
    9799>>> for form in formset.forms: 
    98100...     print form.as_p() 
     
    107109 
    108110>>> data = { 
    109 ...     'form-COUNT': '4', 
     111...     'form-TOTAL_FORMS': '4', # the number of forms rendered 
     112...     'form-INITIAL_FORMS': '3', # the number of forms with initial data 
    110113...     'form-0-id': '2', 
    111114...     'form-0-name': 'Arthur Rimbaud', 
     
    118121... } 
    119122 
    120 >>> formset = AuthorFormSet(qs, data=data
     123>>> formset = AuthorFormSet(data=data, queryset=qs
    121124>>> formset.is_valid() 
    122125True 
     
    132135 
    133136 
     137# Inline Formsets ############################################################ 
     138 
    134139We can also create a formset that is tied to a parent model. This is how the 
    135140admin system's edit inline functionality works. 
    136141 
    137 >>> from django.newforms.models import inline_formset 
    138  
    139 >>> AuthorBooksFormSet = inline_formset(Author, Book, deletable=False, extra=3) 
     142>>> from django.newforms.models import _inlineformset_factory 
     143 
     144>>> AuthorBooksFormSet = _inlineformset_factory(Author, Book, can_delete=False, extra=3) 
    140145>>> author = Author.objects.get(name='Charles Baudelaire') 
    141146 
    142 >>> formset = AuthorBooksFormSet(author) 
     147>>> formset = AuthorBooksFormSet(instance=author) 
    143148>>> for form in formset.forms: 
    144149...     print form.as_p() 
     
    148153 
    149154>>> data = { 
    150 ...     'book_set-COUNT': '3', 
     155...     'book_set-TOTAL_FORMS': '3', # the number of forms rendered 
     156...     'book_set-INITIAL_FORMS': '0', # the number of forms with initial data 
    151157...     'book_set-0-title': 'Les Fleurs du Mal', 
    152158...     'book_set-1-title': '', 
     
    154160... } 
    155161 
    156 >>> formset = AuthorBooksFormSet(author, data=data
     162>>> formset = AuthorBooksFormSet(data, instance=author
    157163>>> formset.is_valid() 
    158164True 
     
    170176book. 
    171177 
    172 >>> AuthorBooksFormSet = inline_formset(Author, Book, deletable=False, extra=2) 
     178>>> AuthorBooksFormSet = _inlineformset_factory(Author, Book, can_delete=False, extra=2) 
    173179>>> author = Author.objects.get(name='Charles Baudelaire') 
    174180 
    175 >>> formset = AuthorBooksFormSet(author) 
     181>>> formset = AuthorBooksFormSet(instance=author) 
    176182>>> for form in formset.forms: 
    177183...     print form.as_p() 
     
    181187 
    182188>>> data = { 
    183 ...     'book_set-COUNT': '3', 
     189...     'book_set-TOTAL_FORMS': '3', # the number of forms rendered 
     190...     'book_set-INITIAL_FORMS': '1', # the number of forms with initial data 
    184191...     'book_set-0-id': '1', 
    185192...     'book_set-0-title': 'Les Fleurs du Mal', 
     
    188195... } 
    189196 
    190 >>> formset = AuthorBooksFormSet(author, data=data
     197>>> formset = AuthorBooksFormSet(data, instance=author
    191198>>> formset.is_valid() 
    192199True 
  • django/branches/newforms-admin/tests/regressiontests/forms/formsets.py

    r6419 r7270  
    11# -*- coding: utf-8 -*- 
    2 formset_tests = """ 
     2tests = """ 
    33# Basic FormSet creation and usage ############################################ 
    44 
    55FormSet allows us to use multiple instance of the same form on 1 page. For now, 
    6 the best way to create a FormSet is by using the formset_for_form function. 
     6the best way to create a FormSet is by using the _formset_factory function. 
    77 
    88>>> from django.newforms import Form, CharField, IntegerField, ValidationError 
    9 >>> from django.newforms.formsets import formset_for_form, BaseFormSet 
     9>>> from django.newforms.formsets import _formset_factory, BaseFormSet 
    1010 
    1111>>> class Choice(Form): 
     
    1313...     votes = IntegerField() 
    1414 
    15 >>> ChoiceFormSet = formset_for_form(Choice) 
    16  
     15>>> ChoiceFormSet = _formset_factory(Choice) 
    1716 
    1817A FormSet constructor takes the same arguments as Form. Let's create a FormSet 
     
    2120 
    2221>>> formset = ChoiceFormSet(auto_id=False, prefix='choices') 
    23 >>> for form in formset.forms: 
    24 ...    print form.as_ul() 
    25 <li>Choice: <input type="text" name="choices-0-choice" /></li> 
    26 <li>Votes: <input type="text" name="choices-0-votes" /></li> 
     22>>> print formset 
     23<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /> 
     24<tr><th>Choice:</th><td><input type="text" name="choices-0-choice" /></td></tr> 
     25<tr><th>Votes:</th><td><input type="text" name="choices-0-votes" /></td></tr> 
     26 
    2727 
    2828On thing to note is that there needs to be a special value in the data. This 
     
    3030many forms it needs to clean and validate. You could use javascript to create 
    3131new forms on the client side, but they won't get validated unless you increment 
    32 the COUNT field appropriately. 
    33  
    34 >>> data = { 
    35 ...     'choices-COUNT': '1', # the number of forms rendered 
     32the TOTAL_FORMS field appropriately. 
     33 
     34>>> data = { 
     35...     'choices-TOTAL_FORMS': '1', # the number of forms rendered 
     36...     'choices-INITIAL_FORMS': '0', # the number of forms with initial data 
    3637...     'choices-0-choice': 'Calexico', 
    3738...     'choices-0-votes': '100', 
     
    4647>>> formset.is_valid() 
    4748True 
    48 >>> formset.cleaned_data 
     49>>> [form.cleaned_data for form in formset.forms] 
    4950[{'votes': 100, 'choice': u'Calexico'}] 
    5051 
     
    5859 
    5960>>> data = { 
    60 ...     'choices-COUNT': '1', # the number of forms rendered 
     61...     'choices-TOTAL_FORMS': '1', # the number of forms rendered 
     62...     'choices-INITIAL_FORMS': '0', # the number of forms with initial data 
    6163...     'choices-0-choice': 'Calexico', 
    6264...     'choices-0-votes': '', 
     
    6870>>> formset.errors 
    6971[{'votes': [u'This field is required.']}] 
    70  
    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' 
    7772 
    7873 
     
    9489 
    9590>>> data = { 
    96 ...     'choices-COUNT': '2', # the number of forms rendered 
     91...     'choices-TOTAL_FORMS': '2', # the number of forms rendered 
     92...     'choices-INITIAL_FORMS': '1', # the number of forms with initial data 
    9793...     'choices-0-choice': 'Calexico', 
    9894...     'choices-0-votes': '100', 
     
    104100>>> formset.is_valid() 
    105101True 
    106 >>> formset.cleaned_data 
    107 [{'votes': 100, 'choice': u'Calexico'}
     102>>> [form.cleaned_data for form in formset.forms] 
     103[{'votes': 100, 'choice': u'Calexico'}, {}
    108104 
    109105But the second form was blank! Shouldn't we get some errors? No. If we display 
     
    114110 
    115111>>> data = { 
    116 ...     'choices-COUNT': '2', # the number of forms rendered 
     112...     'choices-TOTAL_FORMS': '2', # the number of forms rendered 
     113...     'choices-INITIAL_FORMS': '1', # the number of forms with initial data 
    117114...     'choices-0-choice': 'Calexico', 
    118115...     'choices-0-votes': '100', 
     
    126123>>> formset.errors 
    127124[{}, {'votes': [u'This field is required.']}] 
    128  
    129125 
    130126If we delete data that was pre-filled, we should get an error. Simply removing 
     
    133129 
    134130>>> data = { 
    135 ...     'choices-COUNT': '2', # the number of forms rendered 
     131...     'choices-TOTAL_FORMS': '2', # the number of forms rendered 
     132...     'choices-INITIAL_FORMS': '1', # the number of forms with initial data 
    136133...     'choices-0-choice': '', # deleted value 
    137134...     'choices-0-votes': '', # deleted value 
     
    144141False 
    145142>>> formset.errors 
    146 [{'votes': [u'This field is required.'], 'choice': [u'This field is required.']}
     143[{'votes': [u'This field is required.'], 'choice': [u'This field is required.']}, {}
    147144 
    148145 
     
    150147 
    151148We can also display more than 1 empty form at a time. To do so, pass a 
    152 num_extra argument to formset_for_form
    153  
    154 >>> ChoiceFormSet = formset_for_form(Choice, num_extra=3) 
     149extra argument to _formset_factory
     150 
     151>>> ChoiceFormSet = _formset_factory(Choice, extra=3) 
    155152 
    156153>>> formset = ChoiceFormSet(auto_id=False, prefix='choices') 
     
    169166 
    170167>>> data = { 
    171 ...     'choices-COUNT': '3', # the number of forms rendered 
     168...     'choices-TOTAL_FORMS': '3', # the number of forms rendered 
     169...     'choices-INITIAL_FORMS': '0', # the number of forms with initial data 
    172170...     'choices-0-choice': '', 
    173171...     'choices-0-votes': '', 
     
    181179>>> formset.is_valid() 
    182180True 
    183 >>> formset.cleaned_data 
    184 [
     181>>> [form.cleaned_data for form in formset.forms] 
     182[{}, {}, {}
    185183 
    186184 
     
    188186 
    189187>>> data = { 
    190 ...     'choices-COUNT': '3', # the number of forms rendered 
     188...     'choices-TOTAL_FORMS': '3', # the number of forms rendered 
     189...     'choices-INITIAL_FORMS': '0', # the number of forms with initial data 
    191190...     'choices-0-choice': 'Calexico', 
    192191...     'choices-0-votes': '100', 
     
    200199>>> formset.is_valid() 
    201200True 
    202 >>> formset.cleaned_data 
    203 [{'votes': 100, 'choice': u'Calexico'}
     201>>> [form.cleaned_data for form in formset.forms] 
     202[{'votes': 100, 'choice': u'Calexico'}, {}, {}
    204203 
    205204 
     
    207206 
    208207>>> data = { 
    209 ...     'choices-COUNT': '3', # the number of forms rendered 
     208...     'choices-TOTAL_FORMS': '3', # the number of forms rendered 
     209...     'choices-INITIAL_FORMS': '0', # the number of forms with initial data 
    210210...     'choices-0-choice': 'Calexico', 
    211211...     'choices-0-votes': '100', 
     
    220220False 
    221221>>> formset.errors 
    222 [{}, {'votes': [u'This field is required.']}
    223  
    224  
    225 The num_extra argument also works when the formset is pre-filled with initial 
     222[{}, {'votes': [u'This field is required.']}, {}
     223 
     224 
     225The extra argument also works when the formset is pre-filled with initial 
    226226data. 
    227227 
     
    240240 
    241241 
    242 If we try to skip a form, even if it was initially displayed as blank, we will 
    243 get an error. 
    244  
    245 >>> data = { 
    246 ...     'choices-COUNT': '3', # the number of forms rendered 
    247 ...     'choices-0-choice': 'Calexico', 
    248 ...     'choices-0-votes': '100', 
    249 ...     'choices-1-choice': '', 
    250 ...     'choices-1-votes': '', 
    251 ...     'choices-2-choice': 'The Decemberists', 
    252 ...     'choices-2-votes': '12', 
    253 ...     'choices-3-choice': '', 
    254 ...     'choices-3-votes': '', 
    255 ... } 
    256  
    257 >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices') 
    258 >>> formset.is_valid() 
    259 False 
    260 >>> formset.errors 
    261 [{}, {'votes': [u'This field is required.'], 'choice': [u'This field is required.']}, {}] 
    262  
    263  
    264242# FormSets with deletion ###################################################### 
    265243 
    266244We can easily add deletion ability to a FormSet with an agrument to 
    267 formset_for_form. This will add a boolean field to each form instance. When 
    268 that boolean field is True, the cleaned data will be in formset.deleted_data 
    269 rather than formset.cleaned_data 
    270  
    271 >>> ChoiceFormSet = formset_for_form(Choice, deletable=True) 
     245_formset_factory. This will add a boolean field to each form instance. When 
     246that boolean field is True, the form will be in formset.deleted_forms 
     247 
     248>>> ChoiceFormSet = _formset_factory(Choice, can_delete=True) 
    272249 
    273250>>> initial = [{'choice': u'Calexico', 'votes': 100}, {'choice': u'Fergie', 'votes': 900}] 
     
    289266 
    290267>>> data = { 
    291 ...     'choices-COUNT': '3', # the number of forms rendered 
     268...     'choices-TOTAL_FORMS': '3', # the number of forms rendered 
     269...     'choices-INITIAL_FORMS': '2', # the number of forms with initial data 
    292270...     'choices-0-choice': 'Calexico', 
    293271...     'choices-0-votes': '100', 
     
    304282>>> formset.is_valid() 
    305283True 
    306 >>> formset.cleaned_data 
    307 [{'votes': 100, 'DELETE': False, 'choice': u'Calexico'}
    308 >>> formset.deleted_data 
     284>>> [form.cleaned_data for form in formset.forms] 
     285[{'votes': 100, 'DELETE': False, 'choice': u'Calexico'}, {'votes': 900, 'DELETE': True, 'choice': u'Fergie'}, {}
     286>>> [form.cleaned_data for form in formset.deleted_forms] 
    309287[{'votes': 900, 'DELETE': True, 'choice': u'Fergie'}] 
    310288 
     289 
    311290# FormSets with ordering ###################################################### 
    312291 
    313292We can also add ordering ability to a FormSet with an agrument to 
    314 formset_for_form. This will add a integer field to each form instance. When 
    315 form validation succeeds, formset.cleaned_data will have the data in the correct 
     293_formset_factory. This will add a integer field to each form instance. When 
     294form validation succeeds, [form.cleaned_data for form in formset.forms] will have the data in the correct 
    316295order specified by the ordering fields. If a number is duplicated in the set 
    317296of ordering fields, for instance form 0 and form 3 are both marked as 1, then 
     
    319298something at the front of the list, you'd need to set it's order to 0. 
    320299 
    321 >>> ChoiceFormSet = formset_for_form(Choice, orderable=True) 
     300>>> ChoiceFormSet = _formset_factory(Choice, can_order=True) 
    322301 
    323302>>> initial = [{'choice': u'Calexico', 'votes': 100}, {'choice': u'Fergie', 'votes': 900}] 
     
    333312<li>Choice: <input type="text" name="choices-2-choice" /></li> 
    334313<li>Votes: <input type="text" name="choices-2-votes" /></li> 
    335 <li>Order: <input type="text" name="choices-2-ORDER" value="3" /></li> 
    336  
    337 >>> data = { 
    338 ...     'choices-COUNT': '3', # the number of forms rendered 
     314<li>Order: <input type="text" name="choices-2-ORDER" /></li> 
     315 
     316>>> data = { 
     317...     'choices-TOTAL_FORMS': '3', # the number of forms rendered 
     318...     'choices-INITIAL_FORMS': '2', # the number of forms with initial data 
    339319...     'choices-0-choice': 'Calexico', 
    340320...     'choices-0-votes': '100', 
     
    351331>>> formset.is_valid() 
    352332True 
    353 >>> for cleaned_data in formset.cleaned_data
    354 ...    print cleaned_data 
     333>>> for form in formset.ordered_forms
     334...    print form.cleaned_data 
    355335{'votes': 500, 'ORDER': 0, 'choice': u'The Decemberists'} 
    356336{'votes': 100, 'ORDER': 1, 'choice': u'Calexico'} 
    357337{'votes': 900, 'ORDER': 2, 'choice': u'Fergie'} 
    358338 
     339Ordering fields are allowed to be left blank, and if they *are* left blank, 
     340they will be sorted below everything else. 
     341 
     342>>> data = { 
     343...     'choices-TOTAL_FORMS': '4', # the number of forms rendered 
     344...     'choices-INITIAL_FORMS': '3', # the number of forms with initial data 
     345...     'choices-0-choice': 'Calexico', 
     346...     'choices-0-votes': '100', 
     347...     'choices-0-ORDER': '1', 
     348...     'choices-1-choice': 'Fergie', 
     349...     'choices-1-votes': '900', 
     350...     'choices-1-ORDER': '2', 
     351...     'choices-2-choice': 'The Decemberists', 
     352...     'choices-2-votes': '500', 
     353...     'choices-2-ORDER': '', 
     354...     'choices-3-choice': 'Basia Bulat', 
     355...     'choices-3-votes': '50', 
     356...     'choices-3-ORDER': '', 
     357... } 
     358 
     359>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices') 
     360>>> formset.is_valid() 
     361True 
     362>>> for form in formset.ordered_forms: 
     363...    print form.cleaned_data 
     364{'votes': 100, 'ORDER': 1, 'choice': u'Calexico'} 
     365{'votes': 900, 'ORDER': 2, 'choice': u'Fergie'} 
     366{'votes': 500, 'ORDER': None, 'choice': u'The Decemberists'} 
     367{'votes': 50, 'ORDER': None, 'choice': u'Basia Bulat'} 
     368 
     369 
    359370# FormSets with ordering + deletion ########################################### 
    360371 
    361372Let's try throwing ordering and deletion into the same form. 
    362373 
    363 >>> ChoiceFormSet = formset_for_form(Choice, orderable=True, deletable=True) 
     374>>> ChoiceFormSet = _formset_factory(Choice, can_order=True, can_delete=True) 
    364375 
    365376>>> initial = [ 
     
    385396<li>Choice: <input type="text" name="choices-3-choice" /></li> 
    386397<li>Votes: <input type="text" name="choices-3-votes" /></li> 
    387 <li>Order: <input type="text" name="choices-3-ORDER" value="4" /></li> 
     398<li>Order: <input type="text" name="choices-3-ORDER" /></li> 
    388399<li>Delete: <input type="checkbox" name="choices-3-DELETE" /></li> 
    389400 
     
    391402 
    392403>>> data = { 
    393 ...     'choices-COUNT': '4', # the number of forms rendered 
     404...     'choices-TOTAL_FORMS': '4', # the number of forms rendered 
     405...     'choices-INITIAL_FORMS': '3', # the number of forms with initial data 
    394406...     'choices-0-choice': 'Calexico', 
    395407...     'choices-0-votes': '100', 
     
    406418...     'choices-3-choice': '', 
    407419...     'choices-3-votes': '', 
    408 ...     'choices-3-ORDER': '4', 
     420...     'choices-3-ORDER': '', 
    409421...     'choices-3-DELETE': '', 
    410422... } 
     
    413425>>> formset.is_valid() 
    414426True 
    415 >>> for cleaned_data in formset.cleaned_data
    416 ...    print cleaned_data 
     427>>> for form in formset.ordered_forms
     428...    print form.cleaned_data 
    417429{'votes': 500, 'DELETE': False, 'ORDER': 0, 'choice': u'The Decemberists'} 
    418430{'votes': 100, 'DELETE': False, 'ORDER': 1, 'choice': u'Calexico'} 
    419 >>> formset.deleted_data 
     431>>> [form.cleaned_data for form in formset.deleted_forms] 
    420432[{'votes': 900, 'DELETE': True, 'ORDER': 2, 'choice': u'Fergie'}] 
    421433 
     
    434446 
    435447>>> class FavoriteDrinksFormSet(BaseFormSet): 
    436 ...     form_class = FavoriteDrinkForm 
    437 ...     num_extra = 2 
    438 ...     orderable = False 
    439 ...     deletable = False 
     448...     form = FavoriteDrinkForm 
     449...     extra = 2 
     450...     can_order = False 
     451...     can_delete = False 
    440452... 
    441453...     def clean(self): 
     
    445457...                 raise ValidationError('You may only specify a drink once.') 
    446458...             seen_drinks.append(drink['name']) 
    447 ...         return self.cleaned_data 
    448459... 
    449460 
     
    451462 
    452463>>> data = { 
    453 ...     'drinks-COUNT': '2', 
     464...     'drinks-TOTAL_FORMS': '2', # the number of forms rendered 
     465...     'drinks-INITIAL_FORMS': '0', # the number of forms with initial data 
    454466...     'drinks-0-name': 'Gin and Tonic', 
    455467...     'drinks-1-name': 'Gin and Tonic', 
     
    471483 
    472484>>> data = { 
    473 ...     'drinks-COUNT': '2', 
     485...     'drinks-TOTAL_FORMS': '2', # the number of forms rendered 
     486...     'drinks-INITIAL_FORMS': '0', # the number of forms with initial data 
    474487...     'drinks-0-name': 'Gin and Tonic', 
    475488...     'drinks-1-name': 'Bloody Mary', 
  • django/branches/newforms-admin/tests/regressiontests/forms/forms.py

    r6776 r7270  
    16041604<input type="submit" /> 
    16051605</form> 
     1606 
     1607 
     1608# The empty_permitted attribute ############################################## 
     1609 
     1610Sometimes (pretty much in formsets) we want to allow a form to pass validation 
     1611if it is completely empty. We can accomplish this by using the empty_permitted 
     1612agrument to a form constructor. 
     1613 
     1614>>> class SongForm(Form): 
     1615...     artist = CharField() 
     1616...     name = CharField() 
     1617 
     1618First let's show what happens id empty_permitted=False (the default): 
     1619 
     1620>>> data = {'artist': '', 'song': ''} 
     1621 
     1622>>> form = SongForm(data, empty_permitted=False) 
     1623>>> form.is_valid() 
     1624False 
     1625>>> form.errors 
     1626{'name': [u'This field is required.'], 'artist': [u'This field is required.']} 
     1627>>> form.cleaned_data 
     1628Traceback (most recent call last): 
     1629... 
     1630AttributeError: 'SongForm' object has no attribute 'cleaned_data' 
     1631 
     1632 
     1633Now let's show what happens when empty_permitted=True and the form is empty. 
     1634 
     1635>>> form = SongForm(data, empty_permitted=True) 
     1636>>> form.is_valid() 
     1637True 
     1638>>> form.errors 
     1639{} 
     1640>>> form.cleaned_data 
     1641{} 
     1642 
     1643But if we fill in data for one of the fields, the form is no longer empty and 
     1644the whole thing must pass validation. 
     1645 
     1646>>> data = {'artist': 'The Doors', 'song': ''} 
     1647>>> form = SongForm(data, empty_permitted=False) 
     1648>>> form.is_valid() 
     1649False 
     1650>>> form.errors 
     1651{'name': [u'This field is required.']} 
     1652>>> form.cleaned_data 
     1653Traceback (most recent call last): 
     1654... 
     1655AttributeError: 'SongForm' object has no attribute 'cleaned_data' 
     1656 
    16061657""" 
  • django/branches/newforms-admin/tests/regressiontests/forms/tests.py

    r6864 r7270  
    2727from util import tests as util_tests 
    2828from widgets import tests as widgets_tests 
    29 from formsets import formset_tests 
     29from formsets import tests as formset_tests 
    3030from media import media_tests 
    3131 
  • django/branches/newforms-admin/tests/regressiontests/forms/widgets.py

    r6837 r7270  
    293293False 
    294294 
    295 The CheckboxInput widget will always be empty when there is a False value 
    296 >>> w.is_empty(False) 
    297 True 
    298 >>> w.is_empty(True) 
    299 False 
    300  
    301295# Select Widget ############################################################### 
    302296 
     
    459453<option value="3" selected="selected">No</option> 
    460454</select> 
    461  
    462 The NullBooleanSelect widget will always be empty when Unknown or No is selected 
    463 as its value.  This is to stay compliant with the CheckboxInput behavior 
    464 >>> w.is_empty(False) 
    465 True 
    466 >>> w.is_empty(None) 
    467 True 
    468 >>> w.is_empty(True) 
    469 False 
    470455 
    471456""" + \ 
     
    911896u'<input id="bar_0" type="text" class="big" value="john" name="name_0" /><br /><input id="bar_1" type="text" class="small" value="lennon" name="name_1" />' 
    912897 
    913 The MultiWidget will be empty only when all widgets are considered empty. 
    914 >>> w.is_empty(['john', 'lennon']) 
    915 False 
    916 >>> w.is_empty(['john', '']) 
    917 False 
    918 >>> w.is_empty(['', '']) 
    919 True 
    920 >>> w.is_empty([None, None]) 
    921 True 
    922  
    923898# SplitDateTimeWidget ######################################################### 
    924899 
  • django/branches/newforms-admin/tests/regressiontests/inline_formsets/models.py

    r6301 r7270  
    1616__test__ = {'API_TESTS': """ 
    1717 
    18 >>> from django.newforms.models import inline_formset 
     18>>> from django.newforms.models import _inlineformset_factory 
    1919 
    2020 
     
    2222for the inline formset, we should get an exception. 
    2323 
    24 >>> ifs = inline_formset(Parent, Child) 
     24>>> ifs = _inlineformset_factory(Parent, Child) 
    2525Traceback (most recent call last): 
    2626    ... 
     
    3030These two should both work without a problem. 
    3131 
    32 >>> ifs = inline_formset(Parent, Child, fk_name='mother') 
    33 >>> ifs = inline_formset(Parent, Child, fk_name='father') 
     32>>> ifs = _inlineformset_factory(Parent, Child, fk_name='mother') 
     33>>> ifs = _inlineformset_factory(Parent, Child, fk_name='father') 
    3434 
    3535 
     
    3737parent model, we should get an exception. 
    3838 
    39 >>> ifs = inline_formset(Parent, Child, fk_name='school') 
     39>>> ifs = _inlineformset_factory(Parent, Child, fk_name='school') 
    4040Traceback (most recent call last): 
    4141    ... 
     
    4646exception. 
    4747 
    48 >>> ifs = inline_formset(Parent, Child, fk_name='test') 
     48>>> ifs = _inlineformset_factory(Parent, Child, fk_name='test') 
    4949Traceback (most recent call last): 
    5050    ...