Ticket #9284: 9284.diff
File 9284.diff, 16.7 KB (added by , 16 years ago) |
---|
-
django/forms/formsets.py
diff --git a/django/forms/formsets.py b/django/forms/formsets.py index 7a5cb8a..1960f21 100644
a b class BaseFormSet(StrAndUnicode): 40 40 self.error_class = error_class 41 41 self._errors = None 42 42 self._non_form_errors = None 43 # initialization is different depending on whether we recieved data, initial, or nothing44 if data or files:45 self.management_form = ManagementForm(data, auto_id=self.auto_id, prefix=self.prefix)46 if self.management_form.is_valid():47 self._total_form_count = self.management_form.cleaned_data[TOTAL_FORM_COUNT]48 self._initial_form_count = self.management_form.cleaned_data[INITIAL_FORM_COUNT]49 else:50 raise ValidationError('ManagementForm data is missing or has been tampered with')51 else:52 if initial:53 self._initial_form_count = len(initial)54 if self._initial_form_count > self.max_num and self.max_num > 0:55 self._initial_form_count = self.max_num56 self._total_form_count = self._initial_form_count + self.extra57 else:58 self._initial_form_count = 059 self._total_form_count = self.extra60 if self._total_form_count > self.max_num and self.max_num > 0:61 self._total_form_count = self.max_num62 initial = {TOTAL_FORM_COUNT: self._total_form_count,63 INITIAL_FORM_COUNT: self._initial_form_count}64 self.management_form = ManagementForm(initial=initial, auto_id=self.auto_id, prefix=self.prefix)65 66 43 # construct the forms in the formset 67 44 self._construct_forms() 68 45 69 46 def __unicode__(self): 70 47 return self.as_table() 71 48 49 def _management_form(self): 50 """Returns the ManagementForm instance for this FormSet.""" 51 if self.data or self.files: 52 form = ManagementForm(self.data, auto_id=self.auto_id, prefix=self.prefix) 53 if not form.is_valid(): 54 raise ValidationError('ManagementForm data is missing or has been tampered with') 55 else: 56 form = ManagementForm(auto_id=self.auto_id, prefix=self.prefix, initial={ 57 TOTAL_FORM_COUNT: self._total_forms(), 58 INITIAL_FORM_COUNT: self._initial_forms() 59 }) 60 return form 61 management_form = property(_management_form) 62 63 def _total_forms(self): 64 """Returns the total number of forms in this FormSet.""" 65 if self.data or self.files: 66 return self.management_form.cleaned_data[TOTAL_FORM_COUNT] 67 else: 68 total_forms = self._initial_forms() + self.extra 69 if total_forms > self.max_num > 0: 70 total_forms = self.max_num 71 return total_forms 72 _total_form_count = property(_total_forms) 73 74 def _initial_forms(self): 75 """Returns the number of forms that are required in this FormSet.""" 76 if self.data or self.files: 77 return self.management_form.cleaned_data[INITIAL_FORM_COUNT] 78 else: 79 # Use the length of the inital data if it's there, 0 otherwise. 80 initial_forms = self.initial and len(self.initial) or 0 81 if initial_forms > self.max_num > 0: 82 initial_forms = self.max_num 83 return initial_forms 84 _initial_form_count = property(_initial_forms) 85 72 86 def _construct_forms(self): 73 87 # instantiate all the forms and put them in self.forms 74 88 self.forms = [] … … class BaseFormSet(StrAndUnicode): 97 111 return form 98 112 99 113 def _get_initial_forms(self): 100 """Return a list of all the in tial forms in this formset."""114 """Return a list of all the initial forms in this formset.""" 101 115 return self.forms[:self._initial_form_count] 102 116 initial_forms = property(_get_initial_forms) 103 117 -
django/forms/models.py
diff --git a/django/forms/models.py b/django/forms/models.py index d62a2ce..02780b6 100644
a b def save_instance(form, instance, fields=None, fail_message='saved', 54 54 # callable upload_to can use the values from other fields. 55 55 if isinstance(f, models.FileField): 56 56 file_field_list.append(f) 57 # OneToOneField doesn't allow assignment of None. Guard against that 58 # instead of allowing it and throwing an error. 59 if isinstance(f, models.OneToOneField) and cleaned_data[f.name] is None: 60 pass 57 61 else: 58 62 f.save_form_data(instance, cleaned_data[f.name]) 59 63 … … class BaseModelForm(BaseForm): 266 270 267 271 lookup_kwargs = {} 268 272 for field_name in unique_check: 269 lookup_kwargs[field_name] = self.cleaned_data[field_name] 273 lookup_value = self.cleaned_data[field_name] 274 # ModelChoiceField will return an object instance rather than 275 # a raw primary key value, so convert it to a pk value before 276 # using it in a lookup. 277 if isinstance(self.fields[field_name], ModelChoiceField): 278 lookup_value = lookup_value.pk 279 lookup_kwargs[field_name] = lookup_value 270 280 271 281 qs = self.instance.__class__._default_manager.filter(**lookup_kwargs) 272 282 … … class BaseModelFormSet(BaseFormSet): 357 367 queryset=None, **kwargs): 358 368 self.queryset = queryset 359 369 defaults = {'data': data, 'files': files, 'auto_id': auto_id, 'prefix': prefix} 360 defaults['initial'] = [model_to_dict(obj) for obj in self.get_queryset()]361 370 defaults.update(kwargs) 362 371 super(BaseModelFormSet, self).__init__(**defaults) 363 372 373 def _initial_forms(self): 374 """Returns the number of forms that are required in this FormSet.""" 375 if not (self.data or self.files): 376 return len(self.get_queryset()) 377 return super(BaseModelFormSet, self)._initial_forms() 378 _initial_form_count = property(_initial_forms) 379 364 380 def _construct_form(self, i, **kwargs): 365 381 if i < self._initial_form_count: 366 382 kwargs['instance'] = self.get_queryset()[i] … … class BaseModelFormSet(BaseFormSet): 380 396 381 397 def save_new(self, form, commit=True): 382 398 """Saves and returns a new model instance for the given form.""" 383 return save_instance(form, self.model(), exclude=[self._pk_field.name],commit=commit)399 return form.save(commit=commit) 384 400 385 401 def save_existing(self, form, instance, commit=True): 386 402 """Saves and returns an existing model instance for the given form.""" 387 return save_instance(form, instance, exclude=[self._pk_field.name],commit=commit)403 return form.save(commit=commit) 388 404 389 405 def save(self, commit=True): 390 406 """Saves model instances for every form, adding and changing instances … … class BaseModelFormSet(BaseFormSet): 410 426 existing_objects[obj.pk] = obj 411 427 saved_instances = [] 412 428 for form in self.initial_forms: 413 obj = existing_objects[form.cleaned_data[self._pk_field.name] ]429 obj = existing_objects[form.cleaned_data[self._pk_field.name].pk] 414 430 if self.can_delete and form.cleaned_data[DELETION_FIELD_NAME]: 415 431 self.deleted_objects.append(obj) 416 432 obj.delete() … … class BaseModelFormSet(BaseFormSet): 438 454 439 455 def add_fields(self, form, index): 440 456 """Add a hidden field for the object's primary key.""" 441 from django.db.models import AutoField 457 from django.db.models import AutoField, OneToOneField, ForeignKey 442 458 self._pk_field = pk = self.model._meta.pk 443 459 if pk.auto_created or isinstance(pk, AutoField): 444 form.fields[self._pk_field.name] = IntegerField(required=False, widget=HiddenInput) 460 # The pk field we're adding to the form won't be able to get its 461 # value from the ModelForm's instance argument, so provide an 462 # initial value here. 463 try: 464 pk_value = self.get_queryset()[index].pk 465 except IndexError: 466 pk_value = None 467 if isinstance(pk, OneToOneField) or isinstance(pk, ForeignKey): 468 qs = pk.rel.to._default_manager.get_query_set() 469 else: 470 qs = self.model._default_manager.get_query_set() 471 form.fields[self._pk_field.name] = ModelChoiceField(qs, initial=pk_value, required=False, widget=HiddenInput) 445 472 super(BaseModelFormSet, self).add_fields(form, index) 446 473 447 474 def modelformset_factory(model, form=ModelForm, formfield_callback=lambda f: f.formfield(), … … class BaseInlineFormSet(BaseModelFormSet): 477 504 super(BaseInlineFormSet, self).__init__(data, files, prefix=prefix, 478 505 queryset=qs) 479 506 480 def _ construct_forms(self):507 def _initial_forms(self): 481 508 if self.save_as_new: 482 self._total_form_count = self._initial_form_count 483 self._initial_form_count = 0 484 super(BaseInlineFormSet, self)._construct_forms() 509 return 0 510 return super(BaseInlineFormSet, self)._initial_forms() 511 _initial_form_count = property(_initial_forms) 512 513 def _total_forms(self): 514 if self.save_as_new: 515 return super(BaseInlineFormSet, self)._initial_forms() 516 return super(BaseInlineFormSet, self)._total_forms() 517 _total_form_count = property(_total_forms) 485 518 486 519 def _construct_form(self, i, **kwargs): 487 520 form = super(BaseInlineFormSet, self)._construct_form(i, **kwargs) … … class BaseInlineFormSet(BaseModelFormSet): 498 531 get_default_prefix = classmethod(get_default_prefix) 499 532 500 533 def save_new(self, form, commit=True): 501 fk_attname = self.fk.get_attname() 502 kwargs = {fk_attname: self.instance.pk} 503 new_obj = self.model(**kwargs) 504 if fk_attname == self._pk_field.attname or self._pk_field.auto_created: 505 exclude = [self._pk_field.name] 506 else: 507 exclude = [] 508 return save_instance(form, new_obj, exclude=exclude, commit=commit) 534 # Use commit=False so we can assign the parent key afterwards, then 535 # save the object. 536 obj = form.save(commit=False) 537 setattr(obj, self.fk.get_attname(), self.instance.pk) 538 obj.save() 539 # form.save_m2m() can be called via the formset later on if commit=False 540 if commit and hasattr(form, 'save_m2m'): 541 form.save_m2m() 542 return obj 509 543 510 544 def add_fields(self, form, index): 511 545 super(BaseInlineFormSet, self).add_fields(form, index) … … class InlineForeignKeyField(Field): 620 654 # ensure the we compare the values as equal types. 621 655 if force_unicode(value) != force_unicode(self.parent_instance.pk): 622 656 raise ValidationError(self.error_messages['invalid_choice']) 623 if self.pk_field:624 return self.parent_instance.pk625 657 return self.parent_instance 626 658 627 659 class ModelChoiceIterator(object): -
tests/modeltests/model_formsets/models.py
diff --git a/tests/modeltests/model_formsets/models.py b/tests/modeltests/model_formsets/models.py index 0503ea2..d8cbe34 100644
a b 1 2 1 import datetime 3 4 2 from django import forms 5 3 from django.db import models 6 4 … … class Book(models.Model): 27 25 28 26 def __unicode__(self): 29 27 return self.title 30 28 31 29 class BookWithCustomPK(models.Model): 32 30 my_pk = models.DecimalField(max_digits=5, decimal_places=0, primary_key=True) 33 31 author = models.ForeignKey(Author) … … class BookWithCustomPK(models.Model): 35 33 36 34 def __unicode__(self): 37 35 return u'%s: %s' % (self.my_pk, self.title) 38 36 39 37 class AlternateBook(Book): 40 38 notes = models.CharField(max_length=100) 41 39 42 40 def __unicode__(self): 43 41 return u'%s - %s' % (self.title, self.notes) 44 42 45 43 class AuthorMeeting(models.Model): 46 44 name = models.CharField(max_length=100) 47 45 authors = models.ManyToManyField(Author) … … class Owner(models.Model): 68 66 auto_id = models.AutoField(primary_key=True) 69 67 name = models.CharField(max_length=100) 70 68 place = models.ForeignKey(Place) 71 69 72 70 def __unicode__(self): 73 71 return "%s at %s" % (self.name, self.place) 74 72 … … class Location(models.Model): 81 79 class OwnerProfile(models.Model): 82 80 owner = models.OneToOneField(Owner, primary_key=True) 83 81 age = models.PositiveIntegerField() 84 82 85 83 def __unicode__(self): 86 84 return "%s is %d" % (self.owner.name, self.age) 87 85 … … class MexicanRestaurant(Restaurant): 114 112 # using inlineformset_factory. 115 113 class Repository(models.Model): 116 114 name = models.CharField(max_length=25) 117 115 118 116 def __unicode__(self): 119 117 return self.name 120 118 121 119 class Revision(models.Model): 122 120 repository = models.ForeignKey(Repository) 123 121 revision = models.CharField(max_length=40) 124 122 125 123 class Meta: 126 124 unique_together = (("repository", "revision"),) 127 125 128 126 def __unicode__(self): 129 127 return u"%s (%s)" % (self.revision, unicode(self.repository)) 130 128 … … class Team(models.Model): 146 144 class Player(models.Model): 147 145 team = models.ForeignKey(Team, null=True) 148 146 name = models.CharField(max_length=100) 149 147 148 def __unicode__(self): 149 return self.name 150 151 # Models for testing custom ModelForm save methods in formsets and inline formsets 152 class Poet(models.Model): 153 name = models.CharField(max_length=100) 154 155 def __unicode__(self): 156 return self.name 157 158 class Poem(models.Model): 159 poet = models.ForeignKey(Poet) 160 name = models.CharField(max_length=100) 161 150 162 def __unicode__(self): 151 163 return self.name 152 164 … … used. 337 349 338 350 >>> AuthorFormSet = modelformset_factory(Author, max_num=2) 339 351 >>> formset = AuthorFormSet(queryset=qs) 340 >>> [ sorted(x.items()) for x in formset.initial]341 [ [('id', 1), ('name', u'Charles Baudelaire')], [('id', 3), ('name', u'Paul Verlaine')]]352 >>> [x.name for x in formset.get_queryset()] 353 [u'Charles Baudelaire', u'Paul Verlaine'] 342 354 343 355 >>> AuthorFormSet = modelformset_factory(Author, max_num=3) 344 356 >>> formset = AuthorFormSet(queryset=qs) 345 >>> [sorted(x.items()) for x in formset.initial] 346 [[('id', 1), ('name', u'Charles Baudelaire')], [('id', 3), ('name', u'Paul Verlaine')], [('id', 2), ('name', u'Walt Whitman')]] 357 >>> [x.name for x in formset.get_queryset()] 358 [u'Charles Baudelaire', u'Paul Verlaine', u'Walt Whitman'] 359 360 361 # ModelForm with a custom save method in a formset ########################### 362 363 >>> class PoetForm(forms.ModelForm): 364 ... def save(self, commit=True): 365 ... # change the name to "Vladimir Mayakovsky" just to be a jerk. 366 ... author = super(PoetForm, self).save(commit=False) 367 ... author.name = u"Vladimir Mayakovsky" 368 ... if commit: 369 ... author.save() 370 ... return author 371 372 >>> PoetFormSet = modelformset_factory(Poet, form=PoetForm) 373 374 >>> data = { 375 ... 'form-TOTAL_FORMS': '3', # the number of forms rendered 376 ... 'form-INITIAL_FORMS': '0', # the number of forms with initial data 377 ... 'form-0-name': 'Walt Whitman', 378 ... 'form-1-name': 'Charles Baudelaire', 379 ... 'form-2-name': '', 380 ... } 381 382 >>> qs = Poet.objects.all() 383 >>> formset = PoetFormSet(data=data, queryset=qs) 384 >>> formset.is_valid() 385 True 386 387 >>> formset.save() 388 [<Poet: Vladimir Mayakovsky>, <Poet: Vladimir Mayakovsky>] 389 347 390 348 391 # Model inheritance in model formsets ######################################## 349 392 … … True 553 596 [<AlternateBook: Flowers of Evil - English translation of Les Fleurs du Mal>] 554 597 555 598 599 # ModelForm with a custom save method in an inline formset ################### 600 601 >>> class PoemForm(forms.ModelForm): 602 ... def save(self, commit=True): 603 ... # change the name to "Brooklyn Bridge" just to be a jerk. 604 ... poem = super(PoemForm, self).save(commit=False) 605 ... poem.name = u"Brooklyn Bridge" 606 ... if commit: 607 ... poem.save() 608 ... return poem 609 610 >>> PoemFormSet = inlineformset_factory(Poet, Poem, form=PoemForm) 611 612 >>> data = { 613 ... 'poem_set-TOTAL_FORMS': '3', # the number of forms rendered 614 ... 'poem_set-INITIAL_FORMS': '0', # the number of forms with initial data 615 ... 'poem_set-0-name': 'The Cloud in Trousers', 616 ... 'poem_set-1-name': 'I', 617 ... 'poem_set-2-name': '', 618 ... } 619 620 >>> poet = Poet.objects.create(name='Vladimir Mayakovsky') 621 >>> formset = PoemFormSet(data=data, instance=poet) 622 >>> formset.is_valid() 623 True 624 625 >>> formset.save() 626 [<Poem: Brooklyn Bridge>, <Poem: Brooklyn Bridge>] 627 628 556 629 # Test a custom primary key ################################################### 557 630 558 631 We need to ensure that it is displayed