Ticket #6241: formset_refactor_7.diff

File formset_refactor_7.diff, 49.6 KB (added by brosner, 8 years ago)

updated to r6955 (this does not include any new changes since my last patch)

  • 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): 
    342342            fields = flatten_fieldsets(self.declared_fieldsets)
    343343        else:
    344344            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)
    346346
    347347    def form_change(self, request, obj):
    348348        """
    class ModelAdmin(BaseModelAdmin): 
    352352            fields = flatten_fieldsets(self.declared_fieldsets)
    353353        else:
    354354            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)
    356356
    357357    def save_add(self, request, model, form, formsets, post_url_continue):
    358358        """
    class ModelAdmin(BaseModelAdmin): 
    492492            # Object list will give 'Permission Denied', so go back to admin home
    493493            post_url = '../../../'
    494494
    495         ModelForm = self.form_add(request)
     495        Form = self.form_add(request)
    496496        inline_formsets = []
    497497        obj = self.model()
    498498        if request.method == 'POST':
    499             form = ModelForm(request.POST, request.FILES)
     499            form = Form(request.POST, request.FILES)
    500500            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)
    502502                inline_formsets.append(inline_formset)
    503503            if all_valid(inline_formsets) and form.is_valid():
    504504                return self.save_add(request, model, form, inline_formsets, '../%s/')
    505505        else:
    506             form = ModelForm(initial=request.GET)
     506            form = Form(initial=request.GET)
    507507            for FormSet in self.formsets_add(request):
    508                 inline_formset = FormSet(obj)
     508                inline_formset = FormSet()
    509509                inline_formsets.append(inline_formset)
    510510
    511511        adminForm = AdminForm(form, list(self.fieldsets_add(request)), self.prepopulated_fields)
    class ModelAdmin(BaseModelAdmin): 
    552552        if request.POST and request.POST.has_key("_saveasnew"):
    553553            return self.add_view(request, form_url='../../add/')
    554554
    555         ModelForm = self.form_change(request, obj)
     555        Form = self.form_change(request, obj)
    556556        inline_formsets = []
    557557        if request.method == 'POST':
    558             form = ModelForm(request.POST, request.FILES)
     558            form = Form(request.POST, request.FILES, instance=obj)
    559559            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)
    561561                inline_formsets.append(inline_formset)
    562562
    563563            if all_valid(inline_formsets) and form.is_valid():
    564564                return self.save_change(request, model, form, inline_formsets)
    565565        else:
    566             form = ModelForm()
     566            form = Form(instance=obj, initial=request.GET)
    567567            for FormSet in self.formsets_change(request, obj):
    568                 inline_formset = FormSet(obj)
     568                inline_formset = FormSet(instance=obj)
    569569                inline_formsets.append(inline_formset)
    570570
    571571        ## Populate the FormWrapper.
    class InlineModelAdmin(BaseModelAdmin): 
    755755    def fieldsets_add(self, request):
    756756        if self.declared_fieldsets:
    757757            return self.declared_fieldsets
    758         form = self.formset_add(request).form_class
    759         return [(None, {'fields': form.base_fields.keys()})]
     758        formset = self.formset_add(request)
     759        return [(None, {'fields': formset.base_fields.keys()})]
    760760
    761761    def fieldsets_change(self, request, obj):
    762762        if self.declared_fieldsets:
    763763            return self.declared_fieldsets
    764         form = self.formset_change(request, obj).form_class
    765         return [(None, {'fields': form.base_fields.keys()})]
     764        formset = self.formset_change(request, obj)
     765        return [(None, {'fields': formset.base_fields.keys()})]
    766766
    767767class StackedInline(InlineModelAdmin):
    768768    template = 'admin/edit_inline/stacked.html'
    class InlineAdminFormSet(object): 
    778778        self.opts = inline
    779779        self.formset = formset
    780780        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
    781785
    782786    def __iter__(self):
    783787        for form, original in zip(self.formset.change_forms, self.formset.get_queryset()):
    class InlineAdminFormSet(object): 
    787791
    788792    def fields(self):
    789793        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]
    791795
    792796class InlineAdminForm(AdminForm):
    793797    """
  • 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  
    1111         <th {% if forloop.first %}colspan="2"{% endif %}>{{ field.label|capfirst|escape }}</th>
    1212        {% endif %}
    1313     {% endfor %}
    14      {% if inline_admin_formset.formset.deletable %}<th>{% trans "Delete" %}?</th>{% endif %}
     14     {% if inline_admin_formset.deletable %}<th>{% trans "Delete" %}?</th>{% endif %}
    1515     </tr></thead>
    1616   
    1717     {% for inline_admin_form in inline_admin_formset %}
     
    4545          {% endfor %}
    4646        {% endfor %}
    4747               
    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 %}
    4949       
    5050        </tr>
    5151
  • 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
     2from warnings import warn
     3
     4from django.utils.datastructures import SortedDict
     5from django.utils.translation import ugettext_lazy as _
     6
     7from forms import BaseForm, Form
     8from fields import Field, IntegerField, BooleanField
     9from options import FormSetOptions
    310from widgets import HiddenInput, Media
    411from util import ErrorList, ValidationError
    512
    6 __all__ = ('BaseFormSet', 'formset_for_form', 'all_valid')
     13__all__ = ('BaseFormSet', 'FormSet', 'formset_for_form', 'all_valid')
    714
    815# special field names
    916FORM_COUNT_FIELD_NAME = 'COUNT'
    class ManagementForm(Form): 
    2027        self.base_fields[FORM_COUNT_FIELD_NAME] = IntegerField(widget=HiddenInput)
    2128        super(ManagementForm, self).__init__(*args, **kwargs)
    2229
     30class 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
    2348class BaseFormSet(object):
    2449    """A collection of instances of the same Form class."""
    2550
    class BaseFormSet(object): 
    3762            self.management_form = ManagementForm(data, files, auto_id=self.auto_id, prefix=self.prefix)
    3863            if self.management_form.is_valid():
    3964                self.total_forms = self.management_form.cleaned_data[FORM_COUNT_FIELD_NAME]
    40                 self.required_forms = self.total_forms - self.num_extra
    41                 self.change_form_count = self.total_forms - self.num_extra
     65                self.required_forms = self.total_forms - self._meta.num_extra
     66                self.change_form_count = self.total_forms - self._meta.num_extra
    4267            else:
    4368                # not sure that ValidationError is the best thing to raise here
    4469                raise ValidationError('ManagementForm data is missing or has been tampered with')
    4570        elif initial:
    4671            self.change_form_count = len(initial)
    4772            self.required_forms = len(initial)
    48             self.total_forms = self.required_forms + self.num_extra
     73            self.total_forms = self.required_forms + self._meta.num_extra
    4974            self.management_form = ManagementForm(initial={FORM_COUNT_FIELD_NAME: self.total_forms}, auto_id=self.auto_id, prefix=self.prefix)
    5075        else:
    5176            self.change_form_count = 0
    5277            self.required_forms = 0
    53             self.total_forms = self.num_extra
     78            self.total_forms = self._meta.num_extra
    5479            self.management_form = ManagementForm(initial={FORM_COUNT_FIELD_NAME: self.total_forms}, auto_id=self.auto_id, prefix=self.prefix)
    5580
    5681    def _get_add_forms(self):
    5782        """Return a list of all the add forms in this ``FormSet``."""
    58         FormClass = self.form_class
    5983        if not hasattr(self, '_add_forms'):
    6084            add_forms = []
    6185            for i in range(self.change_form_count, self.total_forms):
    class BaseFormSet(object): 
    6488                    kwargs['data'] = self.data
    6589                if self.files:
    6690                    kwargs['files'] = self.files
    67                 add_form = FormClass(**kwargs)
     91                add_form = self.get_form_class(i)(**kwargs)
    6892                self.add_fields(add_form, i)
    6993                add_forms.append(add_form)
    7094            self._add_forms = add_forms
    class BaseFormSet(object): 
    7397
    7498    def _get_change_forms(self):
    7599        """Return a list of all the change forms in this ``FormSet``."""
    76         FormClass = self.form_class
    77100        if not hasattr(self, '_change_forms'):
    78101            change_forms = []
    79102            for i in range(0, self.change_form_count):
    class BaseFormSet(object): 
    84107                    kwargs['files'] = self.files
    85108                if self.initial:
    86109                    kwargs['initial'] = self.initial[i]
    87                 change_form = FormClass(**kwargs)
     110                change_form = self.get_form_class(i)(**kwargs)
    88111                self.add_fields(change_form, i)
    89112                change_forms.append(change_form)
    90             self._change_forms= change_forms
     113            self._change_forms = change_forms
    91114        return self._change_forms
    92115    change_forms = property(_get_change_forms)
    93116
    class BaseFormSet(object): 
    117140        # Process change forms
    118141        for form in self.change_forms:
    119142            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]:
    121144                    self.deleted_data.append(form.cleaned_data)
    122145                else:
    123146                    self.cleaned_data.append(form.cleaned_data)
    class BaseFormSet(object): 
    144167        add_errors.reverse()
    145168        errors.extend(add_errors)
    146169        # Sort cleaned_data if the formset is orderable.
    147         if self.orderable:
     170        if self._meta.orderable:
    148171            self.cleaned_data.sort(lambda x,y: x[ORDERING_FIELD_NAME] - y[ORDERING_FIELD_NAME])
    149172        # Give self.clean() a chance to do validation
    150173        try:
    class BaseFormSet(object): 
    167190        via formset.non_form_errors()
    168191        """
    169192        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
    170201
    171202    def add_fields(self, form, index):
    172203        """A hook for adding extra fields on to each form instance."""
    173         if self.orderable:
     204        if self._meta.orderable:
    174205            form.fields[ORDERING_FIELD_NAME] = IntegerField(label='Order', initial=index+1)
    175         if self.deletable:
     206        if self._meta.deletable:
    176207            form.fields[DELETION_FIELD_NAME] = BooleanField(label='Delete', required=False)
    177208
    178209    def add_prefix(self, index):
    class BaseFormSet(object): 
    192223        else:
    193224            return Media()
    194225    media = property(_get_media)
     226
     227class FormSet(BaseFormSet):
     228    __metaclass__ = BaseFormSetMetaclass
    195229   
    196 def formset_for_form(form, formset=BaseFormSet, num_extra=1, orderable=False, deletable=False):
     230def formset_for_form(form, formset=FormSet, num_extra=1, orderable=False,
     231                     deletable=False):
    197232    """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       
    201241def all_valid(formsets):
    202242    """Returns true if every formset in formsets is valid."""
    203243    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 
    1313from util import ValidationError, ErrorList
    1414from forms import BaseForm
    1515from fields import Field, ChoiceField, IntegerField, EMPTY_VALUES
    16 from formsets import BaseFormSet, formset_for_form, DELETION_FIELD_NAME
     16from formsets import FormSetOptions, BaseFormSet, formset_for_form, DELETION_FIELD_NAME
     17from options import ModelFormOptions, ModelFormSetOptions, InlineFormSetOptions
    1718from widgets import Select, SelectMultiple, HiddenInput, MultipleHiddenInput
    1819
    1920__all__ = (
    2021    'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model',
    2122    'save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields',
     23    'ModelFormSet', 'InlineFormset', 'modelform_for_model',
    2224    'formset_for_model', 'inline_formset',
    2325    'ModelChoiceField', 'ModelMultipleChoiceField',
    2426)
    def fields_for_model(model, fields=None, exclude=None, formfield_callback=lambda 
    207209            field_list.append((f.name, formfield))
    208210    return SortedDict(field_list)
    209211
    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 
    216212class ModelFormMetaclass(type):
     213    opts_class = ModelFormOptions
     214   
    217215    def __new__(cls, name, bases, attrs,
    218                 formfield_callback=lambda f: f.formfield()):
     216                formfield_callback=lambda f: f.formfield(), **options):
    219217        fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)]
    220218        fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter))
    221219
    class ModelFormMetaclass(type): 
    227225                fields = base.base_fields.items() + fields
    228226        declared_fields = SortedDict(fields)
    229227
    230         opts = ModelFormOptions(attrs.get('Meta', None))
     228        opts = cls.opts_class(options and options or attrs.get('Meta', None))
    231229        attrs['_meta'] = opts
    232230
    233231        # Don't allow more than one Meta model defenition in bases. The fields
    class ModelFormMetaclass(type): 
    260258        else:
    261259            attrs['base_fields'] = declared_fields
    262260        return type.__new__(cls, name, bases, attrs)
    263 
     261       
    264262class BaseModelForm(BaseForm):
    265263    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
    266264                 initial=None, error_class=ErrorList, label_suffix=':', instance=None):
    class BaseModelForm(BaseForm): 
    293291class ModelForm(BaseModelForm):
    294292    __metaclass__ = ModelFormMetaclass
    295293
     294# this should really be named form_for_model.
     295def 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
    296302
    297303# Fields #####################################################################
    298304
    class ModelMultipleChoiceField(ModelChoiceField): 
    407413
    408414# Model-FormSet integration ###################################################
    409415
    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
     416class ModelFormSetMetaclass(ModelFormMetaclass):
     417    opts_class = ModelFormSetOptions
    434418
    435419class BaseModelFormSet(BaseFormSet):
    436420    """
    437421    A ``FormSet`` for editing a queryset and/or adding new objects to it.
    438422    """
    439     model = None
    440     queryset = None
    441423
    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):
    443425        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
    446432        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()
    447439
    448440    def save_new(self, form, commit=True):
    449441        """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)
    451443
    452444    def save_instance(self, form, instance, commit=True):
    453445        """Saves and returns an existing model instance for the given form."""
    class BaseModelFormSet(BaseFormSet): 
    464456            return []
    465457        # Put the objects from self.get_queryset into a dict so they are easy to lookup by pk
    466458        existing_objects = {}
     459        opts = self._meta
    467460        for obj in self.queryset:
    468461            existing_objects[obj.pk] = obj
    469462        saved_instances = []
    470463        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]:
    473466                obj.delete()
    474467            else:
    475468                saved_instances.append(self.save_instance(form, obj, commit=commit))
    class BaseModelFormSet(BaseFormSet): 
    477470
    478471    def save_new_objects(self, commit=True):
    479472        new_objects = []
     473        opts = self._meta
    480474        for form in self.add_forms:
    481475            if form.is_empty():
    482476                continue
    483477            # If someone has marked an add form for deletion, don't save the
    484478            # object. At some point it would be nice if we didn't display
    485479            # 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]:
    487481                continue
    488482            new_objects.append(self.save_new(form, commit=commit))
    489483        return new_objects
    490484
    491485    def add_fields(self, form, index):
    492486        """Add a hidden field for the object's primary key."""
    493         self._pk_field_name = self.model._meta.pk.attname
     487        self._pk_field_name = self._meta.model._meta.pk.attname
    494488        form.fields[self._pk_field_name] = IntegerField(required=False, widget=HiddenInput)
    495489        super(BaseModelFormSet, self).add_fields(form, index)
    496490
    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):
     491class ModelFormSet(BaseModelFormSet):
     492    __metaclass__ = ModelFormSetMetaclass
     493
     494def formset_for_model(model, formset=BaseModelFormSet,
     495                      formfield_callback=lambda f: f.formfield(), **options):
    499496    """
    500497    Returns a FormSet class for the given Django model class. This FormSet
    501498    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 
    504501    This is essentially the same as ``formset_for_queryset``, but automatically
    505502    uses the model's default manager to determine the queryset.
    506503    """
    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
     509class 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)
    511562
    512 class InlineFormset(BaseModelFormSet):
     563class BaseInlineFormSet(BaseModelFormSet):
    513564    """A formset for child objects related to a parent."""
    514     def __init__(self, instance, data=None, files=None):
     565    def __init__(self, *args, **kwargs):
    515566        from django.db.models.fields.related import RelatedObject
    516         self.instance = instance
     567        opts = self._meta
     568        self.instance = kwargs.pop("instance", None)
    517569        # 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)
    521573
    522     def get_queryset(self):
     574    def get_queryset(self, **kwargs):
    523575        """
    524576        Returns this FormSet's queryset, but restricted to children of
    525577        self.instance
    526578        """
    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})
    529581
    530582    def save_new(self, form, commit=True):
    531583        kwargs = {self.fk.get_attname(): self.instance.pk}
    532         new_obj = self.model(**kwargs)
     584        new_obj = self._meta.model(**kwargs)
    533585        return save_instance(form, new_obj, commit=commit)
    534586
    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
     587class InlineFormset(BaseInlineFormSet):
     588    __metaclass__ = InlineFormSetMetaclass
    561589
    562 def inline_formset(parent_model, model, fk_name=None, fields=None, extra=3, orderable=False, deletable=True, formfield_callback=lambda f: f.formfield()):
     590def inline_formset(parent_model, model, formset=InlineFormset,
     591                   formfield_callback=lambda f: f.formfield(), **options):
    563592    """
    564593    Returns an ``InlineFormset`` for the given kwargs.
    565594
    566595    You must provide ``fk_name`` if ``model`` has more than one ``ForeignKey``
    567596    to ``parent_model``.
    568597    """
    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
     2from forms import BaseForm
     3
     4class 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
     22class 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
     30class 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
     41class ModelFormSetOptions(FormSetOptions, ModelFormOptions):
     42    pass
     43
     44class 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): 
    1616
    1717__test__ = {'API_TESTS': """
    1818
    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
    2021
    21 >>> qs = Author.objects.all()
    22 >>> AuthorFormSet = formset_for_model(Author, extra=3)
     22A bare bones verion.
    2323
    24 >>> formset = AuthorFormSet(qs)
     24>>> class AuthorFormSet(ModelFormSet):
     25...     class Meta:
     26...         model = Author
     27>>> AuthorFormSet.base_fields.keys()
     28['name']
     29
     30Extra 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
     40Lets 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()
    2548>>> for form in formset.forms:
    2649...     print form.as_p()
    2750<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': """ 
    3558...     'form-2-name': '',
    3659... }
    3760
    38 >>> formset = AuthorFormSet(qs, data=data)
     61>>> formset = AuthorFormSet(data)
    3962>>> formset.is_valid()
    4063True
    4164
    Charles Baudelaire 
    4972
    5073
    5174Gah! 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)
     75authors with an extra form to add him. When subclassing ModelFormSet you can
     76override the get_queryset method to return any queryset we like, but in this
     77case 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()
    6090>>> for form in formset.forms:
    6191...     print form.as_p()
    6292<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. 
    73103...     'form-2-name': 'Paul Verlaine',
    74104... }
    75105
    76 >>> formset = AuthorFormSet(qs, data=data)
     106>>> formset = AuthorFormSet(data)
    77107>>> formset.is_valid()
    78108True
    79109
    Paul Verlaine 
    90120This probably shouldn't happen, but it will. If an add form was marked for
    91121deltetion, make sure we don't save that form.
    92122
    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()
    97134>>> for form in formset.forms:
    98135...     print form.as_p()
    99136<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. 
    117154...     'form-3-DELETE': 'on',
    118155... }
    119156
    120 >>> formset = AuthorFormSet(qs, data=data)
     157>>> formset = AuthorFormSet(data)
    121158>>> formset.is_valid()
    122159True
    123160
    Paul Verlaine 
    134171We can also create a formset that is tied to a parent model. This is how the
    135172admin system's edit inline functionality works.
    136173
    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
    138182
    139 >>> AuthorBooksFormSet = inline_formset(Author, Book, deletable=False, extra=3)
    140183>>> author = Author.objects.get(name='Charles Baudelaire')
    141184
    142 >>> formset = AuthorBooksFormSet(author)
     185>>> formset = AuthorBooksFormSet(instance=author)
    143186>>> for form in formset.forms:
    144187...     print form.as_p()
    145188<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. 
    153196...     'book_set-2-title': '',
    154197... }
    155198
    156 >>> formset = AuthorBooksFormSet(author, data=data)
     199>>> formset = AuthorBooksFormSet(data, instance=author)
    157200>>> formset.is_valid()
    158201True
    159202
    Now that we've added a book to Charles Baudelaire, let's try adding another 
    169212one. This time though, an edit form will be available for every existing
    170213book.
    171214
    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
    173222>>> author = Author.objects.get(name='Charles Baudelaire')
    174223
    175 >>> formset = AuthorBooksFormSet(author)
     224>>> formset = AuthorBooksFormSet(instance=author)
    176225>>> for form in formset.forms:
    177226...     print form.as_p()
    178227<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. 
    187236...     'book_set-2-title': '',
    188237... }
    189238
    190 >>> formset = AuthorBooksFormSet(author, data=data)
     239>>> formset = AuthorBooksFormSet(data, instance=author)
    191240>>> formset.is_valid()
    192241True
    193242
  • 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  
    22formset_tests = """
    33# Basic FormSet creation and usage ############################################
    44
    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.
     5FormSet allows us to use multiple instance of the same form on 1 page. Create
     6the formset as you would a regular form by defining the fields declaratively.
    77
    88>>> 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
    1011
    11 >>> class Choice(Form):
     12>>> class ChoiceFormSet(FormSet):
    1213...     choice = CharField()
    1314...     votes = IntegerField()
    1415
    15 >>> ChoiceFormSet = formset_for_form(Choice)
    16 
    1716
    1817A FormSet constructor takes the same arguments as Form. Let's create a FormSet
    1918for adding data. By default, it displays 1 blank form. It can display more,
    False 
    145144>>> formset.errors
    146145[{'votes': [u'This field is required.'], 'choice': [u'This field is required.']}]
    147146
     147# Subclassing a FormSet class #################################################
     148
     149We can subclass a FormSet to add addition fields to an already exisiting
     150FormSet.
     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>
    148161
    149162# Displaying more than 1 blank form ###########################################
    150163
    151 We can also display more than 1 empty form at a time. To do so, pass a
    152 num_extra argument to formset_for_form.
     164We can also display more than 1 empty form at a time. To do so, create an inner
     165Meta class with an attribute num_extra.
    153166
    154 >>> ChoiceFormSet = formset_for_form(Choice, num_extra=3)
     167>>> class NumExtraChoiceFormSet(ChoiceFormSet):
     168...     class Meta:
     169...         num_extra = 3
    155170
    156 >>> formset = ChoiceFormSet(auto_id=False, prefix='choices')
     171>>> formset = NumExtraChoiceFormSet(auto_id=False, prefix='choices')
    157172>>> for form in formset.forms:
    158173...    print form.as_ul()
    159174<li>Choice: <input type="text" name="choices-0-choice" /></li>
    number of forms to be completed. 
    177192...     'choices-2-votes': '',
    178193... }
    179194
    180 >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
     195>>> formset = NumExtraChoiceFormSet(data, auto_id=False, prefix='choices')
    181196>>> formset.is_valid()
    182197True
    183198>>> formset.cleaned_data
    We can just fill out one of the forms. 
    196211...     'choices-2-votes': '',
    197212... }
    198213
    199 >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
     214>>> formset = NumExtraChoiceFormSet(data, auto_id=False, prefix='choices')
    200215>>> formset.is_valid()
    201216True
    202217>>> formset.cleaned_data
    And once again, if we try to partially complete a form, validation will fail. 
    215230...     'choices-2-votes': '',
    216231... }
    217232
    218 >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
     233>>> formset = NumExtraChoiceFormSet(data, auto_id=False, prefix='choices')
    219234>>> formset.is_valid()
    220235False
    221236>>> formset.errors
    The num_extra argument also works when the formset is pre-filled with initial 
    226241data.
    227242
    228243>>> 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')
    230245>>> for form in formset.forms:
    231246...    print form.as_ul()
    232247<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
    get an error. 
    254269...     'choices-3-votes': '',
    255270... }
    256271
    257 >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
     272>>> formset = NumExtraChoiceFormSet(data, auto_id=False, prefix='choices')
    258273>>> formset.is_valid()
    259274False
    260275>>> formset.errors
    False 
    263278
    264279# FormSets with deletion ######################################################
    265280
    266 We can easily add deletion ability to a FormSet with an agrument to
    267 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_data
     281We can easily add deletion ability to a FormSet by setting deletable to True
     282in the inner Meta class. This will add a boolean field to each form instance.
     283When that boolean field is True, the cleaned data will be in formset.deleted_data
    269284rather than formset.cleaned_data
    270285
    271 >>> ChoiceFormSet = formset_for_form(Choice, deletable=True)
     286>>> class DeletableChoiceFormSet(ChoiceFormSet):
     287...     class Meta:
     288...         deletable = True
    272289
    273290>>> 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')
    275292>>> for form in formset.forms:
    276293...    print form.as_ul()
    277294<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 
    300317...     'choices-2-DELETE': '',
    301318... }
    302319
    303 >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
     320>>> formset = DeletableChoiceFormSet(data, auto_id=False, prefix='choices')
    304321>>> formset.is_valid()
    305322True
    306323>>> formset.cleaned_data
    True 
    310327
    311328# FormSets with ordering ######################################################
    312329
    313 We can also add ordering ability to a FormSet with an agrument to
    314 formset_for_form. This will add a integer field to each form instance. When
     330We can also add ordering ability to a FormSet by setting orderable to True in
     331the inner Meta class. This will add a integer field to each form instance. When
    315332form validation succeeds, formset.cleaned_data will have the data in the correct
    316333order specified by the ordering fields. If a number is duplicated in the set
    317334of ordering fields, for instance form 0 and form 3 are both marked as 1, then
    318335the form index used as a secondary ordering criteria. In order to put
    319336something at the front of the list, you'd need to set it's order to 0.
    320337
    321 >>> ChoiceFormSet = formset_for_form(Choice, orderable=True)
     338>>> class OrderableChoiceFormSet(ChoiceFormSet):
     339...     class Meta:
     340...         orderable = True
    322341
    323342>>> 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')
    325344>>> for form in formset.forms:
    326345...    print form.as_ul()
    327346<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. 
    347366...     'choices-2-ORDER': '0',
    348367... }
    349368
    350 >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
     369>>> formset = OrderableChoiceFormSet(data, auto_id=False, prefix='choices')
    351370>>> formset.is_valid()
    352371True
    353372>>> for cleaned_data in formset.cleaned_data:
    True 
    359378# FormSets with ordering + deletion ###########################################
    360379
    361380Let's try throwing ordering and deletion into the same form.
     381TODO: Perhaps handle Meta class inheritance so you can subclass
     382OrderableChoiceFormSet and DeletableChoiceFormSet?
    362383
    363 >>> ChoiceFormSet = formset_for_form(Choice, orderable=True, deletable=True)
     384>>> class MixedChoiceFormSet(ChoiceFormSet):
     385...     class Meta:
     386...         orderable = True
     387...         deletable = True
    364388
    365389>>> initial = [
    366390...     {'choice': u'Calexico', 'votes': 100},
    367391...     {'choice': u'Fergie', 'votes': 900},
    368392...     {'choice': u'The Decemberists', 'votes': 500},
    369393... ]
    370 >>> formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
     394>>> formset = MixedChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
    371395>>> for form in formset.forms:
    372396...    print form.as_ul()
    373397<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
    Let's delete Fergie, and put The Decemberists ahead of Calexico. 
    409433...     'choices-3-DELETE': '',
    410434... }
    411435
    412 >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
     436>>> formset = MixedChoiceFormSet(data, auto_id=False, prefix='choices')
    413437>>> formset.is_valid()
    414438True
    415439>>> for cleaned_data in formset.cleaned_data:
    particular form. It follows the same pattern as the clean hook on Forms. 
    428452Let's define a FormSet that takes a list of favorite drinks, but raises am
    429453error if there are any duplicates.
    430454
    431 >>> class FavoriteDrinkForm(Form):
     455>>> class FavoriteDrinksFormSet(FormSet):
    432456...     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
    440462...
    441463...     def clean(self):
    442464...         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': """ 
    2121Child has two ForeignKeys to Parent, so if we don't specify which one to use
    2222for the inline formset, we should get an exception.
    2323
    24 >>> ifs = inline_formset(Parent, Child)
     24>>> inline_formset(Parent, Child)()
    2525Traceback (most recent call last):
    2626    ...
    2727Exception: <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 
    2929
    3030These two should both work without a problem.
    3131
    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')()
    3434
    3535
    3636If we specify fk_name, but it isn't a ForeignKey from the child model to the
    3737parent model, we should get an exception.
    3838
    39 >>> ifs = inline_formset(Parent, Child, fk_name='school')
     39>>> inline_formset(Parent, Child, fk_name='school')()
    4040Traceback (most recent call last):
    4141    ...
    4242Exception: 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 
    4545If the field specified in fk_name is not a ForeignKey, we should get an
    4646exception.
    4747
    48 >>> ifs = inline_formset(Parent, Child, fk_name='test')
     48>>> inline_formset(Parent, Child, fk_name='test')()
    4949Traceback (most recent call last):
    5050    ...
    5151Exception: <class 'regressiontests.inline_formsets.models.Child'> has no field named 'test'
Back to Top