diff -r 6f697e5f2375 tests/regressiontests/admin_views/models.py
--- a/tests/regressiontests/admin_views/models.py	Sat Mar 20 10:01:32 2010 -0400
+++ b/tests/regressiontests/admin_views/models.py	Sat Mar 20 15:44:59 2010 -0400
@@ -565,6 +565,52 @@
     def __unicode__(self):
         return self.name
 
+
+class Organization(models.Model):
+    """
+    Test the save method
+    """
+    
+    name = models.CharField(max_length=255)
+    dilberts = models.ManyToManyField(
+        'Dilbert', through='Role', blank=True
+    )
+
+    def save(self, force_insert=False, force_update=False):
+        if self.pk == 1: # For test_model_save_method
+            self.dilberts.clear()
+        super(Organization, self).save(force_insert, force_update)
+
+
+class Role(models.Model):
+    name = models.CharField(max_length=255)
+    organization = models.ForeignKey('Organization')
+    dilbert = models.ForeignKey('Dilbert')
+        
+
+class Dilbert(models.Model):
+    name = models.CharField(max_length=255)
+
+
+class RoleInline(admin.TabularInline):
+    model = Role
+
+
+class OrganizationAdmin(admin.ModelAdmin):
+    """
+    Test the save_model method.
+    """
+    inlines = (RoleInline,)
+
+    def save_model(self, request, obj, form, change):
+        obj.save()
+        if obj.pk == 2: # For test_admin_save_model_method
+            role = Role.objects.get(pk=3)
+            role.delete()
+        if obj.pk == 4: # For test_change_data
+            role = Role.objects.get(pk=8)
+            role.name = 'Water drinker'
+
 admin.site.register(Article, ArticleAdmin)
 admin.site.register(CustomArticle, CustomArticleAdmin)
 admin.site.register(Section, save_as=True, inlines=[ArticleInline])
@@ -588,6 +634,7 @@
 admin.site.register(Recommender)
 admin.site.register(Collector, CollectorAdmin)
 admin.site.register(Category, CategoryAdmin)
+admin.site.register(Organization, OrganizationAdmin)
 admin.site.register(Post, PostAdmin)
 admin.site.register(Gadget, GadgetAdmin)
 admin.site.register(Villain)
diff -r 6f697e5f2375 tests/regressiontests/admin_views/tests.py
--- a/tests/regressiontests/admin_views/tests.py	Sat Mar 20 10:01:32 2010 -0400
+++ b/tests/regressiontests/admin_views/tests.py	Sat Mar 20 15:44:59 2010 -0400
@@ -23,7 +23,7 @@
     ExternalSubscriber, FooAccount, Gallery, ModelWithStringPrimaryKey, \
     Person, Persona, Picture, Podcast, Section, Subscriber, Vodcast, \
     Language, Collector, Widget, Grommet, DooHickey, FancyDoodad, Whatsit, \
-    Category, Post, Plot, FunkyTag
+    Category, Organization, Role, Post, Plot, FunkyTag
 
 
 class AdminViewBasicTest(TestCase):
@@ -1936,6 +1936,132 @@
         self.failUnlessEqual(get_max_age(response), None)
 
 
+class SaveModelTest(TestCase):
+    fixtures = ['admin-views-save-model.xml']
+
+    def setUp(self):
+        self.client.login(username='super', password='secret')
+        self.post_data = {
+            "name": u"Django foundation",
+            # inline data
+            "role_set-TOTAL_FORMS": "6",
+            "role_set-INITIAL_FORMS": "2",
+            "role_set-MAX_NUM_FORMS": "0",
+            "role_set-0-id": "1",
+            "role_set-0-name": u"Programmer",
+            "role_set-0-organization": "1",
+            "role_set-0-dilbert": "1",
+            "role_set-1-id": "2",
+            "role_set-1-name": u"Coffee drinker",
+            "role_set-1-organization": "1",
+            "role_set-1-dilbert": "1",
+            "role_set-2-id": "",
+            "role_set-2-name": "",
+            "role_set-2-organization": "",
+            "role_set-2-dilbert": "",
+            "role_set-3-id": "",
+            "role_set-3-name": "",
+            "role_set-3-organization": "",
+            "role_set-3-dilbert": "",
+            "role_set-4-id": "",
+            "role_set-4-name": "",
+            "role_set-4-organization": "",
+            "role_set-4-dilbert": "",
+            "role_set-5-id": "",
+            "role_set-5-name": "",
+            "role_set-5-organization": "",
+            "role_set-5-dilbert": "",
+        }
+
+    def tearDown(self):
+        self.client.logout()
+
+    def test_model_save_method(self):
+        """
+        Test removing of related objects in the save method on the model.
+        """
+        org = Organization.objects.get(pk=1)
+        self.assertEqual(org.dilberts.count(), 2)
+        
+        response = self.client.post(
+            '/test_admin/admin/admin_views/organization/1/', self.post_data
+        )
+
+        self.assertEqual(org.dilberts.count(), 0)
+        self.assertEqual(response.status_code, 302) # redirect somewhere
+
+    def test_admin_save_model_method(self):
+        """
+        Test removing of related objects in the model_save method
+        in the ModelAdmin.
+        """
+        self.post_data['role_set-0-id'] = '3'
+        self.post_data['role_set-0-organization'] = '2'
+        self.post_data['role_set-1-id'] = '4'
+        self.post_data['role_set-1-organization'] = '2'
+        self.post_data['role_set-1-DELETE'] = 'on'
+
+        org = Organization.objects.get(pk=2)
+        self.assertEqual(org.dilberts.count(), 2)
+
+        response = self.client.post(
+            '/test_admin/admin/admin_views/organization/2/', self.post_data
+        )
+
+        self.assertEqual(org.dilberts.count(), 0)
+        self.assertEqual(response.status_code, 302) # redirect somewhere
+
+    def test_invalid_related_field_choice(self):
+        """
+        A test to ensure that the model is not changed when an
+        invalid role_set-x-id is posted.
+        """
+        self.post_data['role_set-0-id'] = '100' #Should not exist
+        self.post_data['role_set-0-organization'] = '3'
+        self.post_data['role_set-1-id'] = '6'
+        self.post_data['role_set-1-organization'] = '3'
+        self.post_data['role_set-1-DELETE'] = 'on'
+
+        org = Organization.objects.get(pk=3)
+        self.assertEqual(org.dilberts.count(), 2)
+
+        response = self.client.post(
+            '/test_admin/admin/admin_views/organization/3/', self.post_data
+        )
+
+        self.assertEqual(org.dilberts.count(), 2)
+        self.assertEqual(response.status_code, 200) 
+
+    def test_change_data(self):
+        """
+        A test to ensure that the model is not changed when an
+        invalid role_set-x-id is posted.
+        """
+        self.post_data['role_set-0-id'] = '7' #Should not exist
+        self.post_data['role_set-0-organization'] = '4'
+        self.post_data['role_set-1-id'] = '8'
+        self.post_data['role_set-1-name'] = 'Tea drinker'
+        self.post_data['role_set-1-organization'] = '4'
+
+
+        org = Organization.objects.get(pk=4)
+        self.assertEqual(org.dilberts.count(), 2)
+
+        response = self.client.post(
+            '/test_admin/admin/admin_views/organization/4/', self.post_data
+        )
+
+        role = Role.objects.get(pk=8)
+        # The form is the master
+        self.assertEqual(role.name, 'Tea drinker')
+
+        # save_model or save is the master
+        #self.assertEqual(role.name, 'Water drinker') 
+
+        self.assertEqual(org.dilberts.count(), 2)
+        self.assertEqual(response.status_code, 302) # redirect somewhere
+
+
 class ReadonlyTest(TestCase):
     fixtures = ['admin-views-users.xml']
 
