Index: django/contrib/admin/options.py
===================================================================
--- django/contrib/admin/options.py	(revision 6956)
+++ django/contrib/admin/options.py	(working copy)
@@ -342,7 +342,7 @@
             fields = flatten_fieldsets(self.declared_fieldsets)
         else:
             fields = None
-        return forms.form_for_model(self.model, fields=fields, formfield_callback=self.formfield_for_dbfield)
+        return forms.modelform_for_model(self.model, fields=fields, formfield_callback=self.formfield_for_dbfield)
 
     def form_change(self, request, obj):
         """
@@ -352,7 +352,7 @@
             fields = flatten_fieldsets(self.declared_fieldsets)
         else:
             fields = None
-        return forms.form_for_instance(obj, fields=fields, formfield_callback=self.formfield_for_dbfield)
+        return forms.modelform_for_model(self.model, fields=fields, formfield_callback=self.formfield_for_dbfield)
 
     def save_add(self, request, model, form, formsets, post_url_continue):
         """
@@ -492,20 +492,20 @@
             # Object list will give 'Permission Denied', so go back to admin home
             post_url = '../../../'
 
-        ModelForm = self.form_add(request)
+        Form = self.form_add(request)
         inline_formsets = []
         obj = self.model()
         if request.method == 'POST':
-            form = ModelForm(request.POST, request.FILES)
+            form = Form(request.POST, request.FILES)
             for FormSet in self.formsets_add(request):
-                inline_formset = FormSet(obj, data=request.POST, files=request.FILES)
+                inline_formset = FormSet(request.POST, request.FILES)
                 inline_formsets.append(inline_formset)
             if all_valid(inline_formsets) and form.is_valid():
                 return self.save_add(request, model, form, inline_formsets, '../%s/')
         else:
-            form = ModelForm(initial=request.GET)
+            form = Form(initial=request.GET)
             for FormSet in self.formsets_add(request):
-                inline_formset = FormSet(obj)
+                inline_formset = FormSet()
                 inline_formsets.append(inline_formset)
 
         adminForm = AdminForm(form, list(self.fieldsets_add(request)), self.prepopulated_fields)
@@ -552,20 +552,20 @@
         if request.POST and request.POST.has_key("_saveasnew"):
             return self.add_view(request, form_url='../../add/')
 
-        ModelForm = self.form_change(request, obj)
+        Form = self.form_change(request, obj)
         inline_formsets = []
         if request.method == 'POST':
-            form = ModelForm(request.POST, request.FILES)
+            form = Form(request.POST, request.FILES, instance=obj)
             for FormSet in self.formsets_change(request, obj):
-                inline_formset = FormSet(obj, request.POST, request.FILES)
+                inline_formset = FormSet(request.POST, request.FILES, instance=obj)
                 inline_formsets.append(inline_formset)
 
             if all_valid(inline_formsets) and form.is_valid():
                 return self.save_change(request, model, form, inline_formsets)
         else:
-            form = ModelForm()
+            form = Form(instance=obj, initial=request.GET)
             for FormSet in self.formsets_change(request, obj):
-                inline_formset = FormSet(obj)
+                inline_formset = FormSet(instance=obj)
                 inline_formsets.append(inline_formset)
 
         ## Populate the FormWrapper.
@@ -755,15 +755,17 @@
     def fieldsets_add(self, request):
         if self.declared_fieldsets:
             return self.declared_fieldsets
-        form = self.formset_add(request).form_class
-        return [(None, {'fields': form.base_fields.keys()})]
+        formset = self.formset_add(request)
+        return [(None, {'fields': formset.base_fields.keys()})]
 
+
     def fieldsets_change(self, request, obj):
         if self.declared_fieldsets:
             return self.declared_fieldsets
-        form = self.formset_change(request, obj).form_class
-        return [(None, {'fields': form.base_fields.keys()})]
+        formset = self.formset_change(request, obj)
+        return [(None, {'fields': formset.base_fields.keys()})]
 
+
 class StackedInline(InlineModelAdmin):
     template = 'admin/edit_inline/stacked.html'
 
@@ -778,6 +780,10 @@
         self.opts = inline
         self.formset = formset
         self.fieldsets = fieldsets
+        # place orderable and deletable here since _meta is inaccesible in the
+        # templates.
+        self.orderable = formset._meta.orderable
+        self.deletable = formset._meta.deletable
 
     def __iter__(self):
         for form, original in zip(self.formset.change_forms, self.formset.get_queryset()):
@@ -787,7 +793,7 @@
 
     def fields(self):
         for field_name in flatten_fieldsets(self.fieldsets):
-            yield self.formset.form_class.base_fields[field_name]
+            yield self.formset.base_fields[field_name]
 
 class InlineAdminForm(AdminForm):
     """
Index: django/contrib/admin/templates/admin/edit_inline/tabular.html
===================================================================
--- django/contrib/admin/templates/admin/edit_inline/tabular.html	(revision 6956)
+++ django/contrib/admin/templates/admin/edit_inline/tabular.html	(working copy)
@@ -11,7 +11,7 @@
          <th {% if forloop.first %}colspan="2"{% endif %}>{{ field.label|capfirst|escape }}</th>
         {% endif %}
      {% endfor %}
-     {% if inline_admin_formset.formset.deletable %}<th>{% trans "Delete" %}?</th>{% endif %}
+     {% if inline_admin_formset.deletable %}<th>{% trans "Delete" %}?</th>{% endif %}
      </tr></thead>
    
      {% for inline_admin_form in inline_admin_formset %}
@@ -45,7 +45,7 @@
           {% endfor %}
         {% endfor %}
                 
-        {% if inline_admin_formset.formset.deletable %}<td class="delete">{{ inline_admin_form.deletion_field.field }}</td>{% endif %}
+        {% if inline_admin_formset.deletable %}<td class="delete">{{ inline_admin_form.deletion_field.field }}</td>{% endif %}
         
         </tr>
 
Index: django/newforms/options.py
===================================================================
--- django/newforms/options.py	(revision 0)
+++ django/newforms/options.py	(revision 0)
@@ -0,0 +1,50 @@
+
+from forms import BaseForm
+
+class BaseFormOptions(object):
+    """
+    The base class for all options that are associated to a form object.
+    """
+    def __init__(self, options=None):
+        self.fields = self._dynamic_attribute(options, "fields")
+        self.exclude = self._dynamic_attribute(options, "exclude")
+
+    def _dynamic_attribute(self, obj, key, default=None):
+        try:
+            return getattr(obj, key)
+        except AttributeError:
+            try:
+                return obj[key]
+            except (TypeError, KeyError):
+                # key doesnt exist in obj or obj is None
+                return default
+
+class ModelFormOptions(BaseFormOptions):
+    """
+    Encapsulates the options on a ModelForm class.
+    """
+    def __init__(self, options=None):
+        self.model = self._dynamic_attribute(options, "model")
+        super(ModelFormOptions, self).__init__(options)
+
+class FormSetOptions(BaseFormOptions):
+    """
+    Encapsulates the options on a FormSet class.
+    """
+    def __init__(self, options=None):
+        self.form = self._dynamic_attribute(options, "form", BaseForm)
+        self.num_extra = self._dynamic_attribute(options, "num_extra", 1)
+        self.orderable = self._dynamic_attribute(options, "orderable", False)
+        self.deletable = self._dynamic_attribute(options, "deletable", False)
+        super(FormSetOptions, self).__init__(options)
+
+class ModelFormSetOptions(FormSetOptions, ModelFormOptions):
+    def __init__(self, options=None):
+        super(ModelFormSetOptions, self).__init__(options)
+        self.deletable = True
+
+class InlineFormSetOptions(ModelFormSetOptions):
+    def __init__(self, options=None):
+        super(InlineFormSetOptions, self).__init__(options)
+        self.parent_model = self._dynamic_attribute(options, "parent_model")
+ 
\ No newline at end of file
Index: django/newforms/formsets.py
===================================================================
--- django/newforms/formsets.py	(revision 6956)
+++ django/newforms/formsets.py	(working copy)
@@ -1,9 +1,16 @@
-from forms import Form
-from fields import IntegerField, BooleanField
+from warnings import warn
+
+from django.utils.datastructures import SortedDict
+from django.utils.translation import ugettext_lazy as _
+
+from forms import BaseForm, Form
+from fields import Field, IntegerField, BooleanField
+from options import FormSetOptions
+
 from widgets import HiddenInput, Media
 from util import ErrorList, ValidationError
 
-__all__ = ('BaseFormSet', 'formset_for_form', 'all_valid')
+__all__ = ('BaseFormSet', 'FormSet', 'formset_for_form', 'all_valid')
 
 # special field names
 FORM_COUNT_FIELD_NAME = 'COUNT'
@@ -19,7 +26,25 @@
     def __init__(self, *args, **kwargs):
         self.base_fields[FORM_COUNT_FIELD_NAME] = IntegerField(widget=HiddenInput)
         super(ManagementForm, self).__init__(*args, **kwargs)
+        
+class BaseFormSetMetaclass(type):
+    def __new__(cls, name, bases, attrs, **options):
+        fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)]
+        fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter))
 
+        # If this class is subclassing another FormSet, ad that FormSet's fields.
+        # Note that we loop over the bases in *reverse*. This is necessary in
+        # order to preserve the correct order of fields.
+        for base in bases[::-1]:
+            if hasattr(base, "base_fields"):
+                fields = base.base_fields.items() + fields
+        attrs["base_fields"] = SortedDict(fields)
+
+        opts = FormSetOptions(options and options or attrs.get("Meta", None))
+        attrs["_meta"] = opts
+
+        return type.__new__(cls, name, bases, attrs)
+
 class BaseFormSet(object):
     """A collection of instances of the same Form class."""
 
@@ -37,25 +62,24 @@
             self.management_form = ManagementForm(data, files, auto_id=self.auto_id, prefix=self.prefix)
             if self.management_form.is_valid():
                 self.total_forms = self.management_form.cleaned_data[FORM_COUNT_FIELD_NAME]
-                self.required_forms = self.total_forms - self.num_extra
-                self.change_form_count = self.total_forms - self.num_extra
+                self.required_forms = self.total_forms - self._meta.num_extra
+                self.change_form_count = self.total_forms - self._meta.num_extra
             else:
                 # not sure that ValidationError is the best thing to raise here
                 raise ValidationError('ManagementForm data is missing or has been tampered with')
         elif initial:
             self.change_form_count = len(initial)
             self.required_forms = len(initial)
-            self.total_forms = self.required_forms + self.num_extra
+            self.total_forms = self.required_forms + self._meta.num_extra
             self.management_form = ManagementForm(initial={FORM_COUNT_FIELD_NAME: self.total_forms}, auto_id=self.auto_id, prefix=self.prefix)
         else:
             self.change_form_count = 0
             self.required_forms = 0
-            self.total_forms = self.num_extra
+            self.total_forms = self._meta.num_extra
             self.management_form = ManagementForm(initial={FORM_COUNT_FIELD_NAME: self.total_forms}, auto_id=self.auto_id, prefix=self.prefix)
 
     def _get_add_forms(self):
         """Return a list of all the add forms in this ``FormSet``."""
-        FormClass = self.form_class
         if not hasattr(self, '_add_forms'):
             add_forms = []
             for i in range(self.change_form_count, self.total_forms):
@@ -64,7 +88,7 @@
                     kwargs['data'] = self.data
                 if self.files:
                     kwargs['files'] = self.files
-                add_form = FormClass(**kwargs)
+                add_form = self.get_form_class(i)(**kwargs)
                 self.add_fields(add_form, i)
                 add_forms.append(add_form)
             self._add_forms = add_forms
@@ -73,7 +97,6 @@
 
     def _get_change_forms(self):
         """Return a list of all the change forms in this ``FormSet``."""
-        FormClass = self.form_class
         if not hasattr(self, '_change_forms'):
             change_forms = []
             for i in range(0, self.change_form_count):
@@ -84,10 +107,10 @@
                     kwargs['files'] = self.files
                 if self.initial:
                     kwargs['initial'] = self.initial[i]
-                change_form = FormClass(**kwargs)
+                change_form = self.get_form_class(i)(**kwargs)
                 self.add_fields(change_form, i)
                 change_forms.append(change_form)
-            self._change_forms= change_forms
+            self._change_forms = change_forms
         return self._change_forms
     change_forms = property(_get_change_forms)
 
@@ -117,7 +140,7 @@
         # Process change forms
         for form in self.change_forms:
             if form.is_valid():
-                if self.deletable and form.cleaned_data[DELETION_FIELD_NAME]:
+                if self._meta.deletable and form.cleaned_data[DELETION_FIELD_NAME]:
                     self.deleted_data.append(form.cleaned_data)
                 else:
                     self.cleaned_data.append(form.cleaned_data)
@@ -144,7 +167,7 @@
         add_errors.reverse()
         errors.extend(add_errors)
         # Sort cleaned_data if the formset is orderable.
-        if self.orderable:
+        if self._meta.orderable:
             self.cleaned_data.sort(lambda x,y: x[ORDERING_FIELD_NAME] - y[ORDERING_FIELD_NAME])
         # Give self.clean() a chance to do validation
         try:
@@ -168,11 +191,19 @@
         """
         return self.cleaned_data
 
+    def get_form_class(self, index):
+        """
+        A hook to change a form class object.
+        """
+        FormClass = self._meta.form
+        FormClass.base_fields = self.base_fields
+        return FormClass
+
     def add_fields(self, form, index):
         """A hook for adding extra fields on to each form instance."""
-        if self.orderable:
+        if self._meta.orderable:
             form.fields[ORDERING_FIELD_NAME] = IntegerField(label='Order', initial=index+1)
-        if self.deletable:
+        if self._meta.deletable:
             form.fields[DELETION_FIELD_NAME] = BooleanField(label='Delete', required=False)
 
     def add_prefix(self, index):
@@ -193,10 +224,20 @@
             return Media()
     media = property(_get_media)
     
-def formset_for_form(form, formset=BaseFormSet, num_extra=1, orderable=False, deletable=False):
+class FormSet(BaseFormSet):
+    __metaclass__ = BaseFormSetMetaclass
+    
+def formset_for_form(form, formset=FormSet, num_extra=1, orderable=False,
+                     deletable=False):
+
     """Return a FormSet for the given form class."""
-    attrs = {'form_class': form, 'num_extra': num_extra, 'orderable': orderable, 'deletable': deletable}
-    return type(form.__name__ + 'FormSet', (formset,), attrs)
+    warn("formset_for_form is deprecated, use FormSet instead.",
+         PendingDeprecationWarning,
+         stacklevel=3)
+    return BaseFormSetMetaclass(
+        form.__name__ + "FormSet", (formset,), form.base_fields,
+        form=form, num_extra=num_extra, orderable=orderable,
+        deletable=deletable)
 
 def all_valid(formsets):
     """Returns true if every formset in formsets is valid."""
Index: django/newforms/models.py
===================================================================
--- django/newforms/models.py	(revision 6956)
+++ django/newforms/models.py	(working copy)
@@ -13,12 +13,14 @@
 from util import ValidationError, ErrorList
 from forms import BaseForm
 from fields import Field, ChoiceField, IntegerField, EMPTY_VALUES
-from formsets import BaseFormSet, formset_for_form, DELETION_FIELD_NAME
+from formsets import FormSetOptions, BaseFormSet, formset_for_form, DELETION_FIELD_NAME
+from options import ModelFormOptions, ModelFormSetOptions, InlineFormSetOptions
 from widgets import Select, SelectMultiple, HiddenInput, MultipleHiddenInput
 
 __all__ = (
     'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model',
     'save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields',
+    'ModelFormSet', 'InlineFormset', 'modelform_for_model',
     'formset_for_model', 'inline_formset',
     'ModelChoiceField', 'ModelMultipleChoiceField',
 )
@@ -207,15 +209,12 @@
             field_list.append((f.name, formfield))
     return SortedDict(field_list)
 
-class ModelFormOptions(object):
-    def __init__(self, options=None):
-        self.model = getattr(options, 'model', None)
-        self.fields = getattr(options, 'fields', None)
-        self.exclude = getattr(options, 'exclude', None)
-
 class ModelFormMetaclass(type):
+    
+    opts_class = ModelFormOptions
+    
     def __new__(cls, name, bases, attrs,
-                formfield_callback=lambda f: f.formfield()):
+                formfield_callback=lambda f: f.formfield(), **options):
         fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)]
         fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter))
 
@@ -227,7 +226,7 @@
                 fields = base.base_fields.items() + fields
         declared_fields = SortedDict(fields)
 
-        opts = ModelFormOptions(attrs.get('Meta', None))
+        opts = cls.opts_class(options and options or attrs.get('Meta', None))
         attrs['_meta'] = opts
 
         # Don't allow more than one Meta model defenition in bases. The fields
@@ -293,6 +292,13 @@
 class ModelForm(BaseModelForm):
     __metaclass__ = ModelFormMetaclass
 
+# this should really be named form_for_model.
+def modelform_for_model(model, form=ModelForm,
+                        formfield_callback=lambda f: f.formfield(), **options):
+    opts = model._meta
+    options.update({"model": model})
+    return ModelFormMetaclass(opts.object_name + "ModelForm", (form,),
+                              {}, formfield_callback, **options)
 
 # Fields #####################################################################
 
@@ -407,31 +413,9 @@
 
 # Model-FormSet integration ###################################################
 
-def initial_data(instance, fields=None):
-    """
-    Return a dictionary from data in ``instance`` that is suitable for
-    use as a ``Form`` constructor's ``initial`` argument.
+class ModelFormSetMetaclass(ModelFormMetaclass):
+    opts_class = ModelFormSetOptions
 
-    Provide ``fields`` to specify the names of specific fields to return.
-    All field values in the instance will be returned if ``fields`` is not
-    provided.
-    """
-    # avoid a circular import
-    from django.db.models.fields.related import ManyToManyField
-    opts = instance._meta
-    initial = {}
-    for f in opts.fields + opts.many_to_many:
-        if not f.editable:
-            continue
-        if fields and not f.name in fields:
-            continue
-        if isinstance(f, ManyToManyField):
-            # MultipleChoiceWidget needs a list of ints, not object instances.
-            initial[f.name] = [obj.pk for obj in f.value_from_object(instance)]
-        else:
-            initial[f.name] = f.value_from_object(instance)
-    return initial
-
 class BaseModelFormSet(BaseFormSet):
     """
     A ``FormSet`` for editing a queryset and/or adding new objects to it.
@@ -439,19 +423,30 @@
     model = None
     queryset = None
 
-    def __init__(self, qs, data=None, files=None, auto_id='id_%s', prefix=None):
+    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None):
         kwargs = {'data': data, 'files': files, 'auto_id': auto_id, 'prefix': prefix}
-        self.queryset = qs
-        kwargs['initial'] = [initial_data(obj) for obj in qs]
+        opts = self._meta
+        self.queryset = self.get_queryset(**kwargs)
+        initial_data = []
+        for obj in self.queryset:
+            initial_data.append(model_to_dict(obj, opts.fields, opts.exclude))
+        kwargs['initial'] = initial_data
+
         super(BaseModelFormSet, self).__init__(**kwargs)
 
+    def get_queryset(self, **kwargs):
+        """
+        Hook to returning a queryset for this model.
+        """
+        return self._meta.model._default_manager.all()
+
     def save_new(self, form, commit=True):
         """Saves and returns a new model instance for the given form."""
-        return save_instance(form, self.model(), commit=commit)
+        return save_instance(form, self._meta.model(), commit=commit)
 
     def save_instance(self, form, instance, commit=True):
         """Saves and returns an existing model instance for the given form."""
-        return save_instance(form, instance, commit=commit)
+        return save_instance(form, self._meta.model(), commit=commit)
 
     def save(self, commit=True):
         """Saves model instances for every form, adding and changing instances
@@ -464,12 +459,13 @@
             return []
         # Put the objects from self.get_queryset into a dict so they are easy to lookup by pk
         existing_objects = {}
+        opts = self._meta
         for obj in self.queryset:
             existing_objects[obj.pk] = obj
         saved_instances = []
         for form in self.change_forms:
-            obj = existing_objects[form.cleaned_data[self.model._meta.pk.attname]]
-            if self.deletable and form.cleaned_data[DELETION_FIELD_NAME]:
+            obj = existing_objects[form.cleaned_data[opts.model._meta.pk.attname]]
+            if opts.deletable and form.cleaned_data[DELETION_FIELD_NAME]:
                 obj.delete()
             else:
                 saved_instances.append(self.save_instance(form, obj, commit=commit))
@@ -483,19 +479,23 @@
             # If someone has marked an add form for deletion, don't save the
             # object. At some point it would be nice if we didn't display
             # the deletion widget for add forms.
-            if self.deletable and form.cleaned_data[DELETION_FIELD_NAME]:
+            opts = self._meta
+            if opts.self.deletable and form.cleaned_data[DELETION_FIELD_NAME]:
                 continue
             new_objects.append(self.save_new(form, commit=commit))
         return new_objects
 
     def add_fields(self, form, index):
         """Add a hidden field for the object's primary key."""
-        self._pk_field_name = self.model._meta.pk.attname
+        self._pk_field_name = self._meta.model._meta.pk.attname
         form.fields[self._pk_field_name] = IntegerField(required=False, widget=HiddenInput)
         super(BaseModelFormSet, self).add_fields(form, index)
 
-def formset_for_model(model, form=BaseForm, formfield_callback=lambda f: f.formfield(),
-                      formset=BaseModelFormSet, extra=1, orderable=False, deletable=False, fields=None):
+class ModelFormSet(BaseModelFormSet):
+    __metaclass__ = ModelFormSetMetaclass
+
+def formset_for_model(model, formset=BaseModelFormSet,
+                      formfield_callback=lambda f: f.formfield(), **options):
     """
     Returns a FormSet class for the given Django model class. This FormSet
     will contain change forms for every instance of the given model as well
@@ -504,80 +504,100 @@
     This is essentially the same as ``formset_for_queryset``, but automatically
     uses the model's default manager to determine the queryset.
     """
-    form = form_for_model(model, form=form, fields=fields, formfield_callback=formfield_callback)
-    FormSet = formset_for_form(form, formset, extra, orderable, deletable)
-    FormSet.model = model
-    return FormSet
+    opts = model._meta
+    options.update({"model": model})
+    return ModelFormSetMetaclass(opts.object_name + "ModelFormSet", (formset,),
+                                 {}, **options)
 
-class InlineFormset(BaseModelFormSet):
+class InlineFormSetMetaclass(ModelFormSetMetaclass):
+    opts_class = InlineFormSetOptions
+
+    def __new__(cls, name, bases, attrs,
+                formfield_callback=lambda f: f.formfield(), **options):
+        formset = super(InlineFormSetMetaclass, cls).__new__(cls, name, bases, attrs,
+            formfield_callback, **options)
+        # If this isn't a subclass of InlineFormset, don't do anything special.
+        try:
+            if not filter(lambda b: issubclass(b, InlineFormset), bases):
+                return formset
+        except NameError:
+            # 'InlineFormset' isn't defined yet, meaning we're looking at
+            # Django's own InlineFormset class, defined below.
+            return formset
+        opts = formset._meta
+        # resolve the foreign key
+        fk = cls.resolve_foreign_key(opts.parent_model, opts.model, opts.fk_name)
+        # remove the fk from base_fields to keep it transparent to the form.
+        try:
+            del formset.base_fields[fk.name]
+        except KeyError:
+            pass
+        formset.fk = fk
+        return formset
+
+    def _resolve_foreign_key(cls, parent_model, model, fk_name=None):
+        """
+        Finds and returns the ForeignKey from model to parent if there is one.
+        If fk_name is provided, assume it is the name of the ForeignKey field.
+        """
+        # avoid a circular import
+        from django.db.models import ForeignKey
+        opts = model._meta
+        if fk_name:
+            fks_to_parent = [f for f in opts.fields if f.name == fk_name]
+            if len(fks_to_parent) == 1:
+                fk = fks_to_parent[0]
+                if not isinstance(fk, ForeignKey) or fk.rel.to != parent_model:
+                    raise Exception("fk_name '%s' is not a ForeignKey to %s" % (fk_name, parent_model))
+            elif len(fks_to_parent) == 0:
+                raise Exception("%s has no field named '%s'" % (model, fk_name))
+        else:
+            # Try to discover what the ForeignKey from model to parent_model is
+            fks_to_parent = [f for f in opts.fields if isinstance(f, ForeignKey) and f.rel.to == parent_model]
+            if len(fks_to_parent) == 1:
+                fk = fks_to_parent[0]
+            elif len(fks_to_parent) == 0:
+                raise Exception("%s has no ForeignKey to %s" % (model, parent_model))
+            else:
+                raise Exception("%s has more than 1 ForeignKey to %s" % (model, parent_model))
+        return fk
+    resolve_foreign_key = classmethod(_resolve_foreign_key)
+
+class BaseInlineFormSet(BaseModelFormSet):
     """A formset for child objects related to a parent."""
-    def __init__(self, instance, data=None, files=None):
+    def __init__(self, *args, **kwargs):
         from django.db.models.fields.related import RelatedObject
-        self.instance = instance
+        opts = self._meta
+        self.instance = kwargs.pop("instance", None)
         # is there a better way to get the object descriptor?
-        self.rel_name = RelatedObject(self.fk.rel.to, self.model, self.fk).get_accessor_name()
-        qs = self.get_queryset()
-        super(InlineFormset, self).__init__(qs, data, files, prefix=self.rel_name)
+        rel_name = RelatedObject(self.fk.rel.to, opts.model, self.fk).get_accessor_name()
+        kwargs["prefix"] = rel_name
+        super(BaseInlineFormSet, self).__init__(*args, **kwargs)
 
-    def get_queryset(self):
+    def get_queryset(self, **kwargs):
         """
         Returns this FormSet's queryset, but restricted to children of 
         self.instance
         """
-        kwargs = {self.fk.name: self.instance}
-        return self.model._default_manager.filter(**kwargs)
+        queryset = super(BaseInlineFormSet, self).get_queryset(**kwargs)
+        return queryset.filter(**{self.fk.name: self.instance})
 
     def save_new(self, form, commit=True):
         kwargs = {self.fk.get_attname(): self.instance.pk}
-        new_obj = self.model(**kwargs)
+        new_obj = self._meta.model(**kwargs)
         return save_instance(form, new_obj, commit=commit)
+        
+class InlineFormset(BaseInlineFormSet):
+    __metaclass__ = InlineFormSetMetaclass
 
-def get_foreign_key(parent_model, model, fk_name=None):
+def inline_formset(parent_model, model, formset=InlineFormset,
+                   formfield_callback=lambda f: f.formfield(), **options):
     """
-    Finds and returns the ForeignKey from model to parent if there is one.
-    If fk_name is provided, assume it is the name of the ForeignKey field.
-    """
-    # avoid circular import
-    from django.db.models import ForeignKey
-    opts = model._meta
-    if fk_name:
-        fks_to_parent = [f for f in opts.fields if f.name == fk_name]
-        if len(fks_to_parent) == 1:
-            fk = fks_to_parent[0]
-            if not isinstance(fk, ForeignKey) or fk.rel.to != parent_model:
-                raise Exception("fk_name '%s' is not a ForeignKey to %s" % (fk_name, parent_model))
-        elif len(fks_to_parent) == 0:
-            raise Exception("%s has no field named '%s'" % (model, fk_name))
-    else:
-        # Try to discover what the ForeignKey from model to parent_model is
-        fks_to_parent = [f for f in opts.fields if isinstance(f, ForeignKey) and f.rel.to == parent_model]
-        if len(fks_to_parent) == 1:
-            fk = fks_to_parent[0]
-        elif len(fks_to_parent) == 0:
-            raise Exception("%s has no ForeignKey to %s" % (model, parent_model))
-        else:
-            raise Exception("%s has more than 1 ForeignKey to %s" % (model, parent_model))
-    return fk
-
-def inline_formset(parent_model, model, fk_name=None, fields=None, extra=3, orderable=False, deletable=True, formfield_callback=lambda f: f.formfield()):
-    """
     Returns an ``InlineFormset`` for the given kwargs.
 
     You must provide ``fk_name`` if ``model`` has more than one ``ForeignKey``
     to ``parent_model``.
     """
-    fk = get_foreign_key(parent_model, model, fk_name=fk_name)
-    # let the formset handle object deletion by default
-    FormSet = formset_for_model(model, formset=InlineFormset, fields=fields,
-                                formfield_callback=formfield_callback,
-                                extra=extra, orderable=orderable,
-                                deletable=deletable)
-    # HACK: remove the ForeignKey to the parent from every form
-    # This should be done a line above before we pass 'fields' to formset_for_model
-    # an 'omit' argument would be very handy here
-    try:
-        del FormSet.form_class.base_fields[fk.name]
-    except KeyError:
-        pass
-    FormSet.fk = fk
-    return FormSet
+    opts = model._meta
+    options.update({"parent_model": parent_model, "model": model})
+    return InlineFormSetMetaclass(opts.object_name + "InlineFormset", (formset,))
Index: tests/modeltests/model_formsets/models.py
===================================================================
--- tests/modeltests/model_formsets/models.py	(revision 6956)
+++ tests/modeltests/model_formsets/models.py	(working copy)
@@ -16,12 +16,35 @@
 
 __test__ = {'API_TESTS': """
 
->>> from django.newforms.models import formset_for_model
+>>> from django import newforms as forms
+>>> from django.newforms.models import formset_for_model, ModelFormSet
 
->>> qs = Author.objects.all()
->>> AuthorFormSet = formset_for_model(Author, extra=3)
+A bare bones verion. 
 
->>> formset = AuthorFormSet(qs)
+>>> class AuthorFormSet(ModelFormSet):
+...     class Meta:
+...         model = Author
+>>> AuthorFormSet.base_fields.keys()
+['name']
+
+Extra fields.
+
+>>> class AuthorFormSet(ModelFormSet):
+...     published = forms.BooleanField()
+...
+...     class Meta:
+...         model = Author
+>>> AuthorFormSet.base_fields.keys()
+['name', 'published']
+
+Lets create a formset that is bound to a model.
+
+>>> class AuthorFormSet(ModelFormSet):
+...     class Meta:
+...         model = Author
+...         num_extra = 3
+
+>>> formset = AuthorFormSet()
 >>> for form in formset.forms:
 ...     print form.as_p()
 <p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" maxlength="100" /><input type="hidden" name="form-0-id" id="id_form-0-id" /></p>
@@ -35,7 +58,7 @@
 ...     'form-2-name': '',
 ... }
 
->>> formset = AuthorFormSet(qs, data=data)
+>>> formset = AuthorFormSet(data)
 >>> formset.is_valid()
 True
 
@@ -49,14 +72,21 @@
 
 
 Gah! We forgot Paul Verlaine. Let's create a formset to edit the existing
-authors with an extra form to add him. This time we'll use formset_for_queryset.
-We *could* use formset_for_queryset to restrict the Author objects we edit,
-but in that case we'll use it to display them in alphabetical order by name.
+authors with an extra form to add him. When subclassing ModelFormSet you can
+override the get_queryset method to return any queryset we like, but in this
+case we'll use it to display it in alphabetical order by name.
 
->>> qs = Author.objects.order_by('name')
->>> AuthorFormSet = formset_for_model(Author, extra=1, deletable=False)
+>>> class AuthorFormSet(ModelFormSet):
+...     class Meta:
+...         model = Author
+...         num_extra = 1
+...         deletable = False
+...
+...     def get_queryset(self, **kwargs):
+...         qs = super(AuthorFormSet, self).get_queryset(**kwargs)
+...         return qs.order_by('name')
 
->>> formset = AuthorFormSet(qs)
+>>> formset = AuthorFormSet()
 >>> for form in formset.forms:
 ...     print form.as_p()
 <p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" value="Arthur Rimbaud" maxlength="100" /><input type="hidden" name="form-0-id" value="2" id="id_form-0-id" /></p>
@@ -73,7 +103,7 @@
 ...     'form-2-name': 'Paul Verlaine',
 ... }
 
->>> formset = AuthorFormSet(qs, data=data)
+>>> formset = AuthorFormSet(data)
 >>> formset.is_valid()
 True
 
@@ -90,10 +120,18 @@
 This probably shouldn't happen, but it will. If an add form was marked for
 deltetion, make sure we don't save that form.
 
->>> qs = Author.objects.order_by('name')
->>> AuthorFormSet = formset_for_model(Author, extra=1, deletable=True)
+>>> class AuthorFormSet(ModelFormSet):
+...     class Meta:
+...         model = Author
+...         num_extra = 1
+...         deletable = True
+...
+...     def get_queryset(self, **kwargs):
+...         qs = super(AuthorFormSet, self).get_queryset(**kwargs)
+...         return qs.order_by('name')
 
->>> formset = AuthorFormSet(qs)
+>>> formset = AuthorFormSet()
+
 >>> for form in formset.forms:
 ...     print form.as_p()
 <p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" value="Arthur Rimbaud" maxlength="100" /></p>
@@ -117,7 +155,7 @@
 ...     'form-3-DELETE': 'on',
 ... }
 
->>> formset = AuthorFormSet(qs, data=data)
+>>> formset = AuthorFormSet(data)
 >>> formset.is_valid()
 True
 
@@ -134,12 +172,18 @@
 We can also create a formset that is tied to a parent model. This is how the
 admin system's edit inline functionality works.
 
->>> from django.newforms.models import inline_formset
+>>> from django.newforms.models import inline_formset, InlineFormset
 
->>> AuthorBooksFormSet = inline_formset(Author, Book, deletable=False, extra=3)
+>>> class AuthorBooksFormSet(InlineFormset):
+...     class Meta:
+...         parent_model = Author
+...         model = Book
+...         num_extra = 3
+...         deletable = False
+
 >>> author = Author.objects.get(name='Charles Baudelaire')
 
->>> formset = AuthorBooksFormSet(author)
+>>> formset = AuthorBooksFormSet(instance=author)
 >>> for form in formset.forms:
 ...     print form.as_p()
 <p><label for="id_book_set-0-title">Title:</label> <input id="id_book_set-0-title" type="text" name="book_set-0-title" maxlength="100" /><input type="hidden" name="book_set-0-id" id="id_book_set-0-id" /></p>
@@ -153,7 +197,7 @@
 ...     'book_set-2-title': '',
 ... }
 
->>> formset = AuthorBooksFormSet(author, data=data)
+>>> formset = AuthorBooksFormSet(data, instance=author)
 >>> formset.is_valid()
 True
 
@@ -169,10 +213,17 @@
 one. This time though, an edit form will be available for every existing
 book.
 
->>> AuthorBooksFormSet = inline_formset(Author, Book, deletable=False, extra=2)
+>>> class AuthorBooksFormSet(InlineFormset):
+...     class Meta:
+...         parent_model = Author
+...         model = Book
+...         num_extra = 2
+...         deletable = False
+
+
 >>> author = Author.objects.get(name='Charles Baudelaire')
 
->>> formset = AuthorBooksFormSet(author)
+>>> formset = AuthorBooksFormSet(instance=author)
 >>> for form in formset.forms:
 ...     print form.as_p()
 <p><label for="id_book_set-0-title">Title:</label> <input id="id_book_set-0-title" type="text" name="book_set-0-title" value="Les Fleurs du Mal" maxlength="100" /><input type="hidden" name="book_set-0-id" value="1" id="id_book_set-0-id" /></p>
@@ -187,7 +238,7 @@
 ...     'book_set-2-title': '',
 ... }
 
->>> formset = AuthorBooksFormSet(author, data=data)
+>>> formset = AuthorBooksFormSet(data, instance=author)
 >>> formset.is_valid()
 True
 
Index: tests/regressiontests/forms/formsets.py
===================================================================
--- tests/regressiontests/forms/formsets.py	(revision 6956)
+++ tests/regressiontests/forms/formsets.py	(working copy)
@@ -2,19 +2,16 @@
 formset_tests = """
 # Basic FormSet creation and usage ############################################
 
-FormSet allows us to use multiple instance of the same form on 1 page. For now,
-the best way to create a FormSet is by using the formset_for_form function.
+FormSet allows us to use multiple instance of the same form on 1 page. Create
+the formset as you would a regular form by defining the fields declaratively.
 
->>> from django.newforms import Form, CharField, IntegerField, ValidationError
->>> from django.newforms.formsets import formset_for_form, BaseFormSet
+>>> from django.newforms import BooleanField
+>>> from django.newforms.formsets import formset_for_form, BaseFormSet, FormSet
 
->>> class Choice(Form):
+>>> class ChoiceFormSet(FormSet):
 ...     choice = CharField()
 ...     votes = IntegerField()
 
->>> ChoiceFormSet = formset_for_form(Choice)
-
-
 A FormSet constructor takes the same arguments as Form. Let's create a FormSet
 for adding data. By default, it displays 1 blank form. It can display more,
 but we'll look at how to do so later.
@@ -145,15 +142,31 @@
 >>> formset.errors
 [{'votes': [u'This field is required.'], 'choice': [u'This field is required.']}]
 
+# Subclassing a FormSet class #################################################
 
+We can subclass a FormSet to add addition fields to an already exisiting
+FormSet.
+
+>>> class SecondChoiceFormSet(ChoiceFormSet):
+...     is_public = BooleanField()
+
+>>> formset = SecondChoiceFormSet(auto_id=False, prefix="choices")
+>>> for form in formset.forms:
+...     print form.as_ul()
+<li>Choice: <input type="text" name="choices-0-choice" /></li>
+<li>Votes: <input type="text" name="choices-0-votes" /></li>
+<li>Is public: <input type="checkbox" name="choices-0-is_public" /></li>
+
 # Displaying more than 1 blank form ###########################################
 
-We can also display more than 1 empty form at a time. To do so, pass a
-num_extra argument to formset_for_form.
+We can also display more than 1 empty form at a time. To do so, create an inner
+Meta class with an attribute num_extra.
 
->>> ChoiceFormSet = formset_for_form(Choice, num_extra=3)
+>>> class NumExtraChoiceFormSet(ChoiceFormSet):
+...     class Meta:
+...         num_extra = 3
 
->>> formset = ChoiceFormSet(auto_id=False, prefix='choices')
+>>> formset = NumExtraChoiceFormSet(auto_id=False, prefix='choices')
 >>> for form in formset.forms:
 ...    print form.as_ul()
 <li>Choice: <input type="text" name="choices-0-choice" /></li>
@@ -177,7 +190,7 @@
 ...     'choices-2-votes': '',
 ... }
 
->>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+>>> formset = NumExtraChoiceFormSet(auto_id=False, prefix='choices')
 >>> formset.is_valid()
 True
 >>> formset.cleaned_data
@@ -196,7 +209,7 @@
 ...     'choices-2-votes': '',
 ... }
 
->>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+>>> formset = NumExtraChoiceFormSet(data, auto_id=False, prefix='choices')
 >>> formset.is_valid()
 True
 >>> formset.cleaned_data
@@ -215,7 +228,7 @@
 ...     'choices-2-votes': '',
 ... }
 
->>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+>>> formset = NumExtraChoiceFormSet(data, auto_id=False, prefix='choices')
 >>> formset.is_valid()
 False
 >>> formset.errors
@@ -226,7 +239,7 @@
 data.
 
 >>> initial = [{'choice': u'Calexico', 'votes': 100}]
->>> formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
+>>> formset = NumExtraChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
 >>> for form in formset.forms:
 ...    print form.as_ul()
 <li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
@@ -254,7 +267,7 @@
 ...     'choices-3-votes': '',
 ... }
 
->>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+>>> formset = NumExtraChoiceFormSet(data, auto_id=False, prefix='choices')
 >>> formset.is_valid()
 False
 >>> formset.errors
@@ -263,15 +276,17 @@
 
 # FormSets with deletion ######################################################
 
-We can easily add deletion ability to a FormSet with an agrument to
-formset_for_form. This will add a boolean field to each form instance. When
-that boolean field is True, the cleaned data will be in formset.deleted_data
+We can easily add deletion ability to a FormSet by setting deletable to True
+in the inner Meta class. This will add a boolean field to each form instance.
+When that boolean field is True, the cleaned data will be in formset.deleted_data
 rather than formset.cleaned_data
 
->>> ChoiceFormSet = formset_for_form(Choice, deletable=True)
+>>> class DeletableChoiceFormSet(ChoiceFormSet):
+...     class Meta:
+...         deletable = True
 
 >>> initial = [{'choice': u'Calexico', 'votes': 100}, {'choice': u'Fergie', 'votes': 900}]
->>> formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
+>>> formset = DeletableChoiceFormSet(data, auto_id=False, prefix='choices')
 >>> for form in formset.forms:
 ...    print form.as_ul()
 <li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
@@ -300,7 +315,7 @@
 ...     'choices-2-DELETE': '',
 ... }
 
->>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+>>> formset = DeletableChoiceFormSet(data, auto_id=False, prefix='choices')
 >>> formset.is_valid()
 True
 >>> formset.cleaned_data
@@ -310,18 +325,20 @@
 
 # FormSets with ordering ######################################################
 
-We can also add ordering ability to a FormSet with an agrument to
-formset_for_form. This will add a integer field to each form instance. When
+We can also add ordering ability to a FormSet by setting orderable to True in
+the inner Meta class. This will add a integer field to each form instance. When
 form validation succeeds, formset.cleaned_data will have the data in the correct
 order specified by the ordering fields. If a number is duplicated in the set
 of ordering fields, for instance form 0 and form 3 are both marked as 1, then
 the form index used as a secondary ordering criteria. In order to put
 something at the front of the list, you'd need to set it's order to 0.
 
->>> ChoiceFormSet = formset_for_form(Choice, orderable=True)
+>>> class OrderableChoiceFormSet(ChoiceFormSet):
+...     class Meta:
+...         orderable = True
 
 >>> initial = [{'choice': u'Calexico', 'votes': 100}, {'choice': u'Fergie', 'votes': 900}]
->>> formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
+>>> formset = OrderableChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
 >>> for form in formset.forms:
 ...    print form.as_ul()
 <li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
@@ -347,7 +364,7 @@
 ...     'choices-2-ORDER': '0',
 ... }
 
->>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+>>> formset = OrderableChoiceFormSet(data, auto_id=False, prefix='choices')
 >>> formset.is_valid()
 True
 >>> for cleaned_data in formset.cleaned_data:
@@ -359,15 +376,21 @@
 # FormSets with ordering + deletion ###########################################
 
 Let's try throwing ordering and deletion into the same form.
+TODO: Perhaps handle Meta class inheritance so you can subclass
+OrderableChoiceFormSet and DeletableChoiceFormSet?
 
->>> ChoiceFormSet = formset_for_form(Choice, orderable=True, deletable=True)
+>>> class MixedChoiceFormSet(ChoiceFormSet):
+...     class Meta:
+...         orderable = True
+...         deletable = True
 
+>>> formset = MixedChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
 >>> initial = [
 ...     {'choice': u'Calexico', 'votes': 100},
 ...     {'choice': u'Fergie', 'votes': 900},
 ...     {'choice': u'The Decemberists', 'votes': 500},
 ... ]
->>> formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
+>>> formset = MixedChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
 >>> for form in formset.forms:
 ...    print form.as_ul()
 <li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
@@ -409,7 +432,7 @@
 ...     'choices-3-DELETE': '',
 ... }
 
->>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+>>> formset = MixedChoiceFormSet(data, auto_id=False, prefix='choices')
 >>> formset.is_valid()
 True
 >>> for cleaned_data in formset.cleaned_data:
@@ -428,15 +451,13 @@
 Let's define a FormSet that takes a list of favorite drinks, but raises am
 error if there are any duplicates.
 
->>> class FavoriteDrinkForm(Form):
+>>> class FavoriteDrinkForm(FormSet):
 ...     name = CharField()
 ...
-
->>> class FavoriteDrinksFormSet(BaseFormSet):
-...     form_class = FavoriteDrinkForm
-...     num_extra = 2
-...     orderable = False
-...     deletable = False
+...     class Meta:
+...         num_extra = 2
+...         orderable = False
+...         deletable = False
 ...
 ...     def clean(self):
 ...         seen_drinks = []
Index: tests/regressiontests/inline_formsets/models.py
===================================================================
--- tests/regressiontests/inline_formsets/models.py	(revision 6956)
+++ tests/regressiontests/inline_formsets/models.py	(working copy)
@@ -21,7 +21,7 @@
 Child has two ForeignKeys to Parent, so if we don't specify which one to use
 for the inline formset, we should get an exception.
 
->>> ifs = inline_formset(Parent, Child)
+>>> inline_formset(Parent, Child)()
 Traceback (most recent call last):
     ...
 Exception: <class 'regressiontests.inline_formsets.models.Child'> has more than 1 ForeignKey to <class 'regressiontests.inline_formsets.models.Parent'>
@@ -29,14 +29,14 @@
 
 These two should both work without a problem.
 
->>> ifs = inline_formset(Parent, Child, fk_name='mother')
->>> ifs = inline_formset(Parent, Child, fk_name='father')
++>>> ifs = inline_formset(Parent, Child, fk_name='mother')()
++>>> ifs = inline_formset(Parent, Child, fk_name='father')()
 
 
 If we specify fk_name, but it isn't a ForeignKey from the child model to the
 parent model, we should get an exception.
 
->>> ifs = inline_formset(Parent, Child, fk_name='school')
+>>> inline_formset(Parent, Child, fk_name='school')()
 Traceback (most recent call last):
     ...
 Exception: fk_name 'school' is not a ForeignKey to <class 'regressiontests.inline_formsets.models.Parent'>
@@ -45,7 +45,7 @@
 If the field specified in fk_name is not a ForeignKey, we should get an
 exception.
 
->>> ifs = inline_formset(Parent, Child, fk_name='test')
+>>> inline_formset(Parent, Child, fk_name='test')()
 Traceback (most recent call last):
     ...
 Exception: <class 'regressiontests.inline_formsets.models.Child'> has no field named 'test'
