diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index 3a0ad74..a1bc2f2 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -15,6 +15,7 @@ from django.core.urlresolvers import reverse
 from django.db import models, transaction, router
 from django.db.models.related import RelatedObject
 from django.db.models.fields import BLANK_CHOICE_DASH, FieldDoesNotExist
+from django.db.models.fields.related import ManyRelatedObjectsDescriptor
 from django.db.models.sql.constants import LOOKUP_SEP, QUERY_TERMS
 from django.http import Http404, HttpResponse, HttpResponseRedirect
 from django.shortcuts import get_object_or_404
@@ -455,6 +456,15 @@ class ModelAdmin(BaseModelAdmin):
             "exclude": exclude,
             "formfield_callback": partial(self.formfield_for_dbfield, request=request),
         }
+        if fields:
+            revM2M_widgets = {}
+            other_fields = set(fields) - set(self.model._meta.fields + self.model._meta.many_to_many)
+            for item in other_fields:
+                if hasattr(self.model, item) and isinstance(getattr(self.model, item), ManyRelatedObjectsDescriptor):
+                    if item in (list(self.filter_vertical) + list(self.filter_horizontal)):
+                        revM2M_widgets[item] = widgets.FilteredSelectMultiple(item, (item in self.filter_vertical))
+            if widgets:
+                defaults['widgets'] = revM2M_widgets
         defaults.update(kwargs)
         return modelform_factory(self.model, **defaults)
 
diff --git a/django/contrib/admin/validation.py b/django/contrib/admin/validation.py
index 733f89d..0787549 100644
--- a/django/contrib/admin/validation.py
+++ b/django/contrib/admin/validation.py
@@ -1,6 +1,7 @@
 from django.core.exceptions import ImproperlyConfigured
 from django.db import models
 from django.db.models.fields import FieldDoesNotExist
+from django.db.models.fields.related import ManyRelatedObjectsDescriptor
 from django.forms.models import (BaseModelForm, BaseModelFormSet, fields_for_model,
     _get_foreign_key)
 from django.contrib.admin import ListFilter, FieldListFilter
@@ -246,6 +247,9 @@ def validate_fields_spec(cls, model, opts, flds, label):
                 # readonly_fields will handle the validation of such
                 # things.
                 continue
+            if hasattr(model, field) and isinstance(getattr(model, field), ManyRelatedObjectsDescriptor):
+                # allow reverse M2M descriptors
+                continue
             check_formfield(cls, model, opts, label, field)
             try:
                 f = opts.get_field(field)
@@ -331,8 +335,14 @@ def validate_base(cls, model):
     if hasattr(cls, 'filter_horizontal'):
         check_isseq(cls, 'filter_horizontal', cls.filter_horizontal)
         for idx, field in enumerate(cls.filter_horizontal):
-            f = get_field(cls, model, opts, 'filter_horizontal', field)
-            if not isinstance(f, models.ManyToManyField):
+            valid = False
+            if hasattr(model, field) and isinstance(getattr(model, field), ManyRelatedObjectsDescriptor):
+                valid = True
+            if not valid:
+                f = get_field(cls, model, opts, 'filter_horizontal', field)
+                if isinstance(f, models.ManyToManyField):
+                    valid = True
+            if not valid:
                 raise ImproperlyConfigured("'%s.filter_horizontal[%d]' must be "
                     "a ManyToManyField." % (cls.__name__, idx))
 
diff --git a/django/contrib/auth/admin.py b/django/contrib/auth/admin.py
index 3868794..a58069c 100644
--- a/django/contrib/auth/admin.py
+++ b/django/contrib/auth/admin.py
@@ -19,7 +19,8 @@ csrf_protect_m = method_decorator(csrf_protect)
 class GroupAdmin(admin.ModelAdmin):
     search_fields = ('name',)
     ordering = ('name',)
-    filter_horizontal = ('permissions',)
+    fields = ('name', 'permissions', 'user_set')
+    filter_horizontal = ('permissions', 'user_set')
 
     def formfield_for_manytomany(self, db_field, request=None, **kwargs):
         if db_field.name == 'permissions':
diff --git a/django/forms/models.py b/django/forms/models.py
index b65f067..d6cc94a 100644
--- a/django/forms/models.py
+++ b/django/forms/models.py
@@ -18,7 +18,6 @@ from django.utils.datastructures import SortedDict
 from django.utils.text import get_text_list, capfirst
 from django.utils.translation import ugettext_lazy as _, ugettext
 
-
 __all__ = (
     'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model',
     'save_instance', 'ModelChoiceField', 'ModelMultipleChoiceField',
@@ -81,6 +80,11 @@ def save_instance(form, instance, fields=None, fail_message='saved',
                 continue
             if f.name in cleaned_data:
                 f.save_form_data(instance, cleaned_data[f.name])
+        if fields:
+            other_fields = set(fields) - set(opts.fields + opts.many_to_many)
+            for item in other_fields:
+                setattr(instance, item, cleaned_data[item])
+
     if commit:
         # If we are committing, save the instance and the m2m data immediately.
         instance.save()
@@ -107,7 +111,7 @@ def model_to_dict(instance, fields=None, exclude=None):
     the ``fields`` argument.
     """
     # avoid a circular import
-    from django.db.models.fields.related import ManyToManyField
+    from django.db.models.fields.related import ManyToManyField, ManyRelatedObjectsDescriptor
     opts = instance._meta
     data = {}
     for f in opts.fields + opts.many_to_many:
@@ -128,6 +132,16 @@ def model_to_dict(instance, fields=None, exclude=None):
                 data[f.name] = [obj.pk for obj in f.value_from_object(instance)]
         else:
             data[f.name] = f.value_from_object(instance)
+    if fields:
+        other_fields = set(fields) - set(data.keys())
+        for item in other_fields:
+            if hasattr(instance.__class__, item) and \
+                    isinstance(getattr(instance.__class__, item), ManyRelatedObjectsDescriptor):
+                if instance.pk is None:
+                    data[item] = []
+                else:
+                    # MultipleChoiceWidget needs a list of pks, not object instances.
+                    data[item] = [obj.pk for obj in getattr(instance, item).all()]
     return data
 
 def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_callback=None):
@@ -140,7 +154,10 @@ def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_c
     ``exclude`` is an optional list of field names. If provided, the named
     fields will be excluded from the returned fields, even if they are listed
     in the ``fields`` argument.
-    """
+    """   
+    from django.db.models.fields.related import ManyRelatedObjectsDescriptor
+    from django.forms import ModelMultipleChoiceField
+
     field_list = []
     ignored = []
     opts = model._meta
@@ -167,6 +184,20 @@ def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_c
             field_list.append((f.name, formfield))
         else:
             ignored.append(f.name)
+
+    if fields:
+        missing_fields = set(fields) - set([k[0] for k in field_list])
+        for item in missing_fields:
+            if hasattr(model, item) and isinstance(getattr(model, item), ManyRelatedObjectsDescriptor):
+                kwargs = {
+                    'required': False,
+                    'queryset': getattr(model, item).related.model._default_manager.all()
+                }
+                if widgets and item in widgets:
+                    kwargs['widget'] = widgets[item]
+                formfield = ModelMultipleChoiceField(**kwargs)
+                field_list.append((item, formfield))
+
     field_dict = SortedDict(field_list)
     if fields:
         field_dict = SortedDict(
diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py
index f4ec63f..42aebdb 100644
--- a/tests/regressiontests/admin_views/tests.py
+++ b/tests/regressiontests/admin_views/tests.py
@@ -2986,7 +2986,7 @@ class GroupAdminTest(TestCase):
     def test_group_permission_performance(self):
         g = Group.objects.create(name="test_group")
 
-        with self.assertNumQueries(6):  # instead of 259!
+        with self.assertNumQueries(8):  # instead of 259!
             response = self.client.get('/test_admin/admin/auth/group/%s/' % g.pk)
             self.assertEqual(response.status_code, 200)
 
