Django

Code

Changeset 8756

Show
Ignore:
Timestamp:
08/31/08 04:49:55 (3 months ago)
Author:
brosner
Message:

Fixed handling of primary keys in model formsets. Model formsets should now work nicely with custom primary keys that are OneToOneField?, ForeignKey? and AutoField?. Added tests to handle each of them.

Fixes #8241, #8694, #8695 and #8719.

Thanks Karen Tracey, jonloyens, sciyoshi, semenov and magneto for tracking down various parts of this patch.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/trunk/django/contrib/admin/helpers.py

    r8698 r8756  
    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): 
  • django/trunk/django/db/models/fields/related.py

    r8723 r8756  
    685685    def contribute_to_related_class(self, cls, related): 
    686686        setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related)) 
    687  
     687         
    688688    def formfield(self, **kwargs): 
    689689        defaults = {'form_class': forms.ModelChoiceField, 'queryset': self.rel.to._default_manager.complex_filter(self.rel.limit_choices_to)} 
  • django/trunk/django/forms/models.py

    r8708 r8756  
    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``. 
     
    4040            continue 
    4141        if fields and f.name not in fields: 
     42            continue 
     43        if exclude and f.name in exclude: 
    4244            continue 
    4345        f.save_form_data(instance, cleaned_data[f.name]) 
     
    116118                # MultipleChoiceWidget needs a list of pks, not object instances. 
    117119                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) 
    120120        else: 
    121121            data[f.name] = f.value_from_object(instance) 
     
    262262    def save_new(self, form, commit=True): 
    263263        """Saves and returns a new model instance for the given form.""" 
    264         return save_instance(form, self.model(), commit=commit) 
     264        return save_instance(form, self.model(), exclude=[self._pk_field.name], commit=commit) 
    265265 
    266266    def save_existing(self, form, instance, commit=True): 
    267267        """Saves and returns an existing model instance for the given form.""" 
    268         return save_instance(form, instance, commit=commit) 
     268        return save_instance(form, instance, exclude=[self._pk_field.name], commit=commit) 
    269269 
    270270    def save(self, commit=True): 
     
    292292        saved_instances = [] 
    293293        for form in self.initial_forms: 
    294             obj = existing_objects[form.cleaned_data[self.model._meta.pk.attname]] 
     294            obj = existing_objects[form.cleaned_data[self._pk_field.name]] 
    295295            if self.can_delete and form.cleaned_data[DELETION_FIELD_NAME]: 
    296296                self.deleted_objects.append(obj) 
     
    320320    def add_fields(self, form, index): 
    321321        """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) 
     322        from django.db.models import AutoField 
     323        self._pk_field = pk = self.model._meta.pk 
     324        if pk.auto_created or isinstance(pk, AutoField): 
     325            form.fields[self._pk_field.name] = IntegerField(required=False, widget=HiddenInput) 
    325326        super(BaseModelFormSet, self).add_fields(form, index) 
    326327 
     
    370371        kwargs = {self.fk.get_attname(): self.instance.pk} 
    371372        new_obj = self.model(**kwargs) 
    372         return save_instance(form, new_obj, commit=commit) 
     373        return save_instance(form, new_obj, exclude=[self._pk_field.name], commit=commit) 
     374     
     375    def add_fields(self, form, index): 
     376        super(BaseInlineFormSet, self).add_fields(form, index) 
     377        if self._pk_field == self.fk: 
     378            form.fields[self._pk_field.name] = IntegerField(required=False, widget=HiddenInput) 
    373379 
    374380def _get_foreign_key(parent_model, model, fk_name=None): 
  • django/trunk/tests/modeltests/model_formsets/models.py

    r8708 r8756  
    4848 
    4949class Owner(models.Model): 
     50    auto_id = models.AutoField(primary_key=True) 
    5051    name = models.CharField(max_length=100) 
    5152    place = models.ForeignKey(Place) 
     53     
     54    def __unicode__(self): 
     55        return "%s at %s" % (self.name, self.place) 
     56 
     57class OwnerProfile(models.Model): 
     58    owner = models.OneToOneField(Owner, primary_key=True) 
     59    age = models.PositiveIntegerField() 
     60     
     61    def __unicode__(self): 
     62        return "%s is %d" % (self.owner.name, self.age) 
    5263 
    5364class Restaurant(Place): 
     
    263274...     print form.as_p() 
    264275<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> 
     276<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> 
    266277 
    267278>>> data = { 
    268279...     'form-TOTAL_FORMS': '1', # the number of forms rendered 
    269280...     'form-INITIAL_FORMS': '0', # the number of forms with initial data 
    270 ...     'form-0-author_ptr_id': '', 
     281...     'form-0-author_ptr': '', 
    271282...     'form-0-name': 'Ernest Hemingway', 
    272283...     'form-0-write_speed': '10', 
     
    284295...     print form.as_p() 
    285296<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> 
     297<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> 
    287298<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> 
     299<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> 
    289300 
    290301>>> data = { 
    291302...     'form-TOTAL_FORMS': '2', # the number of forms rendered 
    292303...     'form-INITIAL_FORMS': '1', # the number of forms with initial data 
    293 ...     'form-0-author_ptr_id': hemingway_id, 
     304...     'form-0-author_ptr': hemingway_id, 
    294305...     'form-0-name': 'Ernest Hemingway', 
    295306...     'form-0-write_speed': '10', 
    296 ...     'form-1-author_ptr_id': '', 
     307...     'form-1-author_ptr': '', 
    297308...     'form-1-name': '', 
    298309...     'form-1-write_speed': '', 
     
    420431<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> 
    421432 
     433# Custom primary keys with ForeignKey, OneToOneField and AutoField ############ 
     434 
     435>>> place = Place(name=u'Giordanos', city=u'Chicago') 
     436>>> place.save() 
     437 
     438>>> FormSet = inlineformset_factory(Place, Owner, extra=2, can_delete=False) 
     439>>> formset = FormSet(instance=place) 
     440>>> for form in formset.forms: 
     441...     print form.as_p() 
     442<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" /><input type="hidden" name="owner_set-0-auto_id" id="id_owner_set-0-auto_id" /></p> 
     443<p><label for="id_owner_set-1-name">Name:</label> <input id="id_owner_set-1-name" type="text" name="owner_set-1-name" maxlength="100" /><input type="hidden" name="owner_set-1-auto_id" id="id_owner_set-1-auto_id" /></p> 
     444 
     445>>> data = { 
     446...     'owner_set-TOTAL_FORMS': '2', 
     447...     'owner_set-INITIAL_FORMS': '0', 
     448...     'owner_set-0-auto_id': '', 
     449...     'owner_set-0-name': u'Joe Perry', 
     450...     'owner_set-1-auto_id': '', 
     451...     'owner_set-1-name': '', 
     452... } 
     453>>> formset = FormSet(data, instance=place) 
     454>>> formset.is_valid() 
     455True 
     456>>> formset.save() 
     457[<Owner: Joe Perry at Giordanos>] 
     458 
     459>>> formset = FormSet(instance=place) 
     460>>> for form in formset.forms: 
     461...     print form.as_p() 
     462<p><label for="id_owner_set-0-name">Name:</label> <input id="id_owner_set-0-name" type="text" name="owner_set-0-name" value="Joe Perry" maxlength="100" /><input type="hidden" name="owner_set-0-auto_id" value="1" id="id_owner_set-0-auto_id" /></p> 
     463<p><label for="id_owner_set-1-name">Name:</label> <input id="id_owner_set-1-name" type="text" name="owner_set-1-name" maxlength="100" /><input type="hidden" name="owner_set-1-auto_id" id="id_owner_set-1-auto_id" /></p> 
     464<p><label for="id_owner_set-2-name">Name:</label> <input id="id_owner_set-2-name" type="text" name="owner_set-2-name" maxlength="100" /><input type="hidden" name="owner_set-2-auto_id" id="id_owner_set-2-auto_id" /></p> 
     465 
     466>>> data = { 
     467...     'owner_set-TOTAL_FORMS': '3', 
     468...     'owner_set-INITIAL_FORMS': '1', 
     469...     'owner_set-0-auto_id': u'1', 
     470...     'owner_set-0-name': u'Joe Perry', 
     471...     'owner_set-1-auto_id': '', 
     472...     'owner_set-1-name': u'Jack Berry', 
     473...     'owner_set-2-auto_id': '', 
     474...     'owner_set-2-name': '', 
     475... } 
     476>>> formset = FormSet(data, instance=place) 
     477>>> formset.is_valid() 
     478True 
     479>>> formset.save() 
     480[<Owner: Jack Berry at Giordanos>] 
     481 
     482# Ensure a custom primary key that is a ForeignKey or OneToOneField get rendered for the user to choose. 
     483 
     484>>> FormSet = modelformset_factory(OwnerProfile) 
     485>>> formset = FormSet() 
     486>>> for form in formset.forms: 
     487...     print form.as_p() 
     488<p><label for="id_form-0-owner">Owner:</label> <select name="form-0-owner" id="id_form-0-owner"> 
     489<option value="" selected="selected">---------</option> 
     490<option value="1">Joe Perry at Giordanos</option> 
     491<option value="2">Jack Berry at Giordanos</option> 
     492</select></p> 
     493<p><label for="id_form-0-age">Age:</label> <input type="text" name="form-0-age" id="id_form-0-age" /></p> 
     494 
     495>>> owner = Owner.objects.get(name=u'Joe Perry') 
     496>>> FormSet = inlineformset_factory(Owner, OwnerProfile, max_num=1, can_delete=False) 
     497 
     498>>> formset = FormSet(instance=owner) 
     499>>> for form in formset.forms: 
     500...     print form.as_p() 
     501<p><label for="id_ownerprofile-0-age">Age:</label> <input type="text" name="ownerprofile-0-age" id="id_ownerprofile-0-age" /><input type="hidden" name="ownerprofile-0-owner" id="id_ownerprofile-0-owner" /></p> 
     502 
     503>>> data = { 
     504...     'ownerprofile-TOTAL_FORMS': '1', 
     505...     'ownerprofile-INITIAL_FORMS': '0', 
     506...     'ownerprofile-0-owner': '', 
     507...     'ownerprofile-0-age': u'54', 
     508... } 
     509>>> formset = FormSet(data, instance=owner) 
     510>>> formset.is_valid() 
     511True 
     512>>> formset.save() 
     513[<OwnerProfile: Joe Perry is 54>] 
     514 
     515>>> formset = FormSet(instance=owner) 
     516>>> for form in formset.forms: 
     517...     print form.as_p() 
     518<p><label for="id_ownerprofile-0-age">Age:</label> <input type="text" name="ownerprofile-0-age" value="54" id="id_ownerprofile-0-age" /><input type="hidden" name="ownerprofile-0-owner" value="1" id="id_ownerprofile-0-owner" /></p> 
     519 
     520>>> data = { 
     521...     'ownerprofile-TOTAL_FORMS': '1', 
     522...     'ownerprofile-INITIAL_FORMS': '1', 
     523...     'ownerprofile-0-owner': u'1', 
     524...     'ownerprofile-0-age': u'55', 
     525... } 
     526>>> formset = FormSet(data, instance=owner) 
     527>>> formset.is_valid() 
     528True 
     529>>> formset.save() 
     530[<OwnerProfile: Joe Perry is 55>] 
     531 
    422532# Foreign keys in parents ######################################## 
    423533 
  • django/trunk/tests/modeltests/model_forms/models.py

    r8682 r8756  
    6969class BetterWriter(Writer): 
    7070    pass 
     71 
     72class WriterProfile(models.Model): 
     73    writer = models.OneToOneField(Writer, primary_key=True) 
     74    age = models.PositiveIntegerField() 
     75     
     76    def __unicode__(self): 
     77        return "%s is %s" % (self.writer, self.age) 
    7178 
    7279class PhoneNumber(models.Model): 
     
    812819>>> bw.save() 
    813820>>> sorted(model_to_dict(bw).keys()) 
    814 ['id', 'name', 'writer_ptr_id'] 
     821['id', 'name', 'writer_ptr'] 
     822 
     823>>> class WriterProfileForm(ModelForm): 
     824...     class Meta: 
     825...         model = WriterProfile 
     826>>> form = WriterProfileForm() 
     827>>> print form.as_p() 
     828<p><label for="id_writer">Writer:</label> <select name="writer" id="id_writer"> 
     829<option value="" selected="selected">---------</option> 
     830<option value="1">Mike Royko</option> 
     831<option value="2">Bob Woodward</option> 
     832<option value="3">Carl Bernstein</option> 
     833<option value="4">Joe Better</option> 
     834</select></p> 
     835<p><label for="id_age">Age:</label> <input type="text" name="age" id="id_age" /></p> 
     836 
     837>>> data = { 
     838...     'writer': u'2', 
     839...     'age': u'65', 
     840... } 
     841>>> form = WriterProfileForm(data) 
     842>>> instance = form.save() 
     843>>> instance 
     844<WriterProfile: Bob Woodward is 65> 
     845 
     846>>> form = WriterProfileForm(instance=instance) 
     847>>> print form.as_p() 
     848<p><label for="id_writer">Writer:</label> <select name="writer" id="id_writer"> 
     849<option value="">---------</option> 
     850<option value="1">Mike Royko</option> 
     851<option value="2" selected="selected">Bob Woodward</option> 
     852<option value="3">Carl Bernstein</option> 
     853<option value="4">Joe Better</option> 
     854</select></p> 
     855<p><label for="id_age">Age:</label> <input type="text" name="age" value="65" id="id_age" /></p> 
    815856 
    816857# PhoneNumberField ############################################################