Ticket #6241: formset_refactor_7.diff
File formset_refactor_7.diff, 49.6 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..70640c1 100644
a b class ModelAdmin(BaseModelAdmin): 342 342 fields = flatten_fieldsets(self.declared_fieldsets) 343 343 else: 344 344 fields = None 345 return forms. form_for_model(self.model, fields=fields, formfield_callback=self.formfield_for_dbfield)345 return forms.modelform_for_model(self.model, fields=fields, formfield_callback=self.formfield_for_dbfield) 346 346 347 347 def form_change(self, request, obj): 348 348 """ … … class ModelAdmin(BaseModelAdmin): 352 352 fields = flatten_fieldsets(self.declared_fieldsets) 353 353 else: 354 354 fields = None 355 return forms. form_for_instance(obj, fields=fields, formfield_callback=self.formfield_for_dbfield)355 return forms.modelform_for_model(self.model, fields=fields, formfield_callback=self.formfield_for_dbfield) 356 356 357 357 def save_add(self, request, model, form, formsets, post_url_continue): 358 358 """ … … class ModelAdmin(BaseModelAdmin): 492 492 # Object list will give 'Permission Denied', so go back to admin home 493 493 post_url = '../../../' 494 494 495 ModelForm = self.form_add(request)495 Form = self.form_add(request) 496 496 inline_formsets = [] 497 497 obj = self.model() 498 498 if request.method == 'POST': 499 form = ModelForm(request.POST, request.FILES)499 form = Form(request.POST, request.FILES) 500 500 for FormSet in self.formsets_add(request): 501 inline_formset = FormSet( obj, data=request.POST, files=request.FILES)501 inline_formset = FormSet(request.POST, request.FILES) 502 502 inline_formsets.append(inline_formset) 503 503 if all_valid(inline_formsets) and form.is_valid(): 504 504 return self.save_add(request, model, form, inline_formsets, '../%s/') 505 505 else: 506 form = ModelForm(initial=request.GET)506 form = Form(initial=request.GET) 507 507 for FormSet in self.formsets_add(request): 508 inline_formset = FormSet( obj)508 inline_formset = FormSet() 509 509 inline_formsets.append(inline_formset) 510 510 511 511 adminForm = AdminForm(form, list(self.fieldsets_add(request)), self.prepopulated_fields) … … class ModelAdmin(BaseModelAdmin): 552 552 if request.POST and request.POST.has_key("_saveasnew"): 553 553 return self.add_view(request, form_url='../../add/') 554 554 555 ModelForm = self.form_change(request, obj)555 Form = self.form_change(request, obj) 556 556 inline_formsets = [] 557 557 if request.method == 'POST': 558 form = ModelForm(request.POST, request.FILES)558 form = Form(request.POST, request.FILES, instance=obj) 559 559 for FormSet in self.formsets_change(request, obj): 560 inline_formset = FormSet( obj, request.POST, request.FILES)560 inline_formset = FormSet(request.POST, request.FILES, instance=obj) 561 561 inline_formsets.append(inline_formset) 562 562 563 563 if all_valid(inline_formsets) and form.is_valid(): 564 564 return self.save_change(request, model, form, inline_formsets) 565 565 else: 566 form = ModelForm()566 form = Form(instance=obj, initial=request.GET) 567 567 for FormSet in self.formsets_change(request, obj): 568 inline_formset = FormSet( obj)568 inline_formset = FormSet(instance=obj) 569 569 inline_formsets.append(inline_formset) 570 570 571 571 ## Populate the FormWrapper. … … class InlineModelAdmin(BaseModelAdmin): 755 755 def fieldsets_add(self, request): 756 756 if self.declared_fieldsets: 757 757 return self.declared_fieldsets 758 form = self.formset_add(request).form_class759 return [(None, {'fields': form .base_fields.keys()})]758 formset = self.formset_add(request) 759 return [(None, {'fields': formset.base_fields.keys()})] 760 760 761 761 def fieldsets_change(self, request, obj): 762 762 if self.declared_fieldsets: 763 763 return self.declared_fieldsets 764 form = self.formset_change(request, obj).form_class765 return [(None, {'fields': form .base_fields.keys()})]764 formset = self.formset_change(request, obj) 765 return [(None, {'fields': formset.base_fields.keys()})] 766 766 767 767 class StackedInline(InlineModelAdmin): 768 768 template = 'admin/edit_inline/stacked.html' … … class InlineAdminFormSet(object): 778 778 self.opts = inline 779 779 self.formset = formset 780 780 self.fieldsets = fieldsets 781 # place orderable and deletable here since _meta is inaccesible in the 782 # templates. 783 self.orderable = formset._meta.orderable 784 self.deletable = formset._meta.deletable 781 785 782 786 def __iter__(self): 783 787 for form, original in zip(self.formset.change_forms, self.formset.get_queryset()): … … class InlineAdminFormSet(object): 787 791 788 792 def fields(self): 789 793 for field_name in flatten_fieldsets(self.fieldsets): 790 yield self.formset. form_class.base_fields[field_name]794 yield self.formset.base_fields[field_name] 791 795 792 796 class InlineAdminForm(AdminForm): 793 797 """ -
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..d9b34cf 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 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 BaseFormSetMetaclass(type): 31 def __new__(cls, name, bases, attrs, **options): 32 fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)] 33 fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter)) 34 35 # If this class is subclassing another FormSet, ad that FormSet's fields. 36 # Note that we loop over the bases in *reverse*. This is necessary in 37 # order to preserve the correct order of fields. 38 for base in bases[::-1]: 39 if hasattr(base, "base_fields"): 40 fields = base.base_fields.items() + fields 41 attrs["base_fields"] = SortedDict(fields) 42 43 opts = FormSetOptions(options and options or attrs.get("Meta", None)) 44 attrs["_meta"] = opts 45 46 return type.__new__(cls, name, bases, attrs) 47 23 48 class BaseFormSet(object): 24 49 """A collection of instances of the same Form class.""" 25 50 … … class BaseFormSet(object): 37 62 self.management_form = ManagementForm(data, files, auto_id=self.auto_id, prefix=self.prefix) 38 63 if self.management_form.is_valid(): 39 64 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_extra65 self.required_forms = self.total_forms - self._meta.num_extra 66 self.change_form_count = self.total_forms - self._meta.num_extra 42 67 else: 43 68 # not sure that ValidationError is the best thing to raise here 44 69 raise ValidationError('ManagementForm data is missing or has been tampered with') 45 70 elif initial: 46 71 self.change_form_count = len(initial) 47 72 self.required_forms = len(initial) 48 self.total_forms = self.required_forms + self. num_extra73 self.total_forms = self.required_forms + self._meta.num_extra 49 74 self.management_form = ManagementForm(initial={FORM_COUNT_FIELD_NAME: self.total_forms}, auto_id=self.auto_id, prefix=self.prefix) 50 75 else: 51 76 self.change_form_count = 0 52 77 self.required_forms = 0 53 self.total_forms = self. num_extra78 self.total_forms = self._meta.num_extra 54 79 self.management_form = ManagementForm(initial={FORM_COUNT_FIELD_NAME: self.total_forms}, auto_id=self.auto_id, prefix=self.prefix) 55 80 56 81 def _get_add_forms(self): 57 82 """Return a list of all the add forms in this ``FormSet``.""" 58 FormClass = self.form_class59 83 if not hasattr(self, '_add_forms'): 60 84 add_forms = [] 61 85 for i in range(self.change_form_count, self.total_forms): … … class BaseFormSet(object): 64 88 kwargs['data'] = self.data 65 89 if self.files: 66 90 kwargs['files'] = self.files 67 add_form = FormClass(**kwargs)91 add_form = self.get_form_class(i)(**kwargs) 68 92 self.add_fields(add_form, i) 69 93 add_forms.append(add_form) 70 94 self._add_forms = add_forms … … class BaseFormSet(object): 73 97 74 98 def _get_change_forms(self): 75 99 """Return a list of all the change forms in this ``FormSet``.""" 76 FormClass = self.form_class77 100 if not hasattr(self, '_change_forms'): 78 101 change_forms = [] 79 102 for i in range(0, self.change_form_count): … … class BaseFormSet(object): 84 107 kwargs['files'] = self.files 85 108 if self.initial: 86 109 kwargs['initial'] = self.initial[i] 87 change_form = FormClass(**kwargs)110 change_form = self.get_form_class(i)(**kwargs) 88 111 self.add_fields(change_form, i) 89 112 change_forms.append(change_form) 90 self._change_forms = change_forms113 self._change_forms = change_forms 91 114 return self._change_forms 92 115 change_forms = property(_get_change_forms) 93 116 … … class BaseFormSet(object): 117 140 # Process change forms 118 141 for form in self.change_forms: 119 142 if form.is_valid(): 120 if self. deletable and form.cleaned_data[DELETION_FIELD_NAME]:143 if self._meta.deletable and form.cleaned_data[DELETION_FIELD_NAME]: 121 144 self.deleted_data.append(form.cleaned_data) 122 145 else: 123 146 self.cleaned_data.append(form.cleaned_data) … … class BaseFormSet(object): 144 167 add_errors.reverse() 145 168 errors.extend(add_errors) 146 169 # Sort cleaned_data if the formset is orderable. 147 if self. orderable:170 if self._meta.orderable: 148 171 self.cleaned_data.sort(lambda x,y: x[ORDERING_FIELD_NAME] - y[ORDERING_FIELD_NAME]) 149 172 # Give self.clean() a chance to do validation 150 173 try: … … class BaseFormSet(object): 167 190 via formset.non_form_errors() 168 191 """ 169 192 return self.cleaned_data 193 194 def get_form_class(self, index): 195 """ 196 A hook to change a form class object. 197 """ 198 FormClass = self._meta.form 199 FormClass.base_fields = self.base_fields 200 return FormClass 170 201 171 202 def add_fields(self, form, index): 172 203 """A hook for adding extra fields on to each form instance.""" 173 if self. orderable:204 if self._meta.orderable: 174 205 form.fields[ORDERING_FIELD_NAME] = IntegerField(label='Order', initial=index+1) 175 if self. deletable:206 if self._meta.deletable: 176 207 form.fields[DELETION_FIELD_NAME] = BooleanField(label='Delete', required=False) 177 208 178 209 def add_prefix(self, index): … … class BaseFormSet(object): 192 223 else: 193 224 return Media() 194 225 media = property(_get_media) 226 227 class FormSet(BaseFormSet): 228 __metaclass__ = BaseFormSetMetaclass 195 229 196 def formset_for_form(form, formset=BaseFormSet, num_extra=1, orderable=False, deletable=False): 230 def formset_for_form(form, formset=FormSet, num_extra=1, orderable=False, 231 deletable=False): 197 232 """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 233 warn("formset_for_form is deprecated, use FormSet instead.", 234 PendingDeprecationWarning, 235 stacklevel=3) 236 return BaseFormSetMetaclass( 237 form.__name__ + "FormSet", (formset,), form.base_fields, 238 form=form, num_extra=num_extra, orderable=orderable, 239 deletable=deletable) 240 201 241 def all_valid(formsets): 202 242 """Returns true if every formset in formsets is valid.""" 203 243 valid = True -
django/newforms/models.py
diff --git a/django/newforms/models.py b/django/newforms/models.py index e0f2cde..68da932 100644
a b from django.core.exceptions import ImproperlyConfigured 13 13 from util import ValidationError, ErrorList 14 14 from forms import BaseForm 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', 23 'ModelFormSet', 'InlineFormset', 'modelform_for_model', 22 24 'formset_for_model', 'inline_formset', 23 25 'ModelChoiceField', 'ModelMultipleChoiceField', 24 26 ) … … def fields_for_model(model, fields=None, exclude=None, formfield_callback=lambda 207 209 field_list.append((f.name, formfield)) 208 210 return SortedDict(field_list) 209 211 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 212 class ModelFormMetaclass(type): 213 opts_class = ModelFormOptions 214 217 215 def __new__(cls, name, bases, attrs, 218 formfield_callback=lambda f: f.formfield() ):216 formfield_callback=lambda f: f.formfield(), **options): 219 217 fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)] 220 218 fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter)) 221 219 … … class ModelFormMetaclass(type): 227 225 fields = base.base_fields.items() + fields 228 226 declared_fields = SortedDict(fields) 229 227 230 opts = ModelFormOptions(attrs.get('Meta', None))228 opts = cls.opts_class(options and options or attrs.get('Meta', None)) 231 229 attrs['_meta'] = opts 232 230 233 231 # Don't allow more than one Meta model defenition in bases. The fields … … class ModelFormMetaclass(type): 260 258 else: 261 259 attrs['base_fields'] = declared_fields 262 260 return type.__new__(cls, name, bases, attrs) 263 261 264 262 class BaseModelForm(BaseForm): 265 263 def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, 266 264 initial=None, error_class=ErrorList, label_suffix=':', instance=None): … … class BaseModelForm(BaseForm): 293 291 class ModelForm(BaseModelForm): 294 292 __metaclass__ = ModelFormMetaclass 295 293 294 # this should really be named form_for_model. 295 def modelform_for_model(model, form=ModelForm, 296 formfield_callback=lambda f: f.formfield(), **options): 297 opts = model._meta 298 options.update({"model": model}) 299 return ModelFormMetaclass(opts.object_name + "ModelForm", (form,), 300 {}, formfield_callback, **options) 301 296 302 297 303 # Fields ##################################################################### 298 304 … … class ModelMultipleChoiceField(ModelChoiceField): 407 413 408 414 # Model-FormSet integration ################################################### 409 415 410 def initial_data(instance, fields=None): 411 """ 412 Return a dictionary from data in ``instance`` that is suitable for 413 use as a ``Form`` constructor's ``initial`` argument. 414 415 Provide ``fields`` to specify the names of specific fields to return. 416 All field values in the instance will be returned if ``fields`` is not 417 provided. 418 """ 419 # avoid a circular import 420 from django.db.models.fields.related import ManyToManyField 421 opts = instance._meta 422 initial = {} 423 for f in opts.fields + opts.many_to_many: 424 if not f.editable: 425 continue 426 if fields and not f.name in fields: 427 continue 428 if isinstance(f, ManyToManyField): 429 # MultipleChoiceWidget needs a list of ints, not object instances. 430 initial[f.name] = [obj.pk for obj in f.value_from_object(instance)] 431 else: 432 initial[f.name] = f.value_from_object(instance) 433 return initial 416 class ModelFormSetMetaclass(ModelFormMetaclass): 417 opts_class = ModelFormSetOptions 434 418 435 419 class BaseModelFormSet(BaseFormSet): 436 420 """ 437 421 A ``FormSet`` for editing a queryset and/or adding new objects to it. 438 422 """ 439 model = None440 queryset = None441 423 442 def __init__(self, qs,data=None, files=None, auto_id='id_%s', prefix=None):424 def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None): 443 425 kwargs = {'data': data, 'files': files, 'auto_id': auto_id, 'prefix': prefix} 444 self.queryset = qs 445 kwargs['initial'] = [initial_data(obj) for obj in qs] 426 opts = self._meta 427 self.queryset = self.get_queryset(**kwargs) 428 initial_data = [] 429 for obj in self.queryset: 430 initial_data.append(model_to_dict(obj, opts.fields, opts.exclude)) 431 kwargs['initial'] = initial_data 446 432 super(BaseModelFormSet, self).__init__(**kwargs) 433 434 def get_queryset(self, **kwargs): 435 """ 436 Hook to returning a queryset for this model. 437 """ 438 return self._meta.model._default_manager.all() 447 439 448 440 def save_new(self, form, commit=True): 449 441 """Saves and returns a new model instance for the given form.""" 450 return save_instance(form, self. model(), commit=commit)442 return save_instance(form, self._meta.model(), commit=commit) 451 443 452 444 def save_instance(self, form, instance, commit=True): 453 445 """Saves and returns an existing model instance for the given form.""" … … class BaseModelFormSet(BaseFormSet): 464 456 return [] 465 457 # Put the objects from self.get_queryset into a dict so they are easy to lookup by pk 466 458 existing_objects = {} 459 opts = self._meta 467 460 for obj in self.queryset: 468 461 existing_objects[obj.pk] = obj 469 462 saved_instances = [] 470 463 for form in self.change_forms: 471 obj = existing_objects[form.cleaned_data[ self.model._meta.pk.attname]]472 if self.deletable and form.cleaned_data[DELETION_FIELD_NAME]:464 obj = existing_objects[form.cleaned_data[opts.model._meta.pk.attname]] 465 if opts.deletable and form.cleaned_data[DELETION_FIELD_NAME]: 473 466 obj.delete() 474 467 else: 475 468 saved_instances.append(self.save_instance(form, obj, commit=commit)) … … class BaseModelFormSet(BaseFormSet): 477 470 478 471 def save_new_objects(self, commit=True): 479 472 new_objects = [] 473 opts = self._meta 480 474 for form in self.add_forms: 481 475 if form.is_empty(): 482 476 continue 483 477 # If someone has marked an add form for deletion, don't save the 484 478 # object. At some point it would be nice if we didn't display 485 479 # the deletion widget for add forms. 486 if self.deletable and form.cleaned_data[DELETION_FIELD_NAME]:480 if opts.deletable and form.cleaned_data[DELETION_FIELD_NAME]: 487 481 continue 488 482 new_objects.append(self.save_new(form, commit=commit)) 489 483 return new_objects 490 484 491 485 def add_fields(self, form, index): 492 486 """Add a hidden field for the object's primary key.""" 493 self._pk_field_name = self. model._meta.pk.attname487 self._pk_field_name = self._meta.model._meta.pk.attname 494 488 form.fields[self._pk_field_name] = IntegerField(required=False, widget=HiddenInput) 495 489 super(BaseModelFormSet, self).add_fields(form, index) 496 490 497 def formset_for_model(model, form=BaseForm, formfield_callback=lambda f: f.formfield(), 498 formset=BaseModelFormSet, extra=1, orderable=False, deletable=False, fields=None): 491 class ModelFormSet(BaseModelFormSet): 492 __metaclass__ = ModelFormSetMetaclass 493 494 def formset_for_model(model, formset=BaseModelFormSet, 495 formfield_callback=lambda f: f.formfield(), **options): 499 496 """ 500 497 Returns a FormSet class for the given Django model class. This FormSet 501 498 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 504 501 This is essentially the same as ``formset_for_queryset``, but automatically 505 502 uses the model's default manager to determine the queryset. 506 503 """ 507 form = form_for_model(model, form=form, fields=fields, formfield_callback=formfield_callback) 508 FormSet = formset_for_form(form, formset, extra, orderable, deletable) 509 FormSet.model = model 510 return FormSet 504 opts = model._meta 505 options.update({"model": model}) 506 return ModelFormSetMetaclass(opts.object_name + "ModelFormSet", (formset,), 507 {}, **options) 508 509 class InlineFormSetMetaclass(ModelFormSetMetaclass): 510 opts_class = InlineFormSetOptions 511 512 def __new__(cls, name, bases, attrs, 513 formfield_callback=lambda f: f.formfield(), **options): 514 formset = super(InlineFormSetMetaclass, cls).__new__(cls, name, bases, attrs, 515 formfield_callback, **options) 516 # If this isn't a subclass of InlineFormset, don't do anything special. 517 try: 518 if not filter(lambda b: issubclass(b, InlineFormset), bases): 519 return formset 520 except NameError: 521 # 'InlineFormset' isn't defined yet, meaning we're looking at 522 # Django's own InlineFormset class, defined below. 523 return formset 524 opts = formset._meta 525 # resolve the foreign key 526 fk = cls.resolve_foreign_key(opts.parent_model, opts.model, opts.fk_name) 527 # remove the fk from base_fields to keep it transparent to the form. 528 try: 529 del formset.base_fields[fk.name] 530 except KeyError: 531 pass 532 formset.fk = fk 533 return formset 534 535 def _resolve_foreign_key(cls, parent_model, model, fk_name=None): 536 """ 537 Finds and returns the ForeignKey from model to parent if there is one. 538 If fk_name is provided, assume it is the name of the ForeignKey field. 539 """ 540 # avoid a circular import 541 from django.db.models import ForeignKey 542 opts = model._meta 543 if fk_name: 544 fks_to_parent = [f for f in opts.fields if f.name == fk_name] 545 if len(fks_to_parent) == 1: 546 fk = fks_to_parent[0] 547 if not isinstance(fk, ForeignKey) or fk.rel.to != parent_model: 548 raise Exception("fk_name '%s' is not a ForeignKey to %s" % (fk_name, parent_model)) 549 elif len(fks_to_parent) == 0: 550 raise Exception("%s has no field named '%s'" % (model, fk_name)) 551 else: 552 # Try to discover what the ForeignKey from model to parent_model is 553 fks_to_parent = [f for f in opts.fields if isinstance(f, ForeignKey) and f.rel.to == parent_model] 554 if len(fks_to_parent) == 1: 555 fk = fks_to_parent[0] 556 elif len(fks_to_parent) == 0: 557 raise Exception("%s has no ForeignKey to %s" % (model, parent_model)) 558 else: 559 raise Exception("%s has more than 1 ForeignKey to %s" % (model, parent_model)) 560 return fk 561 resolve_foreign_key = classmethod(_resolve_foreign_key) 511 562 512 class InlineFormset(BaseModelFormSet):563 class BaseInlineFormSet(BaseModelFormSet): 513 564 """A formset for child objects related to a parent.""" 514 def __init__(self, instance, data=None, files=None):565 def __init__(self, *args, **kwargs): 515 566 from django.db.models.fields.related import RelatedObject 516 self.instance = instance 567 opts = self._meta 568 self.instance = kwargs.pop("instance", None) 517 569 # is there a better way to get the object descriptor? 518 self.rel_name = RelatedObject(self.fk.rel.to, self.model, self.fk).get_accessor_name()519 qs = self.get_queryset()520 super( InlineFormset, self).__init__(qs, data, files, prefix=self.rel_name)570 rel_name = RelatedObject(self.fk.rel.to, opts.model, self.fk).get_accessor_name() 571 kwargs["prefix"] = rel_name 572 super(BaseInlineFormSet, self).__init__(*args, **kwargs) 521 573 522 def get_queryset(self ):574 def get_queryset(self, **kwargs): 523 575 """ 524 576 Returns this FormSet's queryset, but restricted to children of 525 577 self.instance 526 578 """ 527 kwargs = {self.fk.name: self.instance}528 return self.model._default_manager.filter(**kwargs)579 queryset = super(BaseInlineFormSet, self).get_queryset(**kwargs) 580 return queryset.filter(**{self.fk.name: self.instance}) 529 581 530 582 def save_new(self, form, commit=True): 531 583 kwargs = {self.fk.get_attname(): self.instance.pk} 532 new_obj = self. model(**kwargs)584 new_obj = self._meta.model(**kwargs) 533 585 return save_instance(form, new_obj, commit=commit) 534 586 535 def get_foreign_key(parent_model, model, fk_name=None): 536 """ 537 Finds and returns the ForeignKey from model to parent if there is one. 538 If fk_name is provided, assume it is the name of the ForeignKey field. 539 """ 540 # avoid circular import 541 from django.db.models import ForeignKey 542 opts = model._meta 543 if fk_name: 544 fks_to_parent = [f for f in opts.fields if f.name == fk_name] 545 if len(fks_to_parent) == 1: 546 fk = fks_to_parent[0] 547 if not isinstance(fk, ForeignKey) or fk.rel.to != parent_model: 548 raise Exception("fk_name '%s' is not a ForeignKey to %s" % (fk_name, parent_model)) 549 elif len(fks_to_parent) == 0: 550 raise Exception("%s has no field named '%s'" % (model, fk_name)) 551 else: 552 # Try to discover what the ForeignKey from model to parent_model is 553 fks_to_parent = [f for f in opts.fields if isinstance(f, ForeignKey) and f.rel.to == parent_model] 554 if len(fks_to_parent) == 1: 555 fk = fks_to_parent[0] 556 elif len(fks_to_parent) == 0: 557 raise Exception("%s has no ForeignKey to %s" % (model, parent_model)) 558 else: 559 raise Exception("%s has more than 1 ForeignKey to %s" % (model, parent_model)) 560 return fk 587 class InlineFormset(BaseInlineFormSet): 588 __metaclass__ = InlineFormSetMetaclass 561 589 562 def inline_formset(parent_model, model, fk_name=None, fields=None, extra=3, orderable=False, deletable=True, formfield_callback=lambda f: f.formfield()): 590 def inline_formset(parent_model, model, formset=InlineFormset, 591 formfield_callback=lambda f: f.formfield(), **options): 563 592 """ 564 593 Returns an ``InlineFormset`` for the given kwargs. 565 594 566 595 You must provide ``fk_name`` if ``model`` has more than one ``ForeignKey`` 567 596 to ``parent_model``. 568 597 """ 569 fk = get_foreign_key(parent_model, model, fk_name=fk_name) 570 # let the formset handle object deletion by default 571 FormSet = formset_for_model(model, formset=InlineFormset, fields=fields, 572 formfield_callback=formfield_callback, 573 extra=extra, orderable=orderable, 574 deletable=deletable) 575 # HACK: remove the ForeignKey to the parent from every form 576 # This should be done a line above before we pass 'fields' to formset_for_model 577 # an 'omit' argument would be very handy here 578 try: 579 del FormSet.form_class.base_fields[fk.name] 580 except KeyError: 581 pass 582 FormSet.fk = fk 583 return FormSet 598 opts = model._meta 599 options.update({"parent_model": parent_model, "model": model}) 600 return InlineFormSetMetaclass(opts.object_name + "InlineFormset", (formset,), 601 {}, formfield_callback, **options) -
new file django/newforms/options.py
diff --git a/django/newforms/options.py b/django/newforms/options.py new file mode 100644 index 0000000..a4565e4
- + 1 2 from forms import BaseForm 3 4 class BaseFormOptions(object): 5 """ 6 The base class for all options that are associated to a form object. 7 """ 8 def __init__(self, options=None): 9 self.fields = self._dynamic_attribute(options, "fields") 10 self.exclude = self._dynamic_attribute(options, "exclude") 11 12 def _dynamic_attribute(self, obj, key, default=None): 13 try: 14 return getattr(obj, key) 15 except AttributeError: 16 try: 17 return obj[key] 18 except (TypeError, KeyError): 19 # key doesnt exist in obj or obj is None 20 return default 21 22 class ModelFormOptions(BaseFormOptions): 23 """ 24 Encapsulates the options on a ModelForm class. 25 """ 26 def __init__(self, options=None): 27 self.model = self._dynamic_attribute(options, "model") 28 super(ModelFormOptions, self).__init__(options) 29 30 class FormSetOptions(BaseFormOptions): 31 """ 32 Encapsulates the options on a FormSet class. 33 """ 34 def __init__(self, options=None): 35 self.form = self._dynamic_attribute(options, "form", BaseForm) 36 self.num_extra = self._dynamic_attribute(options, "num_extra", 1) 37 self.orderable = self._dynamic_attribute(options, "orderable", False) 38 self.deletable = self._dynamic_attribute(options, "deletable", False) 39 super(FormSetOptions, self).__init__(options) 40 41 class ModelFormSetOptions(FormSetOptions, ModelFormOptions): 42 pass 43 44 class InlineFormSetOptions(ModelFormSetOptions): 45 def __init__(self, options=None): 46 super(InlineFormSetOptions, self).__init__(options) 47 self.parent_model = self._dynamic_attribute(options, "parent_model") 48 self.fk_name = self._dynamic_attribute(options, "fk_name") 49 self.deletable = self._dynamic_attribute(options, "deletable", True) 50 51 No newline at end of file -
tests/modeltests/model_formsets/models.py
diff --git a/tests/modeltests/model_formsets/models.py b/tests/modeltests/model_formsets/models.py index 19bdeed..d1e72ea 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 A bare bones verion. 23 23 24 >>> formset = AuthorFormSet(qs) 24 >>> class AuthorFormSet(ModelFormSet): 25 ... class Meta: 26 ... model = Author 27 >>> AuthorFormSet.base_fields.keys() 28 ['name'] 29 30 Extra fields. 31 32 >>> class AuthorFormSet(ModelFormSet): 33 ... published = forms.BooleanField() 34 ... 35 ... class Meta: 36 ... model = Author 37 >>> AuthorFormSet.base_fields.keys() 38 ['name', 'published'] 39 40 Lets create a formset that is bound to a model. 41 42 >>> class AuthorFormSet(ModelFormSet): 43 ... class Meta: 44 ... model = Author 45 ... num_extra = 3 46 47 >>> formset = AuthorFormSet() 25 48 >>> for form in formset.forms: 26 49 ... print form.as_p() 27 50 <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 58 ... 'form-2-name': '', 36 59 ... } 37 60 38 >>> formset = AuthorFormSet( qs, data=data)61 >>> formset = AuthorFormSet(data) 39 62 >>> formset.is_valid() 40 63 True 41 64 … … Charles Baudelaire 49 72 50 73 51 74 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) 75 authors with an extra form to add him. When subclassing ModelFormSet you can 76 override the get_queryset method to return any queryset we like, but in this 77 case we'll use it to display it in alphabetical order by name. 78 79 >>> class AuthorFormSet(ModelFormSet): 80 ... class Meta: 81 ... model = Author 82 ... num_extra = 1 83 ... deletable = False 84 ... 85 ... def get_queryset(self, **kwargs): 86 ... qs = super(AuthorFormSet, self).get_queryset(**kwargs) 87 ... return qs.order_by('name') 88 89 >>> formset = AuthorFormSet() 60 90 >>> for form in formset.forms: 61 91 ... print form.as_p() 62 92 <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 103 ... 'form-2-name': 'Paul Verlaine', 74 104 ... } 75 105 76 >>> formset = AuthorFormSet( qs, data=data)106 >>> formset = AuthorFormSet(data) 77 107 >>> formset.is_valid() 78 108 True 79 109 … … Paul Verlaine 90 120 This probably shouldn't happen, but it will. If an add form was marked for 91 121 deltetion, make sure we don't save that form. 92 122 93 >>> qs = Author.objects.order_by('name') 94 >>> AuthorFormSet = formset_for_model(Author, extra=1, deletable=True) 95 96 >>> formset = AuthorFormSet(qs) 123 >>> class AuthorFormSet(ModelFormSet): 124 ... class Meta: 125 ... model = Author 126 ... num_extra = 1 127 ... deletable = True 128 ... 129 ... def get_queryset(self, **kwargs): 130 ... qs = super(AuthorFormSet, self).get_queryset(**kwargs) 131 ... return qs.order_by('name') 132 133 >>> formset = AuthorFormSet() 97 134 >>> for form in formset.forms: 98 135 ... print form.as_p() 99 136 <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 154 ... 'form-3-DELETE': 'on', 118 155 ... } 119 156 120 >>> formset = AuthorFormSet( qs, data=data)157 >>> formset = AuthorFormSet(data) 121 158 >>> formset.is_valid() 122 159 True 123 160 … … Paul Verlaine 134 171 We can also create a formset that is tied to a parent model. This is how the 135 172 admin system's edit inline functionality works. 136 173 137 >>> from django.newforms.models import inline_formset 174 >>> from django.newforms.models import inline_formset, InlineFormset 175 176 >>> class AuthorBooksFormSet(InlineFormset): 177 ... class Meta: 178 ... parent_model = Author 179 ... model = Book 180 ... num_extra = 3 181 ... deletable = False 138 182 139 >>> AuthorBooksFormSet = inline_formset(Author, Book, deletable=False, extra=3)140 183 >>> author = Author.objects.get(name='Charles Baudelaire') 141 184 142 >>> formset = AuthorBooksFormSet( author)185 >>> formset = AuthorBooksFormSet(instance=author) 143 186 >>> for form in formset.forms: 144 187 ... print form.as_p() 145 188 <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 196 ... 'book_set-2-title': '', 154 197 ... } 155 198 156 >>> formset = AuthorBooksFormSet( author, data=data)199 >>> formset = AuthorBooksFormSet(data, instance=author) 157 200 >>> formset.is_valid() 158 201 True 159 202 … … Now that we've added a book to Charles Baudelaire, let's try adding another 169 212 one. This time though, an edit form will be available for every existing 170 213 book. 171 214 172 >>> AuthorBooksFormSet = inline_formset(Author, Book, deletable=False, extra=2) 215 >>> class AuthorBooksFormSet(InlineFormset): 216 ... class Meta: 217 ... parent_model = Author 218 ... model = Book 219 ... num_extra = 2 220 ... deletable = False 221 173 222 >>> author = Author.objects.get(name='Charles Baudelaire') 174 223 175 >>> formset = AuthorBooksFormSet( author)224 >>> formset = AuthorBooksFormSet(instance=author) 176 225 >>> for form in formset.forms: 177 226 ... print form.as_p() 178 227 <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 236 ... 'book_set-2-title': '', 188 237 ... } 189 238 190 >>> formset = AuthorBooksFormSet( author, data=data)239 >>> formset = AuthorBooksFormSet(data, instance=author) 191 240 >>> formset.is_valid() 192 241 True 193 242 -
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..180bba1 100644
a b __test__ = {'API_TESTS': """ 21 21 Child has two ForeignKeys to Parent, so if we don't specify which one to use 22 22 for the inline formset, we should get an exception. 23 23 24 >>> i fs = inline_formset(Parent, Child)24 >>> inline_formset(Parent, Child)() 25 25 Traceback (most recent call last): 26 26 ... 27 27 Exception: <class 'regressiontests.inline_formsets.models.Child'> has more than 1 ForeignKey to <class 'regressiontests.inline_formsets.models.Parent'> … … Exception: <class 'regressiontests.inline_formsets.models.Child'> has more than 29 29 30 30 These two should both work without a problem. 31 31 32 >>> ifs = inline_formset(Parent, Child, fk_name='mother') 33 >>> ifs = inline_formset(Parent, Child, fk_name='father') 32 >>> ifs = inline_formset(Parent, Child, fk_name='mother')() 33 >>> ifs = inline_formset(Parent, Child, fk_name='father')() 34 34 35 35 36 36 If we specify fk_name, but it isn't a ForeignKey from the child model to the 37 37 parent model, we should get an exception. 38 38 39 >>> i fs = inline_formset(Parent, Child, fk_name='school')39 >>> inline_formset(Parent, Child, fk_name='school')() 40 40 Traceback (most recent call last): 41 41 ... 42 42 Exception: fk_name 'school' is not a ForeignKey to <class 'regressiontests.inline_formsets.models.Parent'> … … Exception: fk_name 'school' is not a ForeignKey to <class 'regressiontests.inlin 45 45 If the field specified in fk_name is not a ForeignKey, we should get an 46 46 exception. 47 47 48 >>> i fs = inline_formset(Parent, Child, fk_name='test')48 >>> inline_formset(Parent, Child, fk_name='test')() 49 49 Traceback (most recent call last): 50 50 ... 51 51 Exception: <class 'regressiontests.inline_formsets.models.Child'> has no field named 'test'