Ticket #6632: 01-inlines.diff

File 01-inlines.diff, 14.8 KB (added by Petr Marhoun <petr.marhoun@…>, 7 years ago)
  • django/forms/forms.py

    === modified file 'django/forms/forms.py'
     
    2828        self.fieldsets = getattr(options, 'fieldsets', None)
    2929        self.fields = getattr(options, 'fields', None)
    3030        self.exclude = getattr(options, 'exclude', None)
     31        self.inlines = getattr(options, 'inlines', None)
    3132
    3233def create_declared_fields(cls, attrs):
    3334    """
     
    7273    if cls._meta.fieldsets:
    7374        names = []
    7475        for fieldset in cls._meta.fieldsets:
    75             names.extend(fieldset['fields'])
     76            if isinstance(fieldset, dict):
     77                names.extend(fieldset['fields'])
    7678    elif cls._meta.fields:
    7779        names = cls._meta.fields
    7880    elif cls._meta.exclude:
     
    8183        names = cls.base_fields_pool.keys()
    8284    cls.base_fields = SortedDict([(name, cls.base_fields_pool[name]) for name in names])
    8385
     86def create_fieldsets_if_inlines_exist(cls, attrs):
     87    if cls._meta.inlines is not None:
     88        if cls._meta.fieldsets is not None:
     89            raise ImproperlyConfigured("%s cannot have more than one option from fieldsets and inlines." % cls.__name__)
     90        cls._meta.fieldsets = [{'fields': cls.base_fields.keys()}] + list(cls._meta.inlines)
     91
    8492class FormMetaclass(type):
    8593    """
    8694    Metaclass that converts Field attributes to a dictionary called
     
    94102        create_declared_fields(new_class, attrs)
    95103        create_base_fields_pool_from_declared_fields(new_class, attrs)
    96104        create_base_fields_from_base_fields_pool(new_class, attrs)
     105        create_fieldsets_if_inlines_exist(new_class, attrs)
    97106        if 'media' not in attrs:
    98107            new_class.media = media_property(new_class)
    99108        return new_class
     
    115124        self.error_class = error_class
    116125        self.label_suffix = label_suffix
    117126        self.empty_permitted = empty_permitted
    118         self._errors = None # Stores the errors after clean() has been called.
     127        self._is_valid = None # Stores validation state after full_clean() has been called.
    119128        self._changed_data = None
    120129
    121130        # The base_fields class attribute is the *class-wide* definition of
     
    124133        # Instances should always modify self.fields; they should not modify
    125134        # self.base_fields.
    126135        self.fields = deepcopy(self.base_fields)
     136        self._construct_inlines()
     137
     138    def _construct_inlines(self):
     139        # This class cannot create any inlines.
     140        self.inlines = []
     141        if self.has_fieldsets():
     142            for fieldset in self._meta.fieldsets:
     143                if not isinstance(fieldset, dict):
     144                    raise ValueError('%s cannot create instance of %s.' % (self.__class__.__name__, fieldset.__name__))
    127145
    128146    def __unicode__(self):
    129147        return self.as_table()
     
    145163   
    146164    def fieldsets(self):
    147165        if self.has_fieldsets():
     166            fieldset_counter, formset_counter = 0, 0
    148167            for fieldset in self._meta.fieldsets:
    149                 yield {
    150                     'attrs': fieldset.get('attrs', {}),
    151                     'legend': fieldset.get('legend', u''),
    152                     'fields': [self[name] for name in fieldset['fields']],
    153                 }
    154    
     168                if isinstance(fieldset, dict):
     169                    yield {
     170                        'order': fieldset_counter, 
     171                        'attrs': fieldset.get('attrs', {}),
     172                        'legend': fieldset.get('legend', u''),
     173                        'fields': [self[name] for name in fieldset['fields']],
     174                    }
     175                    fieldset_counter += 1       
     176                else:
     177                    yield {
     178                        'order': formset_counter,
     179                        'formset': self.inlines[formset_counter]
     180                    }
     181                    formset_counter += 1
     182
    155183    def _get_errors(self):
    156184        "Returns an ErrorDict for the data provided for the form"
    157         if self._errors is None:
     185        if self._is_valid is None:
    158186            self.full_clean()
    159187        return self._errors
    160188    errors = property(_get_errors)
     
    164192        Returns True if the form has no errors. Otherwise, False. If errors are
    165193        being ignored, returns False.
    166194        """
    167         return self.is_bound and not bool(self.errors)
     195        if self._is_valid is None:
     196            self.full_clean()
     197        return self._is_valid
    168198
    169199    def add_prefix(self, field_name):
    170200        """
     
    254284
    255285    def full_clean(self):
    256286        """
    257         Cleans all of self.data and populates self._errors and
     287        Cleans all of self.data and populates self._is_valid, self._errors and
    258288        self.cleaned_data.
    259289        """
     290        self._is_valid = True # Assume the form is valid until proven otherwise.
    260291        self._errors = ErrorDict()
    261292        if not self.is_bound: # Stop further processing.
     293            self._is_valid = False
    262294            return
    263295        self.cleaned_data = {}
    264296        # If the form is permitted to be empty, and none of the form data has
     
    284316                self._errors[name] = e.messages
    285317                if name in self.cleaned_data:
    286318                    del self.cleaned_data[name]
     319                self._is_valid = False
    287320        try:
    288321            self.cleaned_data = self.clean()
    289322        except ValidationError, e:
    290323            self._errors[NON_FIELD_ERRORS] = e.messages
    291         if self._errors:
     324            self._is_valid = False
     325        for inline in self.inlines:
     326            inline.full_clean()
     327            if not inline.is_valid():
     328                self._is_valid = False
     329        if not self._is_valid:
    292330            delattr(self, 'cleaned_data')
     331            for inline in self.inlines:
     332                inline._is_valid = False
    293333
    294334    def clean(self):
    295335        """
     
    337377        media = Media()
    338378        for field in self.fields.values():
    339379            media = media + field.widget.media
     380        for inline in self.inlines:
     381            media = media + inline.media
    340382        return media
    341383    media = property(_get_media)
    342384
     
    348390        for field in self.fields.values():
    349391            if field.widget.needs_multipart_form:
    350392                return True
     393        for inline in self.inlines:
     394            if inline.is_multipart():
     395                return True
    351396        return False
    352397
    353398class Form(BaseForm):
  • django/forms/formsets.py

    === modified file 'django/forms/formsets.py'
     
    3838        self.files = files
    3939        self.initial = initial
    4040        self.error_class = error_class
    41         self._errors = None
    42         self._non_form_errors = None
     41        self._is_valid = None # Stores validation state after full_clean() has been called.
    4342        # initialization is different depending on whether we recieved data, initial, or nothing
    4443        if data or files:
    4544            self.management_form = ManagementForm(data, auto_id=self.auto_id, prefix=self.prefix)
     
    182181        form -- i.e., from formset.clean(). Returns an empty ErrorList if there
    183182        are none.
    184183        """
    185         if self._non_form_errors is not None:
    186             return self._non_form_errors
    187         return self.error_class()
     184        if self._is_valid is None:
     185            self.full_clean()
     186        return self._non_form_errors
    188187
    189188    def _get_errors(self):
    190189        """
    191190        Returns a list of form.errors for every form in self.forms.
    192191        """
    193         if self._errors is None:
     192        if self._is_valid is None:
    194193            self.full_clean()
    195194        return self._errors
    196195    errors = property(_get_errors)
     
    199198        """
    200199        Returns True if form.errors is empty for every form in self.forms.
    201200        """
    202         if not self.is_bound:
    203             return False
    204         # We loop over every form.errors here rather than short circuiting on the
    205         # first failure to make sure validation gets triggered for every form.
    206         forms_valid = True
    207         for errors in self.errors:
    208             if bool(errors):
    209                 forms_valid = False
    210         return forms_valid and not bool(self.non_form_errors())
     201        if self._is_valid is None:
     202            self.full_clean()
     203        return self._is_valid
    211204
    212205    def full_clean(self):
    213206        """
    214207        Cleans all of self.data and populates self._errors.
    215208        """
     209        self._is_valid = True # Assume the form is valid until proven otherwise.
    216210        self._errors = []
     211        self._non_form_errors = self.error_class()
    217212        if not self.is_bound: # Stop further processing.
     213            self._is_valid = False
    218214            return
    219215        for i in range(0, self._total_form_count):
    220216            form = self.forms[i]
    221217            self._errors.append(form.errors)
     218            if form.errors:
     219                self._is_valid = False
    222220        # Give self.clean() a chance to do cross-form validation.
    223221        try:
    224222            self.clean()
    225223        except ValidationError, e:
    226             self._non_form_errors = e.messages
     224            self._non_form_errors = self.error_class(e.messages)
     225            self._is_valid = False
    227226
    228227    def clean(self):
    229228        """
  • django/forms/models.py

    === modified file 'django/forms/models.py'
     
    1111from util import ValidationError, ErrorList
    1212from forms import FormOptions, BaseForm, create_declared_fields
    1313from forms import create_base_fields_from_base_fields_pool
     14from forms import create_fieldsets_if_inlines_exist
    1415from fields import Field, ChoiceField, IntegerField, EMPTY_VALUES
    1516from widgets import Select, SelectMultiple, HiddenInput, MultipleHiddenInput
    1617from widgets import media_property
     
    6061                continue
    6162            if f.name in cleaned_data:
    6263                f.save_form_data(instance, cleaned_data[f.name])
     64        if not commit:
     65            for inline in form.inlines:
     66                inline.save_m2m()
    6367    if commit:
    6468        # If we are committing, save the instance and the m2m data immediately.
    6569        instance.save()
     
    209213        create_declared_fields(new_class, attrs)
    210214        create_base_fields_pool_from_model_fields_and_declared_fields(new_class, attrs)
    211215        create_base_fields_from_base_fields_pool(new_class, attrs)
     216        create_fieldsets_if_inlines_exist(new_class, attrs)
    212217        if 'media' not in attrs:
    213218            new_class.media = media_property(new_class)
    214219        return new_class
     
    233238        self.validate_unique()
    234239        return self.cleaned_data
    235240
     241    def _construct_inlines(self):
     242        # This class can create inlines which are subclass of BaseInlineFormSet.
     243        self.inlines = []
     244        if self.has_fieldsets():
     245            for fieldset in self._meta.fieldsets:
     246                if not isinstance(fieldset, dict):
     247                    if not issubclass(fieldset, BaseInlineFormSet):
     248                        raise ValueError('%s cannot create instance of %s.' % (self.__class__.__name__, fieldset.__name__))
     249                    self.inlines.append(fieldset(self.data, self.files, self.instance))
     250
    236251    def validate_unique(self):
    237252        from django.db.models.fields import FieldDoesNotExist
    238253
     
    295310                        {'model_name': unicode(model_name),
    296311                         'field_label': unicode(field_label)}
    297312                    ])
     313                    self._is_valid = False
    298314                # unique_together
    299315                else:
    300316                    field_labels = [self.fields[field_name].label for field_name in unique_check]
     
    304320                        {'model_name': unicode(model_name),
    305321                         'field_label': unicode(field_labels)}
    306322                    )
    307 
     323                    self._is_valid = False
     324               
    308325                # Mark these fields as needing to be removed from cleaned data
    309326                # later.
    310327                for field_name in unique_check:
     
    329346            fail_message = 'created'
    330347        else:
    331348            fail_message = 'changed'
    332         return save_instance(self, self.instance, self.fields.keys(), fail_message, commit)
     349        self.saved_instance = save_instance(self, self.instance, self.fields.keys(), fail_message, commit)
     350        self.saved_inline_instances = [inline.save(commit) for inline in self.inlines]
     351        return self.saved_instance
    333352
    334353class ModelForm(BaseModelForm):
    335354    __metaclass__ = ModelFormMetaclass
    336355
    337356def modelform_factory(model, form=ModelForm, fields=None, exclude=None, fieldsets=None,
    338                        formfield_callback=lambda f: f.formfield()):
     357                       inlines=None, formfield_callback=lambda f: f.formfield()):
    339358    # HACK: we should be able to construct a ModelForm without creating
    340359    # and passing in a temporary inner class
    341360    class Meta:
     
    344363    setattr(Meta, 'fieldsets', fieldsets)
    345364    setattr(Meta, 'fields', fields)
    346365    setattr(Meta, 'exclude', exclude)
     366    setattr(Meta, 'inlines', inlines)
    347367    class_name = model.__name__ + 'Form'
    348368    return ModelFormMetaclass(class_name, (form,), {'Meta': Meta,
    349369                              'formfield_callback': formfield_callback})
     
    451471def modelformset_factory(model, form=ModelForm, formfield_callback=lambda f: f.formfield(),
    452472                         formset=BaseModelFormSet,
    453473                         extra=1, can_delete=False, can_order=False,
    454                          max_num=0, fields=None, exclude=None, fieldsets=None):
     474                         max_num=0, fields=None, exclude=None, fieldsets=None, inlines=None):
    455475    """
    456476    Returns a FormSet class for the given Django model class.
    457477    """
    458478    form = modelform_factory(model, form=form, fields=fields, exclude=exclude,
    459                              fieldsets=fieldsets,
     479                             fieldsets=fieldsets, inlines=inlines,
    460480                             formfield_callback=formfield_callback)
    461481    FormSet = formset_factory(form, formset, extra=extra, max_num=max_num,
    462482                              can_order=can_order, can_delete=can_delete)
     
    546566
    547567def inlineformset_factory(parent_model, model, form=ModelForm,
    548568                          formset=BaseInlineFormSet, fk_name=None,
    549                           fields=None, exclude=None, fieldsets=None,
     569                          fields=None, exclude=None, fieldsets=None, inlines=None,
    550570                          extra=3, can_order=False, can_delete=True, max_num=0,
    551571                          formfield_callback=lambda f: f.formfield()):
    552572    """
     
    574594        'fieldsets': fieldsets,
    575595        'fields': fields,
    576596        'exclude': exclude,
     597        'inlines': inlines,
    577598        'max_num': max_num,
    578599    }
    579600    FormSet = modelformset_factory(model, **kwargs)
Back to Top