Ticket #5372: formset_refactor_3.diff
| File formset_refactor_3.diff, 47.2 kB (added by brosner, 1 year ago) |
|---|
-
a/django/contrib/admin/options.py
old new 331 331 "Hook for specifying fieldsets for the change form." 332 332 if self.declared_fieldsets: 333 333 return self.declared_fieldsets 334 form = self.form_change(request , obj)334 form = self.form_change(request) 335 335 return [(None, {'fields': form.base_fields.keys()})] 336 336 337 337 def form_add(self, request): … … 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 def form_change(self, request , obj):347 def form_change(self, request): 348 348 """ 349 349 Returns a Form class for use in the admin change view. 350 350 """ … … 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 """ … … 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) … … 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) 556 556 inline_formsets = [] 557 557 if request.method == 'POST': 558 form = ModelForm(request.POST, request.FILES)558 form = Form(request.POST, request.FILES) 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. -
a/django/newforms/formsets.py
old new 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' … … 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 … … 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): … … 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 … … 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): … … 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 … … 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) … … 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: … … 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): … … 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 -
a/django/newforms/models.py
old new 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', 22 ' formset_for_model', 'inline_formset',23 'modelform_for_model', 'formset_for_model', 'inline_formset', 23 24 'ModelChoiceField', 'ModelMultipleChoiceField', 24 25 ) 25 26 … … 209 210 field_list.append((f.name, formfield)) 210 211 return SortedDict(field_list) 211 212 212 class ModelFormOptions(object):213 def __init__(self, options=None):214 self.model = getattr(options, 'model', None)215 self.fields = getattr(options, 'fields', None)216 self.exclude = getattr(options, 'exclude', None)217 218 213 class ModelFormMetaclass(type): 219 def __new__(cls, name, bases, attrs): 220 # TODO: no way to specify formfield_callback yet, do we need one, or 221 # should it be a special case for the admin? 214 def __new__(cls, name, bases, attrs, 215 formfield_callback=lambda f: f.formfield(), **options): 222 216 fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)] 223 217 fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter)) 224 218 … … 230 224 fields = base.base_fields.items() + fields 231 225 declared_fields = SortedDict(fields) 232 226 233 opts = ModelFormOptions(attrs.get('Meta', None))227 opts = cls.get_options(options and options or attrs.get('Meta', None)) 234 228 attrs['_meta'] = opts 235 229 236 230 # Don't allow more than one Meta model defenition in bases. The fields … … 262 256 else: 263 257 attrs['base_fields'] = declared_fields 264 258 return type.__new__(cls, name, bases, attrs) 259 260 def _get_options(cls, options=None): 261 """ 262 Returns the options instance for this class. 263 """ 264 return ModelFormOptions(options) 265 get_options = classmethod(_get_options) 265 266 266 267 class BaseModelForm(BaseForm): 267 268 def __init__(self, instance, data=None, files=None, auto_id='id_%s', prefix=None, … … 290 291 class ModelForm(BaseModelForm): 291 292 __metaclass__ = ModelFormMetaclass 292 293 294 # this should really be named form_for_model. 295 def modelform_for_model(model, form=BaseModelForm, 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 {}, **options) 301 293 302 294 303 # Fields ##################################################################### 295 304 … … 404 413 405 414 # Model-FormSet integration ################################################### 406 415 407 def initial_data(instance, fields=None): 408 """ 409 Return a dictionary from data in ``instance`` that is suitable for 410 use as a ``Form`` constructor's ``initial`` argument. 411 412 Provide ``fields`` to specify the names of specific fields to return. 413 All field values in the instance will be returned if ``fields`` is not 414 provided. 415 """ 416 # avoid a circular import 417 from django.db.models.fields.related import ManyToManyField 418 opts = instance._meta 419 initial = {} 420 for f in opts.fields + opts.many_to_many: 421 if not f.editable: 422 continue 423 if fields and not f.name in fields: 424 continue 425 if isinstance(f, ManyToManyField): 426 # MultipleChoiceWidget needs a list of ints, not object instances. 427 initial[f.name] = [obj.pk for obj in f.value_from_object(instance)] 428 else: 429 initial[f.name] = f.value_from_object(instance) 430 return initial 416 class ModelFormSetMetaclass(ModelFormMetaclass): 417 def _get_options(cls, options=None): 418 return ModelFormSetOptions(options) 419 get_options = classmethod(_get_options) 431 420 432 421 class BaseModelFormSet(BaseFormSet): 433 422 """ 434 423 A ``FormSet`` for editing a queryset and/or adding new objects to it. 435 424 """ 436 model = None437 queryset = None438 425 439 def __init__(self, qs,data=None, files=None, auto_id='id_%s', prefix=None):426 def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None): 440 427 kwargs = {'data': data, 'files': files, 'auto_id': auto_id, 'prefix': prefix} 441 self.queryset = qs 442 kwargs['initial'] = [initial_data(obj) for obj in qs] 428 opts = self._meta 429 self.queryset = self.get_queryset(**kwargs) 430 initial_data = [] 431 for obj in self.queryset: 432 initial_data.append(model_to_dict(obj, opts.fields, opts.exclude)) 433 kwargs['initial'] = initial_data 443 434 super(BaseModelFormSet, self).__init__(**kwargs) 435 436 def get_queryset(self, **kwargs): 437 """ 438 Hook to returning a queryset for this model. 439 """ 440 return self._meta.model._default_manager.all() 444 441 445 442 def save_new(self, form, commit=True): 446 443 """Saves and returns a new model instance for the given form.""" 447 return save_instance(form, self. model(), commit=commit)444 return save_instance(form, self._meta.model(), commit=commit) 448 445 449 446 def save_instance(self, form, instance, commit=True): 450 447 """Saves and returns an existing model instance for the given form.""" … … 461 458 return [] 462 459 # Put the objects from self.get_queryset into a dict so they are easy to lookup by pk 463 460 existing_objects = {} 461 opts = self._meta 464 462 for obj in self.queryset: 465 463 existing_objects[obj.pk] = obj 466 464 saved_instances = [] 467 465 for form in self.change_forms: 468 obj = existing_objects[form.cleaned_data[ self.model._meta.pk.attname]]469 if self.deletable and form.cleaned_data[DELETION_FIELD_NAME]:466 obj = existing_objects[form.cleaned_data[opts.model._meta.pk.attname]] 467 if opts.deletable and form.cleaned_data[DELETION_FIELD_NAME]: 470 468 obj.delete() 471 469 else: 472 470 saved_instances.append(self.save_instance(form, obj, commit=commit)) … … 474 472 475 473 def save_new_objects(self, commit=True): 476 474 new_objects = [] 475 opts = self._meta 477 476 for form in self.add_forms: 478 477 if form.is_empty(): 479 478 continue 480 479 # If someone has marked an add form for deletion, don't save the 481 480 # object. At some point it would be nice if we didn't display 482 481 # the deletion widget for add forms. 483 if self.deletable and form.cleaned_data[DELETION_FIELD_NAME]:482 if opts.deletable and form.cleaned_data[DELETION_FIELD_NAME]: 484 483 continue 485 484 new_objects.append(self.save_new(form, commit=commit)) 486 485 return new_objects 487 486 488 487 def add_fields(self, form, index): 489 488 """Add a hidden field for the object's primary key.""" 490 self._pk_field_name = self. model._meta.pk.attname489 self._pk_field_name = self._meta.model._meta.pk.attname 491 490 form.fields[self._pk_field_name] = IntegerField(required=False, widget=HiddenInput) 492 491 super(BaseModelFormSet, self).add_fields(form, index) 493 492 494 def formset_for_model(model, form=BaseForm, formfield_callback=lambda f: f.formfield(), 495 formset=BaseModelFormSet, extra=1, orderable=False, deletable=False, fields=None): 493 class ModelFormSet(BaseModelFormSet): 494 __metaclass__ = ModelFormSetMetaclass 495 496 def formset_for_model(model, formset=BaseModelFormSet, 497 formfield_callback=lambda f: f.formfield(), **options): 496 498 """ 497 499 Returns a FormSet class for the given Django model class. This FormSet 498 500 will contain change forms for every instance of the given model as well … … 501 503 This is essentially the same as ``formset_for_queryset``, but automatically 502 504 uses the model's default manager to determine the queryset. 503 505 """ 504 form = form_for_model(model, form=form, fields=fields, formfield_callback=formfield_callback)505 FormSet = formset_for_form(form, formset, extra, orderable, deletable)506 FormSet.model = model507 return FormSet506 opts = model._meta 507 options.update({"model": model}) 508 return ModelFormSetMetaclass(opts.object_name + "ModelFormSet", (formset,), 509 {}, **options) 508 510 509 class InlineFormset(BaseModelFormSet): 511 class InlineFormSetMetaclass(ModelFormMetaclass): 512 def _get_options(cls, options=None): 513 return InlineFormSetOptions(options) 514 get_options = classmethod(_get_options) 515 516 class BaseInlineFormSet(BaseModelFormSet): 510 517 """A formset for child objects related to a parent.""" 511 def __init__(self, instance, data=None, files=None):518 def __init__(self, *args, **kwargs): 512 519 from django.db.models.fields.related import RelatedObject 513 self.instance = instance 520 opts = self._meta 521 self.instance = kwargs.pop("instance", None) 522 self.fk = self.get_foreign_key(opts.parent_model, opts.model, opts.fk_name) 514 523 # is there a better way to get the object descriptor? 515 self.rel_name = RelatedObject(self.fk.rel.to, self.model, self.fk).get_accessor_name() 516 qs = self.get_queryset() 517 super(InlineFormset, self).__init__(qs, data, files, prefix=self.rel_name) 524 rel_name = RelatedObject(self.fk.rel.to, opts.model, self.fk).get_accessor_name() 525 kwargs["prefix"] = rel_name 526 # remove the foreign key from base_fields 527 try: 528 del self.base_fields[self.fk.name] 529 except KeyError: 530 pass 531 super(BaseInlineFormSet, self).__init__(*args, **kwargs) 518 532 519 def get_queryset(self ):533 def get_queryset(self, **kwargs): 520 534 """ 521 535 Returns this FormSet's queryset, but restricted to children of 522 536 self.instance 523 537 """ 524 kwargs = {self.fk.name: self.instance} 525 return self.model._default_manager.filter(**kwargs) 538 queryset = super(BaseInlineFormSet, self).get_queryset(**kwargs) 539 return queryset.filter(**{self.fk.name: self.instance}) 540 541 def get_foreign_key(self, parent_model, model, fk_name=None): 542 """ 543 Finds and returns the ForeignKey from model to parent if there is one. 544 If fk_name is provided, assume it is the name of the ForeignKey field. 545 """ 546 from django.db.models import ForeignKey 547 opts = model._meta 548 if fk_name: 549 fks_to_parent = [f for f in opts.fields if f.name == fk_name] 550 if len(fks_to_parent) == 1: 551 fk = fks_to_parent[0] 552 if not isinstance(fk, ForeignKey) or fk.rel.to != parent_model: 553 raise Exception("fk_name '%s' is not a ForeignKey to %s" % (fk_name, parent_model)) 554 elif len(fks_to_parent) == 0: 555 raise Exception("%s has no field named '%s'" % (model, fk_name)) 556 else: 557 # Try to discover what the ForeignKey from model to parent_model is 558 fks_to_parent = [f for f in opts.fields if isinstance(f, ForeignKey) and f.rel.to == parent_model] 559 if len(fks_to_parent) == 1: 560 fk = fks_to_parent[0] 561 elif len(fks_to_parent) == 0: 562 raise Exception("%s has no ForeignKey to %s" % (model, parent_model)) 563 else: 564 raise Exception("%s has more than 1 ForeignKey to %s" % (model, parent_model)) 565 return fk 526 566 527 567 def save_new(self, form, commit=True): 528 568 kwargs = {self.fk.get_attname(): self.instance.pk} 529 new_obj = self. model(**kwargs)569 new_obj = self._meta.model(**kwargs) 530 570 return save_instance(form, new_obj, commit=commit) 531 571 532 def get_foreign_key(parent_model, model, fk_name=None): 533 """ 534 Finds and returns the ForeignKey from model to parent if there is one. 535 If fk_name is provided, assume it is the name of the ForeignKey field. 536 """ 537 # avoid circular import 538 from django.db.models import ForeignKey 539 opts = model._meta 540 if fk_name: 541 fks_to_parent = [f for f in opts.fields if f.name == fk_name] 542 if len(fks_to_parent) == 1: 543 fk = fks_to_parent[0] 544 if not isinstance(fk, ForeignKey) or fk.rel.to != parent_model: 545 raise Exception("fk_name '%s' is not a ForeignKey to %s" % (fk_name, parent_model)) 546 elif len(fks_to_parent) == 0: 547 raise Exception("%s has no field named '%s'" % (model, fk_name)) 548 else: 549 # Try to discover what the ForeignKey from model to parent_model is 550 fks_to_parent = [f for f in opts.fields if isinstance(f, ForeignKey) and f.rel.to == parent_model] 551 if len(fks_to_parent) == 1: 552 fk = fks_to_parent[0] 553 elif len(fks_to_parent) == 0: 554 raise Exception("%s has no ForeignKey to %s" % (model, parent_model)) 555 else: 556 raise Exception("%s has more than 1 ForeignKey to %s" % (model, parent_model)) 557 return fk 572 class InlineFormset(BaseInlineFormSet): 573 __metaclass__ = InlineFormSetMetaclass 558 574 559 def inline_formset(parent_model, model, fk_name=None, fields=None, extra=3, orderable=False, deletable=True, formfield_callback=lambda f: f.formfield()): 575 def inline_formset(parent_model, model, formset=BaseInlineFormSet, 576 formfield_callback=lambda f: f.formfield(), **options): 560 577 """ 561 578 Returns an ``InlineFormset`` for the given kwargs. 562 579 563 580 You must provide ``fk_name`` if ``model`` has more than one ``ForeignKey`` 564 581 to ``parent_model``. 565 582 """ 566 fk = get_foreign_key(parent_model, model, fk_name=fk_name) 567 # let the formset handle object deletion by default 568 FormSet = formset_for_model(model, formset=InlineFormset, fields=fields, 569 formfield_callback=formfield_callback, 570 extra=extra, orderable=orderable, 571 deletable=deletable) 572 # HACK: remove the ForeignKey to the parent from every form 573 # This should be done a line above before we pass 'fields' to formset_for_model 574 # an 'omit' argument would be very handy here 575 try: 576 del FormSet.form_class.base_fields[fk.name] 577 except KeyError: 578 pass 579 FormSet.fk = fk 580 return FormSet 583 opts = model._meta 584 options.update({"parent_model": parent_model, "model": model}) 585 return InlineFormSetMetaclass(opts.object_name + "InlineFormset", (formset,), 586 {}, **options) -
/dev/null
old new 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 -
a/tests/modeltests/model_formsets/models.py
old new 16 16 17 17 __test__ = {'API_TESTS': """ 18 18 19 >>> from django.newforms.models import formset_for_model 19 >>> from django.newforms.models import formset_for_model, ModelFormSet 20 20 21 >>> qs = Author.objects.all() 22 >>> AuthorFormSet = formset_for_model(Author, extra=3) 21 Lets create a formset that is bound to a model. 23 22 24 >>> formset = AuthorFormSet(qs) 23 >>> class AuthorFormSet(ModelFormSet): 24 ... class Meta: 25 ... model = Author 26 ... num_extra = 3 27 28 >>> formset = AuthorFormSet() 25 29 >>> for form in formset.forms: 26 30 ... print form.as_p() 27 31 <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> … … 35 39 ... 'form-2-name': '', 36 40 ... } 37 41 38 >>> formset = AuthorFormSet( qs, data=data)42 >>> formset = AuthorFormSet(data) 39 43 >>> formset.is_valid() 40 44 True 41 45 … … 49 53 50 54 51 55 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) 56 authors with an extra form to add him. When subclassing ModelFormSet you can 57 override the get_queryset method to return any queryset we like, but in this 58 case we'll use it to display it in alphabetical order by name. 59 60 >>> class AuthorFormSet(ModelFormSet): 61 ... class Meta: 62 ... model = Author 63 ... num_extra = 1 64 ... deletable = False 65 ... 66 ... def get_queryset(self, **kwargs): 67 ... qs = super(AuthorFormSet, self).get_queryset(**kwargs) 68 ... return qs.order_by('name') 69 70 >>> formset = AuthorFormSet() 60 71 >>> for form in formset.forms: 61 72 ... print form.as_p() 62 73 <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> … … 73 84 ... 'form-2-name': 'Paul Verlaine', 74 85 ... } 75 86 76 >>> formset = AuthorFormSet( qs, data=data)87 >>> formset = AuthorFormSet(data) 77 88 >>> formset.is_valid() 78 89 True 79 90 … … 90 101 This probably shouldn't happen, but it will. If an add form was marked for 91 102 deltetion, make sure we don't save that form. 92 103 93 >>> qs = Author.objects.order_by('name') 94 >>> AuthorFormSet = formset_for_model(Author, extra=1, deletable=True) 95 96 >>> formset = AuthorFormSet(qs) 104 >>> class AuthorFormSet(ModelFormSet): 105 ... class Meta: 106 ... model = Author 107 ... num_extra = 1 108 ... deletable = True 109 ... 110 ... def get_queryset(self, **kwargs): 111 ... qs = super(AuthorFormSet, self).get_queryset(**kwargs) 112 ... return qs.order_by('name') 113 114 >>> formset = AuthorFormSet() 97 115 >>> for form in formset.forms: 98 116 ... print form.as_p() 99 117 <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> … … 117 135 ... 'form-3-DELETE': 'on', 118 136 ... } 119 137 120 >>> formset = AuthorFormSet( qs, data=data)138 >>> formset = AuthorFormSet(data) 121 139 >>> formset.is_valid() 122 140 True 123 141 … … 134 152 We can also create a formset that is tied to a parent model. This is how the 135 153 admin system's edit inline functionality works. 136 154 137 >>> from django.newforms.models import inline_formset 155 >>> from django.newforms.models import inline_formset, InlineFormset 156 157 >>> class AuthorBooksFormSet(InlineFormset): 158 ... class Meta: 159 ... parent_model = Author 160 ... model = Book 161 ... num_extra = 3 162 ... deletable = False 138 163 139 >>> AuthorBooksFormSet = inline_formset(Author, Book, deletable=False, extra=3)140 164 >>> author = Author.objects.get(name='Charles Baudelaire') 141 165 142 >>> formset = AuthorBooksFormSet( author)166 >>> formset = AuthorBooksFormSet(instance=author) 143 167 >>> for form in formset.forms: 144 168 ... print form.as_p() 145 169 <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> … … 153 177 ... 'book_set-2-title': '', 154 178 ... } 155 179 156 >>> formset = AuthorBooksFormSet( author, data=data)180 >>> formset = AuthorBooksFormSet(data, instance=author) 157 181 >>> formset.is_valid() 158 182 True 159 183 … … 169 193 one. This time though, an edit form will be available for every existing 170 194 book. 171 195 172 >>> AuthorBooksFormSet = inline_formset(Author, Book, deletable=False, extra=2) 196 >>> class AuthorBooksFormSet(InlineFormset): 197 ... class Meta: 198 ... parent_model = Author 199 ... model = Book 200 ... num_extra = 2 201 ... deletable = False 202 173 203 >>> author = Author.objects.get(name='Charles Baudelaire') 174 204 175 >>> formset = AuthorBooksFormSet( author)205 >>> formset = AuthorBooksFormSet(instance=author) 176 206 >>> for form in formset.forms: 177 207 ... print form.as_p() 178 208 <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> … … 187 217 ... 'book_set-2-title': '', 188 218 ... } 189 219 190 >>> formset = AuthorBooksFormSet( author, data=data)220 >>> formset = AuthorBooksFormSet(data, instance=author) 191 221 >>> formset.is_valid() 192 222 True 193 223 -
a/tests/regressiontests/forms/formsets.py
old new 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, … … 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> … … 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 … … 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 … … 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 … … 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> … … 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 … … 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):
