Ticket #8719: primary_key_formset_fixes.diff

File primary_key_formset_fixes.diff, 10.5 KB (added by Brian Rosner, 16 years ago)

rough patch.

  • 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..3e3d56c 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        if pk.auto_created:
     332            self._pk_field = self.model._meta.pk
     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..746cfbb 100644
    a b used.  
    262262>>> for form in formset.forms:
    263263...     print form.as_p()
    264264<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>
     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_form-0-author_ptr" /></p>
    266266
    267267>>> data = {
    268268...     'form-TOTAL_FORMS': '1', # the number of forms rendered
    269269...     'form-INITIAL_FORMS': '0', # the number of forms with initial data
    270 ...     'form-0-author_ptr_id': '',
     270...     'form-0-author_ptr': '',
    271271...     'form-0-name': 'Ernest Hemingway',
    272272...     'form-0-write_speed': '10',
    273273... }
    True  
    283283>>> for form in formset.forms:
    284284...     print form.as_p()
    285285<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>
     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" value="..." id="id_form-0-author_ptr" /></p>
    287287<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>
     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_form-1-author_ptr" /></p>
    289289
    290290>>> data = {
    291291...     'form-TOTAL_FORMS': '2', # the number of forms rendered
    292292...     'form-INITIAL_FORMS': '1', # the number of forms with initial data
    293 ...     'form-0-author_ptr_id': hemingway_id,
     293...     'form-0-author_ptr': hemingway_id,
    294294...     'form-0-name': 'Ernest Hemingway',
    295295...     'form-0-write_speed': '10',
    296 ...     'form-1-author_ptr_id': '',
     296...     'form-1-author_ptr': '',
    297297...     'form-1-name': '',
    298298...     'form-1-write_speed': '',
    299299... }
Back to Top