Ticket #6241: formset_refactor_8.diff

File formset_refactor_8.diff, 47.9 KB (added by Øyvind Saltvik <oyvind@…>, 7 years ago)

Slight change to BaseModelFormSet to allow queryset as a kwarg

  • django/newforms/options.py

     
     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
  • django/newforms/formsets.py

     
    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'
     
    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
     
    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):
     
    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
     
    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):
     
    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
     
    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)
     
    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:
     
    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):
     
    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

     
    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)
     
    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
     
    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
     
    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):
     
    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)
    296301
     302
    297303# Fields #####################################################################
    298304
    299305class QuerySetIterator(object):
     
    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.
     416class ModelFormSetMetaclass(ModelFormMetaclass):
     417    opts_class = ModelFormSetOptions
    414418
    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
    434 
    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, queryset=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 = queryset or 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."""
     
    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))
     
    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
     
    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)
    511508
    512 class InlineFormset(BaseModelFormSet):
     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)
     562
     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)
  • django/contrib/admin/options.py

     
    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        """
     
    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        """
     
    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)
     
    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.
     
    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'
     
    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()):
     
    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

     
    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
  • tests/modeltests/model_formsets/models.py

     
    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>
     
    3558...     'form-2-name': '',
    3659... }
    3760
    38 >>> formset = AuthorFormSet(qs, data=data)
     61>>> formset = AuthorFormSet(data)
    3962>>> formset.is_valid()
    4063True
    4164
     
    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.
     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.
    5578
    56 >>> qs = Author.objects.order_by('name')
    57 >>> AuthorFormSet = formset_for_model(Author, extra=1, deletable=False)
     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')
    5888
    59 >>> formset = AuthorFormSet(qs)
     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>
     
    73103...     'form-2-name': 'Paul Verlaine',
    74104... }
    75105
    76 >>> formset = AuthorFormSet(qs, data=data)
     106>>> formset = AuthorFormSet(data)
    77107>>> formset.is_valid()
    78108True
    79109
     
    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)
     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')
    95132
    96 >>> formset = AuthorFormSet(qs)
     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>
     
    117154...     'form-3-DELETE': 'on',
    118155... }
    119156
    120 >>> formset = AuthorFormSet(qs, data=data)
     157>>> formset = AuthorFormSet(data)
    121158>>> formset.is_valid()
    122159True
    123160
     
    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
    138175
    139 >>> AuthorBooksFormSet = inline_formset(Author, Book, deletable=False, extra=3)
     176>>> class AuthorBooksFormSet(InlineFormset):
     177...     class Meta:
     178...         parent_model = Author
     179...         model = Book
     180...         num_extra = 3
     181...         deletable = False
     182
    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>
     
    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
     
    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>
     
    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

     
    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)
    1616
    17 
    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,
    2019but we'll look at how to do so later.
     
    145144>>> formset.errors
    146145[{'votes': [u'This field is required.'], 'choice': [u'This field is required.']}]
    147146
     147# Subclassing a FormSet class #################################################
    148148
     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>
     161
    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>
     
    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
     
    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
     
    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
     
    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>
     
    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
     
    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>
     
    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
     
    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>
     
    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:
     
    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>
     
    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:
     
    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()
     457...     
     458...     class Meta:
     459...         num_extra = 2
     460...         orderable = False
     461...         deletable = False
    433462...
    434 
    435 >>> class FavoriteDrinksFormSet(BaseFormSet):
    436 ...     form_class = FavoriteDrinkForm
    437 ...     num_extra = 2
    438 ...     orderable = False
    439 ...     deletable = False
    440 ...
    441463...     def clean(self):
    442464...         seen_drinks = []
    443465...         for drink in self.cleaned_data:
  • tests/regressiontests/inline_formsets/models.py

     
    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'>
     
    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'>
     
    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