Ticket #6241: formset_refactor_7125.diff
File formset_refactor_7125.diff, 49.5 KB (added by , 17 years ago) |
---|
-
django/contrib/admin/options.py
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index c3289ea..ee28cb4 100644
a b 1 1 from django import oldforms, template 2 2 from django import newforms as forms 3 3 from django.newforms.formsets import all_valid 4 from django.newforms.models import modelform_for_model, inline_formset 4 5 from django.contrib.contenttypes.models import ContentType 5 6 from django.contrib.admin import widgets 6 7 from django.contrib.admin.util import get_deleted_objects … … class ModelAdmin(BaseModelAdmin): 342 343 fields = flatten_fieldsets(self.declared_fieldsets) 343 344 else: 344 345 fields = None 345 return forms.form_for_model(self.model, fields=fields, formfield_callback=self.formfield_for_dbfield)346 return modelform_for_model(self.model, fields=fields, formfield_callback=self.formfield_for_dbfield) 346 347 347 348 def form_change(self, request, obj): 348 349 """ … … class ModelAdmin(BaseModelAdmin): 352 353 fields = flatten_fieldsets(self.declared_fieldsets) 353 354 else: 354 355 fields = None 355 return forms.form_for_instance(obj, fields=fields, formfield_callback=self.formfield_for_dbfield)356 return modelform_for_model(self.model, fields=fields, formfield_callback=self.formfield_for_dbfield) 356 357 357 358 def save_add(self, request, model, form, formsets, post_url_continue): 358 359 """ … … class ModelAdmin(BaseModelAdmin): 492 493 # Object list will give 'Permission Denied', so go back to admin home 493 494 post_url = '../../../' 494 495 495 ModelForm = self.form_add(request)496 Form = self.form_add(request) 496 497 inline_formsets = [] 497 498 obj = self.model() 498 499 if request.method == 'POST': 499 form = ModelForm(request.POST, request.FILES)500 form = Form(request.POST, request.FILES) 500 501 for FormSet in self.formsets_add(request): 501 inline_formset = FormSet( obj, data=request.POST, files=request.FILES)502 inline_formset = FormSet(request.POST, request.FILES) 502 503 inline_formsets.append(inline_formset) 503 504 if all_valid(inline_formsets) and form.is_valid(): 504 505 return self.save_add(request, model, form, inline_formsets, '../%s/') 505 506 else: 506 form = ModelForm(initial=request.GET)507 form = Form(initial=request.GET) 507 508 for FormSet in self.formsets_add(request): 508 inline_formset = FormSet( obj)509 inline_formset = FormSet() 509 510 inline_formsets.append(inline_formset) 510 511 511 512 adminForm = AdminForm(form, list(self.fieldsets_add(request)), self.prepopulated_fields) … … class ModelAdmin(BaseModelAdmin): 552 553 if request.POST and request.POST.has_key("_saveasnew"): 553 554 return self.add_view(request, form_url='../../add/') 554 555 555 ModelForm = self.form_change(request, obj)556 Form = self.form_change(request, obj) 556 557 inline_formsets = [] 557 558 if request.method == 'POST': 558 form = ModelForm(request.POST, request.FILES)559 form = Form(request.POST, request.FILES, instance=obj) 559 560 for FormSet in self.formsets_change(request, obj): 560 inline_formset = FormSet( obj, request.POST, request.FILES)561 inline_formset = FormSet(request.POST, request.FILES, instance=obj) 561 562 inline_formsets.append(inline_formset) 562 563 563 564 if all_valid(inline_formsets) and form.is_valid(): 564 565 return self.save_change(request, model, form, inline_formsets) 565 566 else: 566 form = ModelForm()567 form = Form(instance=obj, initial=request.GET) 567 568 for FormSet in self.formsets_change(request, obj): 568 inline_formset = FormSet( obj)569 inline_formset = FormSet(instance=obj) 569 570 inline_formsets.append(inline_formset) 570 571 571 572 ## Populate the FormWrapper. … … class InlineModelAdmin(BaseModelAdmin): 742 743 fields = flatten_fieldsets(self.declared_fieldsets) 743 744 else: 744 745 fields = None 745 return forms.inline_formset(self.parent_model, self.model, fk_name=self.fk_name, fields=fields, formfield_callback=self.formfield_for_dbfield, extra=self.extra)746 return inline_formset(self.parent_model, self.model, fk_name=self.fk_name, fields=fields, formfield_callback=self.formfield_for_dbfield, extra=self.extra) 746 747 747 748 def formset_change(self, request, obj): 748 749 """Returns an InlineFormSet class for use in admin change views.""" … … class InlineModelAdmin(BaseModelAdmin): 750 751 fields = flatten_fieldsets(self.declared_fieldsets) 751 752 else: 752 753 fields = None 753 return forms.inline_formset(self.parent_model, self.model, fk_name=self.fk_name, fields=fields, formfield_callback=self.formfield_for_dbfield, extra=self.extra)754 return inline_formset(self.parent_model, self.model, fk_name=self.fk_name, fields=fields, formfield_callback=self.formfield_for_dbfield, extra=self.extra) 754 755 755 756 def fieldsets_add(self, request): 756 757 if self.declared_fieldsets: 757 758 return self.declared_fieldsets 758 form = self.formset_add(request).form_class759 return [(None, {'fields': form .base_fields.keys()})]759 formset = self.formset_add(request) 760 return [(None, {'fields': formset.base_fields.keys()})] 760 761 761 762 def fieldsets_change(self, request, obj): 762 763 if self.declared_fieldsets: 763 764 return self.declared_fieldsets 764 form = self.formset_change(request, obj).form_class765 return [(None, {'fields': form .base_fields.keys()})]765 formset = self.formset_change(request, obj) 766 return [(None, {'fields': formset.base_fields.keys()})] 766 767 767 768 class StackedInline(InlineModelAdmin): 768 769 template = 'admin/edit_inline/stacked.html' … … class InlineAdminFormSet(object): 778 779 self.opts = inline 779 780 self.formset = formset 780 781 self.fieldsets = fieldsets 782 # place orderable and deletable here since _meta is inaccesible in the 783 # templates. 784 self.orderable = formset._meta.orderable 785 self.deletable = formset._meta.deletable 781 786 782 787 def __iter__(self): 783 788 for form, original in zip(self.formset.change_forms, self.formset.get_queryset()): … … class InlineAdminFormSet(object): 787 792 788 793 def fields(self): 789 794 for field_name in flatten_fieldsets(self.fieldsets): 790 yield self.formset. form_class.base_fields[field_name]795 yield self.formset.base_fields[field_name] 791 796 792 797 class InlineAdminForm(AdminForm): 793 798 """ -
django/contrib/admin/templates/admin/edit_inline/tabular.html
diff --git a/django/contrib/admin/templates/admin/edit_inline/tabular.html b/django/contrib/admin/templates/admin/edit_inline/tabular.html index 16bb14d..c91bb0e 100644
a b 11 11 <th {% if forloop.first %}colspan="2"{% endif %}>{{ field.label|capfirst|escape }}</th> 12 12 {% endif %} 13 13 {% endfor %} 14 {% if inline_admin_formset. formset.deletable %}<th>{% trans "Delete" %}?</th>{% endif %}14 {% if inline_admin_formset.deletable %}<th>{% trans "Delete" %}?</th>{% endif %} 15 15 </tr></thead> 16 16 17 17 {% for inline_admin_form in inline_admin_formset %} … … 45 45 {% endfor %} 46 46 {% endfor %} 47 47 48 {% if inline_admin_formset. formset.deletable %}<td class="delete">{{ inline_admin_form.deletion_field.field }}</td>{% endif %}48 {% if inline_admin_formset.deletable %}<td class="delete">{{ inline_admin_form.deletion_field.field }}</td>{% endif %} 49 49 50 50 </tr> 51 51 -
django/newforms/formsets.py
diff --git a/django/newforms/formsets.py b/django/newforms/formsets.py index 56179a9..0360dd9 100644
a b 1 from forms import Form 2 from fields import IntegerField, BooleanField 1 2 from warnings import warn 3 4 from django.utils.datastructures import SortedDict 5 from django.utils.translation import ugettext_lazy as _ 6 7 from forms import BaseForm, Form, get_declared_fields 8 from fields import Field, IntegerField, BooleanField 9 from options import FormSetOptions 3 10 from widgets import HiddenInput, Media 4 11 from util import ErrorList, ValidationError 5 12 6 __all__ = ('BaseFormSet', ' formset_for_form', 'all_valid')13 __all__ = ('BaseFormSet', 'FormSet', 'formset_for_form', 'all_valid') 7 14 8 15 # special field names 9 16 FORM_COUNT_FIELD_NAME = 'COUNT' … … class ManagementForm(Form): 20 27 self.base_fields[FORM_COUNT_FIELD_NAME] = IntegerField(widget=HiddenInput) 21 28 super(ManagementForm, self).__init__(*args, **kwargs) 22 29 30 class FormSetMetaclass(type): 31 def __new__(cls, name, bases, attrs, **options): 32 attrs["base_fields"] = get_declared_fields(bases, attrs) 33 attrs["_meta"] = FormSetOptions(options and options or attrs.get("Meta", None)) 34 return type.__new__(cls, name, bases, attrs) 35 23 36 class BaseFormSet(object): 24 37 """A collection of instances of the same Form class.""" 25 38 … … class BaseFormSet(object): 37 50 self.management_form = ManagementForm(data, files, auto_id=self.auto_id, prefix=self.prefix) 38 51 if self.management_form.is_valid(): 39 52 self.total_forms = self.management_form.cleaned_data[FORM_COUNT_FIELD_NAME] 40 self.required_forms = self.total_forms - self. num_extra41 self.change_form_count = self.total_forms - self. num_extra53 self.required_forms = self.total_forms - self._meta.num_extra 54 self.change_form_count = self.total_forms - self._meta.num_extra 42 55 else: 43 56 # not sure that ValidationError is the best thing to raise here 44 57 raise ValidationError('ManagementForm data is missing or has been tampered with') 45 58 elif initial: 46 59 self.change_form_count = len(initial) 47 60 self.required_forms = len(initial) 48 self.total_forms = self.required_forms + self. num_extra61 self.total_forms = self.required_forms + self._meta.num_extra 49 62 self.management_form = ManagementForm(initial={FORM_COUNT_FIELD_NAME: self.total_forms}, auto_id=self.auto_id, prefix=self.prefix) 50 63 else: 51 64 self.change_form_count = 0 52 65 self.required_forms = 0 53 self.total_forms = self. num_extra66 self.total_forms = self._meta.num_extra 54 67 self.management_form = ManagementForm(initial={FORM_COUNT_FIELD_NAME: self.total_forms}, auto_id=self.auto_id, prefix=self.prefix) 55 68 56 69 def _get_add_forms(self): 57 70 """Return a list of all the add forms in this ``FormSet``.""" 58 FormClass = self.form_class59 71 if not hasattr(self, '_add_forms'): 60 72 add_forms = [] 61 73 for i in range(self.change_form_count, self.total_forms): … … class BaseFormSet(object): 64 76 kwargs['data'] = self.data 65 77 if self.files: 66 78 kwargs['files'] = self.files 67 add_form = FormClass(**kwargs)79 add_form = self.get_form_class()(**kwargs) 68 80 self.add_fields(add_form, i) 69 81 add_forms.append(add_form) 70 82 self._add_forms = add_forms … … class BaseFormSet(object): 73 85 74 86 def _get_change_forms(self): 75 87 """Return a list of all the change forms in this ``FormSet``.""" 76 FormClass = self.form_class77 88 if not hasattr(self, '_change_forms'): 78 89 change_forms = [] 79 90 for i in range(0, self.change_form_count): … … class BaseFormSet(object): 84 95 kwargs['files'] = self.files 85 96 if self.initial: 86 97 kwargs['initial'] = self.initial[i] 87 change_form = FormClass(**kwargs)98 change_form = self.get_form_class(change=True)(**kwargs) 88 99 self.add_fields(change_form, i) 89 100 change_forms.append(change_form) 90 self._change_forms = change_forms101 self._change_forms = change_forms 91 102 return self._change_forms 92 103 change_forms = property(_get_change_forms) 93 104 … … class BaseFormSet(object): 117 128 # Process change forms 118 129 for form in self.change_forms: 119 130 if form.is_valid(): 120 if self. deletable and form.cleaned_data[DELETION_FIELD_NAME]:131 if self._meta.deletable and form.cleaned_data[DELETION_FIELD_NAME]: 121 132 self.deleted_data.append(form.cleaned_data) 122 133 else: 123 134 self.cleaned_data.append(form.cleaned_data) … … class BaseFormSet(object): 144 155 add_errors.reverse() 145 156 errors.extend(add_errors) 146 157 # Sort cleaned_data if the formset is orderable. 147 if self. orderable:158 if self._meta.orderable: 148 159 self.cleaned_data.sort(lambda x,y: x[ORDERING_FIELD_NAME] - y[ORDERING_FIELD_NAME]) 149 160 # Give self.clean() a chance to do validation 150 161 try: … … class BaseFormSet(object): 167 178 via formset.non_form_errors() 168 179 """ 169 180 return self.cleaned_data 181 182 def get_form_class(self, change=False): 183 """ 184 A hook to change a form class object. 185 """ 186 FormClass = self._meta.form 187 FormClass.base_fields = self.base_fields 188 return FormClass 170 189 171 190 def add_fields(self, form, index): 172 191 """A hook for adding extra fields on to each form instance.""" 173 if self. orderable:192 if self._meta.orderable: 174 193 form.fields[ORDERING_FIELD_NAME] = IntegerField(label='Order', initial=index+1) 175 if self. deletable:194 if self._meta.deletable: 176 195 form.fields[DELETION_FIELD_NAME] = BooleanField(label='Delete', required=False) 177 196 178 197 def add_prefix(self, index): … … class BaseFormSet(object): 192 211 else: 193 212 return Media() 194 213 media = property(_get_media) 195 196 def formset_for_form(form, formset=BaseFormSet, num_extra=1, orderable=False, deletable=False):197 """Return a FormSet for the given form class."""198 attrs = {'form_class': form, 'num_extra': num_extra, 'orderable': orderable, 'deletable': deletable}199 return type(form.__name__ + 'FormSet', (formset,), attrs)200 214 215 class FormSet(BaseFormSet): 216 __metaclass__ = FormSetMetaclass 217 218 def formset_for_form(form, formset=FormSet, num_extra=1, orderable=False, 219 deletable=False): 220 """Return a FormSet for the given form class.""" 221 warn("formset_for_form is deprecated, use FormSet instead.", 222 PendingDeprecationWarning, 223 stacklevel=3) 224 return BaseFormSetMetaclass( 225 form.__name__ + "FormSet", (formset,), form.base_fields, 226 form=form, num_extra=num_extra, orderable=orderable, 227 deletable=deletable) 228 201 229 def all_valid(formsets): 202 230 """Returns true if every formset in formsets is valid.""" 203 231 valid = True -
django/newforms/models.py
diff --git a/django/newforms/models.py b/django/newforms/models.py index 3d71ee6..656c844 100644
a b from django.core.exceptions import ImproperlyConfigured 13 13 from util import ValidationError, ErrorList 14 14 from forms import BaseForm, get_declared_fields 15 15 from fields import Field, ChoiceField, IntegerField, EMPTY_VALUES 16 from formsets import BaseFormSet, formset_for_form, DELETION_FIELD_NAME 16 from formsets import FormSetOptions, BaseFormSet, formset_for_form, DELETION_FIELD_NAME 17 from options import ModelFormOptions, ModelFormSetOptions, InlineFormSetOptions 17 18 from widgets import Select, SelectMultiple, HiddenInput, MultipleHiddenInput 18 19 19 20 __all__ = ( 20 21 'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model', 21 22 'save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields', 22 ' formset_for_model', 'inline_formset',23 'ModelFormSet', 'InlineFormSet', 23 24 'ModelChoiceField', 'ModelMultipleChoiceField', 24 25 ) 25 26 … … def fields_for_model(model, fields=None, exclude=None, formfield_callback=lambda 207 208 field_list.append((f.name, formfield)) 208 209 return SortedDict(field_list) 209 210 210 class ModelFormOptions(object):211 def __init__(self, options=None):212 self.model = getattr(options, 'model', None)213 self.fields = getattr(options, 'fields', None)214 self.exclude = getattr(options, 'exclude', None)215 216 217 211 class ModelFormMetaclass(type): 212 opts_class = ModelFormOptions 213 218 214 def __new__(cls, name, bases, attrs, 219 formfield_callback=lambda f: f.formfield() ):215 formfield_callback=lambda f: f.formfield(), **options): 220 216 try: 221 parents = [b for b in bases if issubclass(b, ModelForm)] 217 # XXX: The 3 issubclass checks here works, but calling this fragile 218 # is being terribly nice. ModelFormSet and InlineFormSet could 219 # possiblly inherit from ModelForm, but that seems wrong. More 220 # thought needed. 221 parents = [b for b in bases if issubclass(b, ModelForm) or issubclass(b, ModelFormSet) or issubclass(b, InlineFormSet)] 222 222 except NameError: 223 223 # We are defining ModelForm itself. 224 224 parents = None … … class ModelFormMetaclass(type): 228 228 229 229 new_class = type.__new__(cls, name, bases, attrs) 230 230 declared_fields = get_declared_fields(bases, attrs, False) 231 opts = new_class._meta = ModelFormOptions(getattr(new_class, 'Meta', None))231 opts = new_class._meta = cls.opts_class(options and options or getattr(new_class, 'Meta', None)) 232 232 if opts.model: 233 233 # If a model is defined, extract form fields from it. 234 234 fields = fields_for_model(opts.model, opts.fields, … … class BaseModelForm(BaseForm): 276 276 class ModelForm(BaseModelForm): 277 277 __metaclass__ = ModelFormMetaclass 278 278 279 def modelform_for_model(model, form=ModelForm, 280 formfield_callback=lambda f: f.formfield(), **options): 281 opts = model._meta 282 options.update({"model": model}) 283 return ModelFormMetaclass(opts.object_name + "ModelForm", (form,), 284 {}, formfield_callback, **options) 285 279 286 280 287 # Fields ##################################################################### 281 288 … … class ModelMultipleChoiceField(ModelChoiceField): 390 397 391 398 # Model-FormSet integration ################################################### 392 399 393 def initial_data(instance, fields=None): 394 """ 395 Return a dictionary from data in ``instance`` that is suitable for 396 use as a ``Form`` constructor's ``initial`` argument. 397 398 Provide ``fields`` to specify the names of specific fields to return. 399 All field values in the instance will be returned if ``fields`` is not 400 provided. 401 """ 402 # avoid a circular import 403 from django.db.models.fields.related import ManyToManyField 404 opts = instance._meta 405 initial = {} 406 for f in opts.fields + opts.many_to_many: 407 if not f.editable: 408 continue 409 if fields and not f.name in fields: 410 continue 411 if isinstance(f, ManyToManyField): 412 # MultipleChoiceWidget needs a list of ints, not object instances. 413 initial[f.name] = [obj.pk for obj in f.value_from_object(instance)] 414 else: 415 initial[f.name] = f.value_from_object(instance) 416 return initial 400 class ModelFormSetMetaclass(ModelFormMetaclass): 401 opts_class = ModelFormSetOptions 417 402 418 403 class BaseModelFormSet(BaseFormSet): 419 404 """ 420 405 A ``FormSet`` for editing a queryset and/or adding new objects to it. 421 406 """ 422 model = None423 queryset = None424 407 425 def __init__(self, qs, data=None, files=None, auto_id='id_%s', prefix=None): 408 def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, 409 queryset=None): 426 410 kwargs = {'data': data, 'files': files, 'auto_id': auto_id, 'prefix': prefix} 427 self.queryset = qs 428 kwargs['initial'] = [initial_data(obj) for obj in qs] 411 opts = self._meta 412 if queryset is None: 413 self.queryset = self.get_queryset(**kwargs) 414 else: 415 self.queryset = queryset 416 initial_data = [] 417 for obj in self.queryset: 418 initial_data.append(model_to_dict(obj, opts.fields, opts.exclude)) 419 kwargs['initial'] = initial_data 429 420 super(BaseModelFormSet, self).__init__(**kwargs) 421 422 def get_queryset(self, **kwargs): 423 """ 424 Hook to returning a queryset for this model. 425 """ 426 return self._meta.model._default_manager.all() 430 427 431 428 def save_new(self, form, commit=True): 432 429 """Saves and returns a new model instance for the given form.""" 433 return save_instance(form, self. model(), commit=commit)430 return save_instance(form, self._meta.model(), commit=commit) 434 431 435 432 def save_instance(self, form, instance, commit=True): 436 433 """Saves and returns an existing model instance for the given form.""" … … class BaseModelFormSet(BaseFormSet): 445 442 def save_existing_objects(self, commit=True): 446 443 if not self.queryset: 447 444 return [] 448 # Put the objects from self. get_queryset into a dict so they are easy to lookup by pk445 # Put the objects from self.queryset into a dict so they are easy to lookup by pk 449 446 existing_objects = {} 447 opts = self._meta 450 448 for obj in self.queryset: 451 449 existing_objects[obj.pk] = obj 452 450 saved_instances = [] 453 451 for form in self.change_forms: 454 obj = existing_objects[form.cleaned_data[ self.model._meta.pk.attname]]455 if self.deletable and form.cleaned_data[DELETION_FIELD_NAME]:452 obj = existing_objects[form.cleaned_data[opts.model._meta.pk.attname]] 453 if opts.deletable and form.cleaned_data[DELETION_FIELD_NAME]: 456 454 obj.delete() 457 455 else: 458 456 saved_instances.append(self.save_instance(form, obj, commit=commit)) … … class BaseModelFormSet(BaseFormSet): 460 458 461 459 def save_new_objects(self, commit=True): 462 460 new_objects = [] 461 opts = self._meta 463 462 for form in self.add_forms: 464 463 if form.is_empty(): 465 464 continue 466 465 # If someone has marked an add form for deletion, don't save the 467 466 # object. At some point it would be nice if we didn't display 468 467 # the deletion widget for add forms. 469 if self.deletable and form.cleaned_data[DELETION_FIELD_NAME]:468 if opts.deletable and form.cleaned_data[DELETION_FIELD_NAME]: 470 469 continue 471 470 new_objects.append(self.save_new(form, commit=commit)) 472 471 return new_objects 473 472 474 473 def add_fields(self, form, index): 475 474 """Add a hidden field for the object's primary key.""" 476 self._pk_field_name = self. model._meta.pk.attname475 self._pk_field_name = self._meta.model._meta.pk.attname 477 476 form.fields[self._pk_field_name] = IntegerField(required=False, widget=HiddenInput) 478 477 super(BaseModelFormSet, self).add_fields(form, index) 479 478 480 def formset_for_model(model, form=BaseForm, formfield_callback=lambda f: f.formfield(), 481 formset=BaseModelFormSet, extra=1, orderable=False, deletable=False, fields=None): 479 class ModelFormSet(BaseModelFormSet): 480 __metaclass__ = ModelFormSetMetaclass 481 482 def formset_for_model(model, formset=BaseModelFormSet, 483 formfield_callback=lambda f: f.formfield(), **options): 482 484 """ 483 485 Returns a FormSet class for the given Django model class. This FormSet 484 486 will contain change forms for every instance of the given model as well … … def formset_for_model(model, form=BaseForm, formfield_callback=lambda f: f.formf 487 489 This is essentially the same as ``formset_for_queryset``, but automatically 488 490 uses the model's default manager to determine the queryset. 489 491 """ 490 form = form_for_model(model, form=form, fields=fields, formfield_callback=formfield_callback) 491 FormSet = formset_for_form(form, formset, extra, orderable, deletable) 492 FormSet.model = model 493 return FormSet 492 opts = model._meta 493 options.update({"model": model}) 494 return ModelFormSetMetaclass(opts.object_name + "ModelFormSet", (formset,), 495 {}, formfield_callback, **options) 496 497 class InlineFormSetMetaclass(ModelFormSetMetaclass): 498 opts_class = InlineFormSetOptions 499 500 def __new__(cls, name, bases, attrs, 501 formfield_callback=lambda f: f.formfield(), **options): 502 formset = super(InlineFormSetMetaclass, cls).__new__(cls, name, bases, attrs, 503 formfield_callback, **options) 504 # If this isn't a subclass of InlineFormSet, don't do anything special. 505 try: 506 if not filter(lambda b: issubclass(b, InlineFormSet), bases): 507 return formset 508 except NameError: 509 # 'InlineFormSet' isn't defined yet, meaning we're looking at 510 # Django's own InlineFormSet class, defined below. 511 return formset 512 opts = formset._meta 513 # resolve the foreign key 514 fk = cls.resolve_foreign_key(opts.parent_model, opts.model, opts.fk_name) 515 # remove the fk from base_fields to keep it transparent to the form. 516 try: 517 del formset.base_fields[fk.name] 518 except KeyError: 519 pass 520 formset.fk = fk 521 return formset 522 523 def _resolve_foreign_key(cls, parent_model, model, fk_name=None): 524 """ 525 Finds and returns the ForeignKey from model to parent if there is one. 526 If fk_name is provided, assume it is the name of the ForeignKey field. 527 """ 528 # avoid a circular import 529 from django.db.models import ForeignKey 530 opts = model._meta 531 if fk_name: 532 fks_to_parent = [f for f in opts.fields if f.name == fk_name] 533 if len(fks_to_parent) == 1: 534 fk = fks_to_parent[0] 535 if not isinstance(fk, ForeignKey) or fk.rel.to != parent_model: 536 raise Exception("fk_name '%s' is not a ForeignKey to %s" % (fk_name, parent_model)) 537 elif len(fks_to_parent) == 0: 538 raise Exception("%s has no field named '%s'" % (model, fk_name)) 539 else: 540 # Try to discover what the ForeignKey from model to parent_model is 541 fks_to_parent = [f for f in opts.fields if isinstance(f, ForeignKey) and f.rel.to == parent_model] 542 if len(fks_to_parent) == 1: 543 fk = fks_to_parent[0] 544 elif len(fks_to_parent) == 0: 545 raise Exception("%s has no ForeignKey to %s" % (model, parent_model)) 546 else: 547 raise Exception("%s has more than 1 ForeignKey to %s" % (model, parent_model)) 548 return fk 549 resolve_foreign_key = classmethod(_resolve_foreign_key) 494 550 495 class InlineFormset(BaseModelFormSet):551 class BaseInlineFormSet(BaseModelFormSet): 496 552 """A formset for child objects related to a parent.""" 497 def __init__(self, instance, data=None, files=None):553 def __init__(self, *args, **kwargs): 498 554 from django.db.models.fields.related import RelatedObject 499 self.instance = instance 555 opts = self._meta 556 self.instance = kwargs.pop("instance", None) 500 557 # is there a better way to get the object descriptor? 501 self.rel_name = RelatedObject(self.fk.rel.to, self.model, self.fk).get_accessor_name()502 qs = self.get_queryset()503 super( InlineFormset, self).__init__(qs, data, files, prefix=self.rel_name)558 rel_name = RelatedObject(self.fk.rel.to, opts.model, self.fk).get_accessor_name() 559 kwargs["prefix"] = rel_name 560 super(BaseInlineFormSet, self).__init__(*args, **kwargs) 504 561 505 def get_queryset(self ):562 def get_queryset(self, **kwargs): 506 563 """ 507 564 Returns this FormSet's queryset, but restricted to children of 508 565 self.instance 509 566 """ 510 kwargs = {self.fk.name: self.instance} 511 return self.model._default_manager.filter(**kwargs) 567 opts = self._meta 568 if self.instance is None: 569 return opts.model._default_manager.none() 570 queryset = super(BaseInlineFormSet, self).get_queryset(**kwargs) 571 return queryset.filter(**{self.fk.name: self.instance}) 512 572 513 573 def save_new(self, form, commit=True): 514 574 kwargs = {self.fk.get_attname(): self.instance.pk} 515 new_obj = self. model(**kwargs)575 new_obj = self._meta.model(**kwargs) 516 576 return save_instance(form, new_obj, commit=commit) 517 577 518 def get_foreign_key(parent_model, model, fk_name=None): 519 """ 520 Finds and returns the ForeignKey from model to parent if there is one. 521 If fk_name is provided, assume it is the name of the ForeignKey field. 522 """ 523 # avoid circular import 524 from django.db.models import ForeignKey 525 opts = model._meta 526 if fk_name: 527 fks_to_parent = [f for f in opts.fields if f.name == fk_name] 528 if len(fks_to_parent) == 1: 529 fk = fks_to_parent[0] 530 if not isinstance(fk, ForeignKey) or fk.rel.to != parent_model: 531 raise Exception("fk_name '%s' is not a ForeignKey to %s" % (fk_name, parent_model)) 532 elif len(fks_to_parent) == 0: 533 raise Exception("%s has no field named '%s'" % (model, fk_name)) 534 else: 535 # Try to discover what the ForeignKey from model to parent_model is 536 fks_to_parent = [f for f in opts.fields if isinstance(f, ForeignKey) and f.rel.to == parent_model] 537 if len(fks_to_parent) == 1: 538 fk = fks_to_parent[0] 539 elif len(fks_to_parent) == 0: 540 raise Exception("%s has no ForeignKey to %s" % (model, parent_model)) 541 else: 542 raise Exception("%s has more than 1 ForeignKey to %s" % (model, parent_model)) 543 return fk 578 class InlineFormSet(BaseInlineFormSet): 579 __metaclass__ = InlineFormSetMetaclass 544 580 545 def inline_formset(parent_model, model, fk_name=None, fields=None, extra=3, orderable=False, deletable=True, formfield_callback=lambda f: f.formfield()): 581 def inline_formset(parent_model, model, formset=InlineFormSet, 582 formfield_callback=lambda f: f.formfield(), **options): 546 583 """ 547 Returns an ``InlineForm set`` for the given kwargs.584 Returns an ``InlineFormSet`` for the given kwargs. 548 585 549 586 You must provide ``fk_name`` if ``model`` has more than one ``ForeignKey`` 550 587 to ``parent_model``. 551 588 """ 552 fk = get_foreign_key(parent_model, model, fk_name=fk_name) 553 # let the formset handle object deletion by default 554 FormSet = formset_for_model(model, formset=InlineFormset, fields=fields, 555 formfield_callback=formfield_callback, 556 extra=extra, orderable=orderable, 557 deletable=deletable) 558 # HACK: remove the ForeignKey to the parent from every form 559 # This should be done a line above before we pass 'fields' to formset_for_model 560 # an 'omit' argument would be very handy here 561 try: 562 del FormSet.form_class.base_fields[fk.name] 563 except KeyError: 564 pass 565 FormSet.fk = fk 566 return FormSet 589 opts = model._meta 590 options.update({"parent_model": parent_model, "model": model}) 591 return InlineFormSetMetaclass(opts.object_name + "InlineFormSet", (formset,), 592 {}, formfield_callback, **options) -
tests/modeltests/model_formsets/models.py
diff --git a/tests/modeltests/model_formsets/models.py b/tests/modeltests/model_formsets/models.py index 19bdeed..208509e 100644
a b class Book(models.Model): 16 16 17 17 __test__ = {'API_TESTS': """ 18 18 19 >>> from django.newforms.models import formset_for_model 19 >>> from django import newforms as forms 20 >>> from django.newforms.models import formset_for_model, ModelFormSet 20 21 21 >>> qs = Author.objects.all() 22 >>> AuthorFormSet = formset_for_model(Author, extra=3) 22 Lets test the most basic use case of a ModelFormSet. This basically says give 23 me a FormSet that edits all the objects found in the Author model. 23 24 24 >>> formset = AuthorFormSet(qs) 25 >>> class AuthorFormSet(ModelFormSet): 26 ... class Meta: 27 ... model = Author 28 >>> AuthorFormSet.base_fields.keys() 29 ['name'] 30 31 Lets add on an extra field. 32 33 >>> class AuthorFormSet(ModelFormSet): 34 ... published = forms.BooleanField() 35 ... 36 ... class Meta: 37 ... model = Author 38 >>> AuthorFormSet.base_fields.keys() 39 ['name', 'published'] 40 41 Lets create a formset that is bound to a model. 42 43 >>> class AuthorFormSet(ModelFormSet): 44 ... class Meta: 45 ... model = Author 46 ... num_extra = 3 47 48 >>> formset = AuthorFormSet() 25 49 >>> for form in formset.forms: 26 50 ... print form.as_p() 27 51 <p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" maxlength="100" /><input type="hidden" name="form-0-id" id="id_form-0-id" /></p> … … __test__ = {'API_TESTS': """ 35 59 ... 'form-2-name': '', 36 60 ... } 37 61 38 >>> formset = AuthorFormSet( qs, data=data)62 >>> formset = AuthorFormSet(data) 39 63 >>> formset.is_valid() 40 64 True 41 65 … … Charles Baudelaire 49 73 50 74 51 75 Gah! We forgot Paul Verlaine. Let's create a formset to edit the existing 52 authors with an extra form to add him. This time we'll use formset_for_queryset. 53 We *could* use formset_for_queryset to restrict the Author objects we edit, 54 but in that case we'll use it to display them in alphabetical order by name. 55 56 >>> qs = Author.objects.order_by('name') 57 >>> AuthorFormSet = formset_for_model(Author, extra=1, deletable=False) 58 59 >>> formset = AuthorFormSet(qs) 76 authors with an extra form to add him. When subclassing ModelFormSet you can 77 override the get_queryset method to return any queryset we like, but in this 78 case we'll use it to display it in alphabetical order by name. 79 80 >>> class AuthorFormSet(ModelFormSet): 81 ... class Meta: 82 ... model = Author 83 ... num_extra = 1 84 ... deletable = False 85 ... 86 ... def get_queryset(self, **kwargs): 87 ... queryset = super(AuthorFormSet, self).get_queryset(**kwargs) 88 ... return queryset.order_by('name') 89 90 >>> formset = AuthorFormSet() 60 91 >>> for form in formset.forms: 61 92 ... print form.as_p() 62 93 <p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" value="Arthur Rimbaud" maxlength="100" /><input type="hidden" name="form-0-id" value="2" id="id_form-0-id" /></p> … … but in that case we'll use it to display them in alphabetical order by name. 73 104 ... 'form-2-name': 'Paul Verlaine', 74 105 ... } 75 106 76 >>> formset = AuthorFormSet( qs, data=data)107 >>> formset = AuthorFormSet(data) 77 108 >>> formset.is_valid() 78 109 True 79 110 … … Paul Verlaine 90 121 This probably shouldn't happen, but it will. If an add form was marked for 91 122 deltetion, make sure we don't save that form. 92 123 93 >>> qs = Author.objects.order_by('name') 94 >>> AuthorFormSet = formset_for_model(Author, extra=1, deletable=True) 95 96 >>> formset = AuthorFormSet(qs) 124 >>> class AuthorFormSet(ModelFormSet): 125 ... class Meta: 126 ... model = Author 127 ... num_extra = 1 128 ... deletable = True 129 ... 130 ... def get_queryset(self, **kwargs): 131 ... queryset = super(AuthorFormSet, self).get_queryset(**kwargs) 132 ... return queryset.order_by('name') 133 134 >>> formset = AuthorFormSet() 97 135 >>> for form in formset.forms: 98 136 ... print form.as_p() 99 137 <p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" value="Arthur Rimbaud" maxlength="100" /></p> … … deltetion, make sure we don't save that form. 117 155 ... 'form-3-DELETE': 'on', 118 156 ... } 119 157 120 >>> formset = AuthorFormSet( qs, data=data)158 >>> formset = AuthorFormSet(data) 121 159 >>> formset.is_valid() 122 160 True 123 161 … … Paul Verlaine 134 172 We can also create a formset that is tied to a parent model. This is how the 135 173 admin system's edit inline functionality works. 136 174 137 >>> from django.newforms.models import inline_formset 175 >>> from django.newforms.models import inline_formset, InlineFormSet 176 177 >>> class AuthorBooksFormSet(InlineFormSet): 178 ... class Meta: 179 ... parent_model = Author 180 ... model = Book 181 ... num_extra = 3 182 ... deletable = False 138 183 139 >>> AuthorBooksFormSet = inline_formset(Author, Book, deletable=False, extra=3)140 184 >>> author = Author.objects.get(name='Charles Baudelaire') 141 185 142 >>> formset = AuthorBooksFormSet( author)186 >>> formset = AuthorBooksFormSet(instance=author) 143 187 >>> for form in formset.forms: 144 188 ... print form.as_p() 145 189 <p><label for="id_book_set-0-title">Title:</label> <input id="id_book_set-0-title" type="text" name="book_set-0-title" maxlength="100" /><input type="hidden" name="book_set-0-id" id="id_book_set-0-id" /></p> … … admin system's edit inline functionality works. 153 197 ... 'book_set-2-title': '', 154 198 ... } 155 199 156 >>> formset = AuthorBooksFormSet( author, data=data)200 >>> formset = AuthorBooksFormSet(data, instance=author) 157 201 >>> formset.is_valid() 158 202 True 159 203 … … Now that we've added a book to Charles Baudelaire, let's try adding another 169 213 one. This time though, an edit form will be available for every existing 170 214 book. 171 215 172 >>> AuthorBooksFormSet = inline_formset(Author, Book, deletable=False, extra=2) 216 >>> class AuthorBooksFormSet(InlineFormSet): 217 ... class Meta: 218 ... parent_model = Author 219 ... model = Book 220 ... num_extra = 2 221 ... deletable = False 222 173 223 >>> author = Author.objects.get(name='Charles Baudelaire') 174 224 175 >>> formset = AuthorBooksFormSet( author)225 >>> formset = AuthorBooksFormSet(instance=author) 176 226 >>> for form in formset.forms: 177 227 ... print form.as_p() 178 228 <p><label for="id_book_set-0-title">Title:</label> <input id="id_book_set-0-title" type="text" name="book_set-0-title" value="Les Fleurs du Mal" maxlength="100" /><input type="hidden" name="book_set-0-id" value="1" id="id_book_set-0-id" /></p> … … book. 187 237 ... 'book_set-2-title': '', 188 238 ... } 189 239 190 >>> formset = AuthorBooksFormSet( author, data=data)240 >>> formset = AuthorBooksFormSet(data, instance=author) 191 241 >>> formset.is_valid() 192 242 True 193 243 -
tests/regressiontests/forms/formsets.py
diff --git a/tests/regressiontests/forms/formsets.py b/tests/regressiontests/forms/formsets.py index a6da2fe..f5cb2ee 100644
a b 2 2 formset_tests = """ 3 3 # Basic FormSet creation and usage ############################################ 4 4 5 FormSet allows us to use multiple instance of the same form on 1 page. For now,6 the best way to create a FormSet is by using the formset_for_form function.5 FormSet allows us to use multiple instance of the same form on 1 page. Create 6 the formset as you would a regular form by defining the fields declaratively. 7 7 8 8 >>> from django.newforms import Form, CharField, IntegerField, ValidationError 9 >>> from django.newforms.formsets import formset_for_form, BaseFormSet 9 >>> from django.newforms import BooleanField 10 >>> from django.newforms.formsets import formset_for_form, BaseFormSet, FormSet 10 11 11 >>> class Choice (Form):12 >>> class ChoiceFormSet(FormSet): 12 13 ... choice = CharField() 13 14 ... votes = IntegerField() 14 15 15 >>> ChoiceFormSet = formset_for_form(Choice)16 17 16 18 17 A FormSet constructor takes the same arguments as Form. Let's create a FormSet 19 18 for adding data. By default, it displays 1 blank form. It can display more, … … False 145 144 >>> formset.errors 146 145 [{'votes': [u'This field is required.'], 'choice': [u'This field is required.']}] 147 146 147 # Subclassing a FormSet class ################################################# 148 149 We can subclass a FormSet to add addition fields to an already exisiting 150 FormSet. 151 152 >>> class SecondChoiceFormSet(ChoiceFormSet): 153 ... is_public = BooleanField() 154 155 >>> formset = SecondChoiceFormSet(auto_id=False, prefix="choices") 156 >>> for form in formset.forms: 157 ... print form.as_ul() 158 <li>Choice: <input type="text" name="choices-0-choice" /></li> 159 <li>Votes: <input type="text" name="choices-0-votes" /></li> 160 <li>Is public: <input type="checkbox" name="choices-0-is_public" /></li> 148 161 149 162 # Displaying more than 1 blank form ########################################### 150 163 151 We can also display more than 1 empty form at a time. To do so, pass a152 num_extra argument to formset_for_form.164 We can also display more than 1 empty form at a time. To do so, create an inner 165 Meta class with an attribute num_extra. 153 166 154 >>> ChoiceFormSet = formset_for_form(Choice, num_extra=3) 167 >>> class NumExtraChoiceFormSet(ChoiceFormSet): 168 ... class Meta: 169 ... num_extra = 3 155 170 156 >>> formset = ChoiceFormSet(auto_id=False, prefix='choices')171 >>> formset = NumExtraChoiceFormSet(auto_id=False, prefix='choices') 157 172 >>> for form in formset.forms: 158 173 ... print form.as_ul() 159 174 <li>Choice: <input type="text" name="choices-0-choice" /></li> … … number of forms to be completed. 177 192 ... 'choices-2-votes': '', 178 193 ... } 179 194 180 >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')195 >>> formset = NumExtraChoiceFormSet(data, auto_id=False, prefix='choices') 181 196 >>> formset.is_valid() 182 197 True 183 198 >>> formset.cleaned_data … … We can just fill out one of the forms. 196 211 ... 'choices-2-votes': '', 197 212 ... } 198 213 199 >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')214 >>> formset = NumExtraChoiceFormSet(data, auto_id=False, prefix='choices') 200 215 >>> formset.is_valid() 201 216 True 202 217 >>> formset.cleaned_data … … And once again, if we try to partially complete a form, validation will fail. 215 230 ... 'choices-2-votes': '', 216 231 ... } 217 232 218 >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')233 >>> formset = NumExtraChoiceFormSet(data, auto_id=False, prefix='choices') 219 234 >>> formset.is_valid() 220 235 False 221 236 >>> formset.errors … … The num_extra argument also works when the formset is pre-filled with initial 226 241 data. 227 242 228 243 >>> initial = [{'choice': u'Calexico', 'votes': 100}] 229 >>> formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')244 >>> formset = NumExtraChoiceFormSet(initial=initial, auto_id=False, prefix='choices') 230 245 >>> for form in formset.forms: 231 246 ... print form.as_ul() 232 247 <li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li> … … get an error. 254 269 ... 'choices-3-votes': '', 255 270 ... } 256 271 257 >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')272 >>> formset = NumExtraChoiceFormSet(data, auto_id=False, prefix='choices') 258 273 >>> formset.is_valid() 259 274 False 260 275 >>> formset.errors … … False 263 278 264 279 # FormSets with deletion ###################################################### 265 280 266 We can easily add deletion ability to a FormSet with an agrument to267 formset_for_form. This will add a boolean field to each form instance. When 268 that boolean field is True, the cleaned data will be in formset.deleted_data281 We can easily add deletion ability to a FormSet by setting deletable to True 282 in the inner Meta class. This will add a boolean field to each form instance. 283 When that boolean field is True, the cleaned data will be in formset.deleted_data 269 284 rather than formset.cleaned_data 270 285 271 >>> ChoiceFormSet = formset_for_form(Choice, deletable=True) 286 >>> class DeletableChoiceFormSet(ChoiceFormSet): 287 ... class Meta: 288 ... deletable = True 272 289 273 290 >>> initial = [{'choice': u'Calexico', 'votes': 100}, {'choice': u'Fergie', 'votes': 900}] 274 >>> formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')291 >>> formset = DeletableChoiceFormSet(initial=initial, auto_id=False, prefix='choices') 275 292 >>> for form in formset.forms: 276 293 ... print form.as_ul() 277 294 <li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li> … … To delete something, we just need to set that form's special delete field to 300 317 ... 'choices-2-DELETE': '', 301 318 ... } 302 319 303 >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')320 >>> formset = DeletableChoiceFormSet(data, auto_id=False, prefix='choices') 304 321 >>> formset.is_valid() 305 322 True 306 323 >>> formset.cleaned_data … … True 310 327 311 328 # FormSets with ordering ###################################################### 312 329 313 We can also add ordering ability to a FormSet with an agrument to314 formset_for_form. This will add a integer field to each form instance. When330 We can also add ordering ability to a FormSet by setting orderable to True in 331 the inner Meta class. This will add a integer field to each form instance. When 315 332 form validation succeeds, formset.cleaned_data will have the data in the correct 316 333 order specified by the ordering fields. If a number is duplicated in the set 317 334 of ordering fields, for instance form 0 and form 3 are both marked as 1, then 318 335 the form index used as a secondary ordering criteria. In order to put 319 336 something at the front of the list, you'd need to set it's order to 0. 320 337 321 >>> ChoiceFormSet = formset_for_form(Choice, orderable=True) 338 >>> class OrderableChoiceFormSet(ChoiceFormSet): 339 ... class Meta: 340 ... orderable = True 322 341 323 342 >>> initial = [{'choice': u'Calexico', 'votes': 100}, {'choice': u'Fergie', 'votes': 900}] 324 >>> formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')343 >>> formset = OrderableChoiceFormSet(initial=initial, auto_id=False, prefix='choices') 325 344 >>> for form in formset.forms: 326 345 ... print form.as_ul() 327 346 <li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li> … … something at the front of the list, you'd need to set it's order to 0. 347 366 ... 'choices-2-ORDER': '0', 348 367 ... } 349 368 350 >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')369 >>> formset = OrderableChoiceFormSet(data, auto_id=False, prefix='choices') 351 370 >>> formset.is_valid() 352 371 True 353 372 >>> for cleaned_data in formset.cleaned_data: … … True 359 378 # FormSets with ordering + deletion ########################################### 360 379 361 380 Let's try throwing ordering and deletion into the same form. 381 TODO: Perhaps handle Meta class inheritance so you can subclass 382 OrderableChoiceFormSet and DeletableChoiceFormSet? 362 383 363 >>> ChoiceFormSet = formset_for_form(Choice, orderable=True, deletable=True) 384 >>> class MixedChoiceFormSet(ChoiceFormSet): 385 ... class Meta: 386 ... orderable = True 387 ... deletable = True 364 388 365 389 >>> initial = [ 366 390 ... {'choice': u'Calexico', 'votes': 100}, 367 391 ... {'choice': u'Fergie', 'votes': 900}, 368 392 ... {'choice': u'The Decemberists', 'votes': 500}, 369 393 ... ] 370 >>> formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')394 >>> formset = MixedChoiceFormSet(initial=initial, auto_id=False, prefix='choices') 371 395 >>> for form in formset.forms: 372 396 ... print form.as_ul() 373 397 <li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li> … … Let's delete Fergie, and put The Decemberists ahead of Calexico. 409 433 ... 'choices-3-DELETE': '', 410 434 ... } 411 435 412 >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')436 >>> formset = MixedChoiceFormSet(data, auto_id=False, prefix='choices') 413 437 >>> formset.is_valid() 414 438 True 415 439 >>> for cleaned_data in formset.cleaned_data: … … particular form. It follows the same pattern as the clean hook on Forms. 428 452 Let's define a FormSet that takes a list of favorite drinks, but raises am 429 453 error if there are any duplicates. 430 454 431 >>> class FavoriteDrink Form(Form):455 >>> class FavoriteDrinksFormSet(FormSet): 432 456 ... name = CharField() 433 ... 434 435 >>> class FavoriteDrinksFormSet(BaseFormSet): 436 ... form_class = FavoriteDrinkForm 437 ... num_extra = 2 438 ... orderable = False 439 ... deletable = False 457 ... 458 ... class Meta: 459 ... num_extra = 2 460 ... orderable = False 461 ... deletable = False 440 462 ... 441 463 ... def clean(self): 442 464 ... seen_drinks = [] -
tests/regressiontests/inline_formsets/models.py
diff --git a/tests/regressiontests/inline_formsets/models.py b/tests/regressiontests/inline_formsets/models.py index f84be84..f7dec6b 100644
a b class Child(models.Model): 15 15 16 16 __test__ = {'API_TESTS': """ 17 17 18 >>> from django.newforms.models import inline_formset 19 18 >>> from django.newforms.models import InlineFormSet 20 19 21 20 Child has two ForeignKeys to Parent, so if we don't specify which one to use 22 21 for the inline formset, we should get an exception. 23 22 24 >>> ifs = inline_formset(Parent, Child) 23 >>> class ChildrenFormSet(InlineFormSet): 24 ... class Meta: 25 ... parent_model = Parent 26 ... model = Child 25 27 Traceback (most recent call last): 26 28 ... 27 29 Exception: <class 'regressiontests.inline_formsets.models.Child'> has more than 1 ForeignKey to <class 'regressiontests.inline_formsets.models.Parent'> 28 30 29 30 31 These two should both work without a problem. 31 32 32 >>> ifs = inline_formset(Parent, Child, fk_name='mother') 33 >>> ifs = inline_formset(Parent, Child, fk_name='father') 33 >>> class ChildrenFormSet(InlineFormSet): 34 ... class Meta: 35 ... parent_model = Parent 36 ... model = Child 37 ... fk_name = "mother" 34 38 39 >>> class ChildrenFormSet(InlineFormSet): 40 ... class Meta: 41 ... parent_model = Parent 42 ... model = Child 43 ... fk_name = "father" 35 44 36 45 If we specify fk_name, but it isn't a ForeignKey from the child model to the 37 46 parent model, we should get an exception. 38 47 39 >>> ifs = inline_formset(Parent, Child, fk_name='school') 48 >>> class ChildrenFormSet(InlineFormSet): 49 ... class Meta: 50 ... parent_model = Parent 51 ... model = Child 52 ... fk_name = "school" 40 53 Traceback (most recent call last): 41 54 ... 42 55 Exception: fk_name 'school' is not a ForeignKey to <class 'regressiontests.inline_formsets.models.Parent'> 43 56 44 45 57 If the field specified in fk_name is not a ForeignKey, we should get an 46 58 exception. 47 59 48 >>> ifs = inline_formset(Parent, Child, fk_name='test') 60 >>> class ChildreFormSet(InlineFormSet): 61 ... class Meta: 62 ... parent_model = Parent 63 ... model = Child 64 ... fk_name = "test" 49 65 Traceback (most recent call last): 50 66 ... 51 67 Exception: <class 'regressiontests.inline_formsets.models.Child'> has no field named 'test'