Ticket #8719: primary_key_formset_fixes.2.diff

File primary_key_formset_fixes.2.diff, 12.3 KB (added by brosner, 7 years ago)

improved

  • django/contrib/admin/helpers.py

    diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py
    index 7a60943..56b4f56 100644
    a b class InlineAdminForm(AdminForm): 
    126126        super(InlineAdminForm, self).__init__(form, fieldsets, prepopulated_fields)
    127127
    128128    def pk_field(self):
    129         return AdminField(self.form, self.formset._pk_field_name, False)
     129        return AdminField(self.form, self.formset._pk_field.name, False)
    130130
    131131    def deletion_field(self):
    132132        from django.forms.formsets import DELETION_FIELD_NAME
  • django/forms/models.py

    diff --git a/django/forms/models.py b/django/forms/models.py
    index bd9597d..3d551b8 100644
    a b __all__ = ( 
    2121)
    2222
    2323def save_instance(form, instance, fields=None, fail_message='saved',
    24                   commit=True):
     24                  commit=True, exclude=None):
    2525    """
    2626    Saves bound Form ``form``'s cleaned_data into model instance ``instance``.
    2727
    def save_instance(form, instance, fields=None, fail_message='saved', 
    3636    cleaned_data = form.cleaned_data
    3737    for f in opts.fields:
    3838        if not f.editable or isinstance(f, models.AutoField) \
     39                or (isinstance(f, models.OneToOneField) and f.rel.parent_link) \
    3940                or not f.name in cleaned_data:
    4041            continue
    4142        if fields and f.name not in fields:
    4243            continue
     44        if exclude and f.name in exclude:
     45            continue
    4346        f.save_form_data(instance, cleaned_data[f.name])
    4447    # Wrap up the saving of m2m data as a function.
    4548    def save_m2m():
    def model_to_dict(instance, fields=None, exclude=None): 
    115118            else:
    116119                # MultipleChoiceWidget needs a list of pks, not object instances.
    117120                data[f.name] = [obj.pk for obj in f.value_from_object(instance)]
    118         elif isinstance(f, OneToOneField):
    119             data[f.attname] = f.value_from_object(instance)
    120121        else:
    121122            data[f.name] = f.value_from_object(instance)
    122123    return data
    class BaseModelFormSet(BaseFormSet): 
    291292            existing_objects[obj.pk] = obj
    292293        saved_instances = []
    293294        for form in self.initial_forms:
    294             obj = existing_objects[form.cleaned_data[self.model._meta.pk.attname]]
     295            cleaned_pk = form.cleaned_data[self._pk_field.name]
     296            if hasattr(cleaned_pk, "pk"):
     297                obj = existing_objects[cleaned_pk.pk]
     298            else:
     299                obj = existing_objects[cleaned_pk]
    295300            if self.can_delete and form.cleaned_data[DELETION_FIELD_NAME]:
    296301                self.deleted_objects.append(obj)
    297302                obj.delete()
    class BaseModelFormSet(BaseFormSet): 
    319324
    320325    def add_fields(self, form, index):
    321326        """Add a hidden field for the object's primary key."""
    322         if self.model._meta.pk.auto_created:
    323             self._pk_field_name = self.model._meta.pk.attname
    324             form.fields[self._pk_field_name] = IntegerField(required=False, widget=HiddenInput)
     327        from django.db.models import OneToOneField
     328        pk = self.model._meta.pk
     329        while isinstance(pk, OneToOneField):
     330            pk = pk.rel.to._meta.pk # drill down until we find a "real" pk
     331        self._pk_field = self.model._meta.pk
     332        if pk.auto_created:
     333            form.fields[self._pk_field.name] = IntegerField(required=False, widget=HiddenInput)
    325334        super(BaseModelFormSet, self).add_fields(form, index)
    326335
    327336def modelformset_factory(model, form=ModelForm, formfield_callback=lambda f: f.formfield(),
    class BaseInlineFormSet(BaseModelFormSet): 
    369378    def save_new(self, form, commit=True):
    370379        kwargs = {self.fk.get_attname(): self.instance.pk}
    371380        new_obj = self.model(**kwargs)
    372         return save_instance(form, new_obj, commit=commit)
     381        exclude = []
     382        if self.fk == self._pk_field:
     383            exclude.append(self.fk.name)
     384        return save_instance(form, new_obj, exclude=exclude, commit=commit)
     385   
     386    def add_fields(self, form, index):
     387        from django.db.models import OneToOneField
     388        super(BaseInlineFormSet, self).add_fields(form, index)
     389        if isinstance(self._pk_field, OneToOneField):
     390            queryset = self.instance.__class__._default_manager.all()
     391            form.fields[self._pk_field.name] = ModelChoiceField(queryset,
     392                required=False, widget=HiddenInput)
    373393
    374394def _get_foreign_key(parent_model, model, fk_name=None):
    375395    """
    def inlineformset_factory(parent_model, model, form=ModelForm, 
    419439    """
    420440    fk = _get_foreign_key(parent_model, model, fk_name=fk_name)
    421441    # let the formset handle object deletion by default
    422 
    423     if exclude is not None:
    424         exclude.append(fk.name)
    425     else:
    426         exclude = [fk.name]
     442   
     443    def _formfield_callback(f, **kwargs):
     444        if fk == model._meta.pk:
     445            if f.primary_key:
     446                return None
     447        return formfield_callback(f, **kwargs)
     448   
     449    if fk != model._meta.pk:
     450        if exclude is not None:
     451            exclude.append(fk.name)
     452        else:
     453            exclude = [fk.name]
     454   
    427455    FormSet = modelformset_factory(model, form=form,
    428                                     formfield_callback=formfield_callback,
     456                                    formfield_callback=_formfield_callback,
    429457                                    formset=formset,
    430458                                    extra=extra, can_delete=can_delete, can_order=can_order,
    431459                                    fields=fields, exclude=exclude, max_num=max_num)
  • tests/modeltests/model_forms/models.py

    diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py
    index b8d3f93..bff869f 100644
    a b class ImprovedArticle(models.Model): 
    6666class ImprovedArticleWithParentLink(models.Model):
    6767    article = models.OneToOneField(Article, parent_link=True)
    6868
     69class ImprovedArticleWithOneToOnePK(models.Model):
     70    article = models.OneToOneField(Article, primary_key=True)
     71   
     72    def __unicode__(self):
     73        return self.article.headline
     74
    6975class BetterWriter(Writer):
    7076    pass
    7177
    ValidationError: [u'Select a valid choice. 4 is not one of the available choices 
    811817>>> bw = BetterWriter(name=u'Joe Better')
    812818>>> bw.save()
    813819>>> sorted(model_to_dict(bw).keys())
    814 ['id', 'name', 'writer_ptr_id']
     820['id', 'name', 'writer_ptr']
     821
     822>>> class ImprovedArticleWithOneToOnePKForm(ModelForm):
     823...     class Meta:
     824...         model = ImprovedArticleWithOneToOnePK
     825
     826>>> print ImprovedArticleWithOneToOnePKForm().as_p()
     827<p><label for="id_article">Article:</label> <select name="article" id="id_article">
     828<option value="" selected="selected">---------</option>
     829<option value="1">New headline</option>
     830<option value="2">The walrus was Paul</option>
     831<option value="3">The walrus was Paul</option>
     832<option value="4">The walrus was Paul</option>
     833</select></p>
     834
     835>>> form = ImprovedArticleWithOneToOnePKForm(data={'article': u'1'})
     836>>> instance = form.save()
     837>>> instance
     838<ImprovedArticleWithOneToOnePK: New headline>
     839
     840>>> form = ImprovedArticleWithOneToOnePKForm(instance=instance)
     841>>> print form.as_p()
     842<p><label for="id_article">Article:</label> <select name="article" id="id_article">
     843<option value="">---------</option>
     844<option value="1" selected="selected">New headline</option>
     845<option value="2">The walrus was Paul</option>
     846<option value="3">The walrus was Paul</option>
     847<option value="4">The walrus was Paul</option>
     848</select></p>
    815849
    816850# PhoneNumberField ############################################################
    817851
  • tests/modeltests/model_formsets/models.py

    diff --git a/tests/modeltests/model_formsets/models.py b/tests/modeltests/model_formsets/models.py
    index 65987c1..6b00701 100644
    a b class CustomPrimaryKey(models.Model): 
    3636    my_pk = models.CharField(max_length=10, primary_key=True)
    3737    some_field = models.CharField(max_length=100)
    3838
    39 
    4039# models for inheritance tests.
    4140
    4241class Place(models.Model):
    class Place(models.Model): 
    4746        return self.name
    4847
    4948class Owner(models.Model):
     49    auto_id = models.AutoField(primary_key=True)
    5050    name = models.CharField(max_length=100)
    5151    place = models.ForeignKey(Place)
     52   
     53    def __unicode__(self):
     54        return "%s at %s" % (self.name, self.place)
    5255
    5356class Restaurant(Place):
    5457    serves_pizza = models.BooleanField()
    used. 
    262265>>> for form in formset.forms:
    263266...     print form.as_p()
    264267<p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" maxlength="100" /></p>
    265 <p><label for="id_form-0-write_speed">Write speed:</label> <input type="text" name="form-0-write_speed" id="id_form-0-write_speed" /><input type="hidden" name="form-0-author_ptr_id" id="id_form-0-author_ptr_id" /></p>
     268<p><label for="id_form-0-write_speed">Write speed:</label> <input type="text" name="form-0-write_speed" id="id_form-0-write_speed" /><input type="hidden" name="form-0-author_ptr" id="id_form-0-author_ptr" /></p>
    266269
    267270>>> data = {
    268271...     'form-TOTAL_FORMS': '1', # the number of forms rendered
    269272...     'form-INITIAL_FORMS': '0', # the number of forms with initial data
    270 ...     'form-0-author_ptr_id': '',
     273...     'form-0-author_ptr': '',
    271274...     'form-0-name': 'Ernest Hemingway',
    272275...     'form-0-write_speed': '10',
    273276... }
    True 
    283286>>> for form in formset.forms:
    284287...     print form.as_p()
    285288<p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" value="Ernest Hemingway" maxlength="100" /></p>
    286 <p><label for="id_form-0-write_speed">Write speed:</label> <input type="text" name="form-0-write_speed" value="10" id="id_form-0-write_speed" /><input type="hidden" name="form-0-author_ptr_id" value="..." id="id_form-0-author_ptr_id" /></p>
     289<p><label for="id_form-0-write_speed">Write speed:</label> <input type="text" name="form-0-write_speed" value="10" id="id_form-0-write_speed" /><input type="hidden" name="form-0-author_ptr" value="..." id="id_form-0-author_ptr" /></p>
    287290<p><label for="id_form-1-name">Name:</label> <input id="id_form-1-name" type="text" name="form-1-name" maxlength="100" /></p>
    288 <p><label for="id_form-1-write_speed">Write speed:</label> <input type="text" name="form-1-write_speed" id="id_form-1-write_speed" /><input type="hidden" name="form-1-author_ptr_id" id="id_form-1-author_ptr_id" /></p>
     291<p><label for="id_form-1-write_speed">Write speed:</label> <input type="text" name="form-1-write_speed" id="id_form-1-write_speed" /><input type="hidden" name="form-1-author_ptr" id="id_form-1-author_ptr" /></p>
    289292
    290293>>> data = {
    291294...     'form-TOTAL_FORMS': '2', # the number of forms rendered
    292295...     'form-INITIAL_FORMS': '1', # the number of forms with initial data
    293 ...     'form-0-author_ptr_id': hemingway_id,
     296...     'form-0-author_ptr': hemingway_id,
    294297...     'form-0-name': 'Ernest Hemingway',
    295298...     'form-0-write_speed': '10',
    296 ...     'form-1-author_ptr_id': '',
     299...     'form-1-author_ptr': '',
    297300...     'form-1-name': '',
    298301...     'form-1-write_speed': '',
    299302... }
    We need to ensure that it is displayed 
    419422<p><label for="id_form-0-my_pk">My pk:</label> <input id="id_form-0-my_pk" type="text" name="form-0-my_pk" maxlength="10" /></p>
    420423<p><label for="id_form-0-some_field">Some field:</label> <input id="id_form-0-some_field" type="text" name="form-0-some_field" maxlength="100" /></p>
    421424
     425# aa ##########################################################################
     426
     427>>> place = Place(name=u'Giordanos', city=u'Chicago')
     428>>> place.save()
     429
     430>>> FormSet = inlineformset_factory(Place, Owner, extra=1, can_delete=False)
     431>>> formset = FormSet(instance=place)
     432>>> for form in formset.forms:
     433...     print form.as_p()
     434<p><label for="id_owner_set-0-name">Name:</label> <input id="id_owner_set-0-name" type="text" name="owner_set-0-name" maxlength="100" /></p>
     435
     436>>> data = {
     437...     'owner_set-TOTAL_FORMS': '1',
     438...     'owner_set-INITIAL_FORMS': '0',
     439...     'owner_set-0-name': u'Joe Perry',
     440... }
     441>>> formset = FormSet(data, instance=place)
     442>>> formset.is_valid()
     443True
     444>>> formset.save()
     445[<Owner: Joe Perry at Giordanos>]
     446
    422447# Foreign keys in parents ########################################
    423448
    424449>>> from django.forms.models import _get_foreign_key
Back to Top