Index: django/test/client.py
===================================================================
--- django/test/client.py	(revision 9646)
+++ django/test/client.py	(working copy)
@@ -19,6 +19,7 @@
 from django.utils.encoding import smart_str
 from django.utils.http import urlencode
 from django.utils.itercompat import is_iterable
+from django.db import transaction, close_connection
 
 BOUNDARY = 'BoUnDaRyStRiNg'
 MULTIPART_CONTENT = 'multipart/form-data; boundary=%s' % BOUNDARY
@@ -69,7 +70,9 @@
                 response = middleware_method(request, response)
             response = self.apply_response_fixes(request, response)
         finally:
+            signals.request_finished.disconnect(close_connection)            
             signals.request_finished.send(sender=self.__class__)
+            signals.request_finished.connect(close_connection)
 
         return response
 
Index: django/test/testcases.py
===================================================================
--- django/test/testcases.py	(revision 9646)
+++ django/test/testcases.py	(working copy)
@@ -26,7 +26,32 @@
         value = [value]
     return value
 
+real_commit = transaction.commit
+real_rollback = transaction.rollback
+real_enter_transaction_management = transaction.enter_transaction_management
+real_leave_transaction_management = transaction.leave_transaction_management
+real_savepoint_commit = transaction.savepoint_commit
+real_savepoint_rollback = transaction.savepoint_rollback
 
+def nop(x=None):
+    return
+
+def disable_transaction_methods():
+    transaction.commit = nop
+    transaction.rollback = nop
+    transaction.savepoint_commit = nop
+    transaction.savepoint_rollback = nop
+    transaction.enter_transaction_management = nop
+    transaction.leave_transaction_management = nop        
+
+def restore_transaction_methods():
+    transaction.commit = real_commit
+    transaction.rollback = real_rollback
+    transaction.savepoint_commit = real_savepoint_commit
+    transaction.savepoint_rollback = real_savepoint_rollback
+    transaction.enter_transaction_management = real_enter_transaction_management
+    transaction.leave_transaction_management = real_leave_transaction_management
+
 class OutputChecker(doctest.OutputChecker):
     def check_output(self, want, got, optionflags):
         "The entry method for doctest output checking. Defers to a sequence of child checkers"
@@ -168,8 +193,19 @@
         # Rollback, in case of database errors. Otherwise they'd have
         # side effects on other tests.
         transaction.rollback_unless_managed()
+        
+    def run(self, test, compileflags=None, out=None, clear_globs=True):
+        """
+        Wraps the parent run() and encloses it in a transaction.
+        """
+        transaction.enter_transaction_management()
+        transaction.managed(True)
+        result = doctest.DocTestRunner.run(self, test, compileflags, out, clear_globs)
+        transaction.rollback()
+        transaction.leave_transaction_management()
+        return result
 
-class TestCase(unittest.TestCase):
+class TransactionTestCase(unittest.TestCase):
     def _pre_setup(self):
         """Performs any pre-test setup. This includes:
 
@@ -180,16 +216,22 @@
               ROOT_URLCONF with it.
             * Clearing the mail test outbox.
         """
+        self._fixture_setup()
+        self._urlconf_setup()
+        mail.outbox = []
+
+    def _fixture_setup(self):
         call_command('flush', verbosity=0, interactive=False)
         if hasattr(self, 'fixtures'):
             # We have to use this slightly awkward syntax due to the fact
             # that we're using *args and **kwargs together.
             call_command('loaddata', *self.fixtures, **{'verbosity': 0})
+
+    def _urlconf_setup(self):
         if hasattr(self, 'urls'):
             self._old_root_urlconf = settings.ROOT_URLCONF
             settings.ROOT_URLCONF = self.urls
             clear_url_caches()
-        mail.outbox = []
 
     def __call__(self, result=None):
         """
@@ -206,7 +248,7 @@
             import sys
             result.addError(self, sys.exc_info())
             return
-        super(TestCase, self).__call__(result)
+        super(TransactionTestCase, self).__call__(result)        
         try:
             self._post_teardown()
         except (KeyboardInterrupt, SystemExit):
@@ -221,6 +263,13 @@
 
             * Putting back the original ROOT_URLCONF if it was changed.
         """
+        self._fixture_teardown()
+        self._urlconf_teardown()
+
+    def _fixture_teardown(self):
+        pass
+
+    def _urlconf_teardown(self):        
         if hasattr(self, '_old_root_urlconf'):
             settings.ROOT_URLCONF = self._old_root_urlconf
             clear_url_caches()
@@ -354,3 +403,36 @@
         self.failIf(template_name in template_names,
             (u"Template '%s' was used unexpectedly in rendering the"
              u" response") % template_name)
+
+class TestCase(TransactionTestCase):
+    """
+    Does basically the same as TransactionTestCase, but surrounds every test
+    with a transaction, monkey-patches the real transaction management routines to 
+    do nothing, and rollsback the test transaction at the end of the test. You have 
+    to use TransactionTestCase, if you need transaction management inside a test.
+    """
+
+    def _fixture_setup(self):
+        if not settings.DATABASE_SUPPORTS_TRANSACTIONS:
+            return super(TestCase, self)._fixture_setup()
+        
+        transaction.enter_transaction_management()
+        transaction.managed(True)
+        disable_transaction_methods()
+
+        from django.contrib.sites.models import Site
+        Site.objects.clear_cache()
+
+        if hasattr(self, 'fixtures'):
+            call_command('loaddata', *self.fixtures, **{
+                                                        'verbosity': 0,
+                                                        'commit': False
+                                                        })
+
+    def _fixture_teardown(self):
+        if not settings.DATABASE_SUPPORTS_TRANSACTIONS:
+            return super(TestCase, self)._fixture_teardown()
+                
+        restore_transaction_methods()
+        transaction.rollback()
+        transaction.leave_transaction_management()
Index: django/test/__init__.py
===================================================================
--- django/test/__init__.py	(revision 9646)
+++ django/test/__init__.py	(working copy)
@@ -3,4 +3,4 @@
 """
 
 from django.test.client import Client
-from django.test.testcases import TestCase
+from django.test.testcases import TestCase, TransactionTestCase
Index: django/db/backends/creation.py
===================================================================
--- django/db/backends/creation.py	(revision 9646)
+++ django/db/backends/creation.py	(working copy)
@@ -311,7 +311,8 @@
 
         self.connection.close()
         settings.DATABASE_NAME = test_database_name
-
+        settings.DATABASE_SUPPORTS_TRANSACTIONS = self._rollback_works()
+        
         call_command('syncdb', verbosity=verbosity, interactive=False)
 
         if settings.CACHE_BACKEND.startswith('db://'):
@@ -362,7 +363,19 @@
                 sys.exit(1)
 
         return test_database_name
-
+    
+    def _rollback_works(self):
+        cursor = self.connection.cursor()
+        cursor.execute('CREATE TABLE ROLLBACK_TEST (X INT)')
+        self.connection._commit()
+        cursor.execute('INSERT INTO ROLLBACK_TEST (X) VALUES (8)')
+        self.connection._rollback()
+        cursor.execute('SELECT COUNT(X) FROM ROLLBACK_TEST')
+        count, = cursor.fetchone()
+        cursor.execute('DROP TABLE ROLLBACK_TEST')
+        self.connection._commit()
+        return count == 0
+        
     def destroy_test_db(self, old_database_name, verbosity=1):
         """
         Destroy a test database, prompting the user for confirmation if the
Index: tests/regressiontests/generic_inline_admin/tests.py
===================================================================
--- tests/regressiontests/generic_inline_admin/tests.py	(revision 9646)
+++ tests/regressiontests/generic_inline_admin/tests.py	(working copy)
@@ -21,8 +21,10 @@
         # relies on content type IDs, which will vary depending on what
         # other tests have been run), thus we do it here.
         e = Episode.objects.create(name='This Week in Django')
+        self.episode_pk = e.pk
         m = Media(content_object=e, url='http://example.com/podcast.mp3')
         m.save()
+        self.media_pk = m.pk
     
     def tearDown(self):
         self.client.logout()
@@ -39,7 +41,7 @@
         """
         A smoke test to ensure GET on the change_view works.
         """
-        response = self.client.get('/generic_inline_admin/admin/generic_inline_admin/episode/1/')
+        response = self.client.get('/generic_inline_admin/admin/generic_inline_admin/episode/%d/' % self.episode_pk)
         self.failUnlessEqual(response.status_code, 200)
     
     def testBasicAddPost(self):
@@ -64,10 +66,11 @@
             # inline data
             "generic_inline_admin-media-content_type-object_id-TOTAL_FORMS": u"2",
             "generic_inline_admin-media-content_type-object_id-INITIAL_FORMS": u"1",
-            "generic_inline_admin-media-content_type-object_id-0-id": u"1",
+            "generic_inline_admin-media-content_type-object_id-0-id": u"%d" % self.media_pk,
             "generic_inline_admin-media-content_type-object_id-0-url": u"http://example.com/podcast.mp3",
             "generic_inline_admin-media-content_type-object_id-1-id": u"",
             "generic_inline_admin-media-content_type-object_id-1-url": u"",
         }
-        response = self.client.post('/generic_inline_admin/admin/generic_inline_admin/episode/1/', post_data)
+        url = '/generic_inline_admin/admin/generic_inline_admin/episode/%d/' % self.episode_pk
+        response = self.client.post(url, post_data)
         self.failUnlessEqual(response.status_code, 302) # redirect somewhere
Index: tests/regressiontests/comment_tests/tests/moderation_view_tests.py
===================================================================
--- tests/regressiontests/comment_tests/tests/moderation_view_tests.py	(revision 9646)
+++ tests/regressiontests/comment_tests/tests/moderation_view_tests.py	(working copy)
@@ -8,39 +8,43 @@
 
     def testFlagGet(self):
         """GET the flag view: render a confirmation page."""
-        self.createSomeComments()
+        comments = self.createSomeComments()
+        pk = comments[0].pk
         self.client.login(username="normaluser", password="normaluser")
-        response = self.client.get("/flag/1/")
+        response = self.client.get("/flag/%d/" % pk)
         self.assertTemplateUsed(response, "comments/flag.html")
 
     def testFlagPost(self):
         """POST the flag view: actually flag the view (nice for XHR)"""
-        self.createSomeComments()
+        comments = self.createSomeComments()
+        pk = comments[0].pk
         self.client.login(username="normaluser", password="normaluser")
-        response = self.client.post("/flag/1/")
-        self.assertEqual(response["Location"], "http://testserver/flagged/?c=1")
-        c = Comment.objects.get(pk=1)
+        response = self.client.post("/flag/%d/" % pk)
+        self.assertEqual(response["Location"], "http://testserver/flagged/?c=%d" % pk)
+        c = Comment.objects.get(pk=pk)
         self.assertEqual(c.flags.filter(flag=CommentFlag.SUGGEST_REMOVAL).count(), 1)
         return c
 
     def testFlagPostTwice(self):
         """Users don't get to flag comments more than once."""
         c = self.testFlagPost()
-        self.client.post("/flag/1/")
-        self.client.post("/flag/1/")
+        self.client.post("/flag/%d/" % c.pk)
+        self.client.post("/flag/%d/" % c.pk)
         self.assertEqual(c.flags.filter(flag=CommentFlag.SUGGEST_REMOVAL).count(), 1)
 
     def testFlagAnon(self):
         """GET/POST the flag view while not logged in: redirect to log in."""
-        self.createSomeComments()
-        response = self.client.get("/flag/1/")
-        self.assertEqual(response["Location"], "http://testserver/accounts/login/?next=/flag/1/")
-        response = self.client.post("/flag/1/")
-        self.assertEqual(response["Location"], "http://testserver/accounts/login/?next=/flag/1/")
+        comments = self.createSomeComments()
+        pk = comments[0].pk        
+        response = self.client.get("/flag/%d/" % pk)
+        self.assertEqual(response["Location"], "http://testserver/accounts/login/?next=/flag/%d/" % pk)
+        response = self.client.post("/flag/%d/" % pk)
+        self.assertEqual(response["Location"], "http://testserver/accounts/login/?next=/flag/%d/" % pk)
 
     def testFlaggedView(self):
-        self.createSomeComments()
-        response = self.client.get("/flagged/", data={"c":1})
+        comments = self.createSomeComments()
+        pk = comments[0].pk        
+        response = self.client.get("/flagged/", data={"c":pk})
         self.assertTemplateUsed(response, "comments/flagged.html")
 
     def testFlagSignals(self):
@@ -70,23 +74,25 @@
 
     def testDeletePermissions(self):
         """The delete view should only be accessible to 'moderators'"""
-        self.createSomeComments()
+        comments = self.createSomeComments()
+        pk = comments[0].pk        
         self.client.login(username="normaluser", password="normaluser")
-        response = self.client.get("/delete/1/")
-        self.assertEqual(response["Location"], "http://testserver/accounts/login/?next=/delete/1/")
+        response = self.client.get("/delete/%d/" % pk)
+        self.assertEqual(response["Location"], "http://testserver/accounts/login/?next=/delete/%d/" % pk)
 
         makeModerator("normaluser")
-        response = self.client.get("/delete/1/")
+        response = self.client.get("/delete/%d/" % pk)
         self.assertEqual(response.status_code, 200)
 
     def testDeletePost(self):
         """POSTing the delete view should mark the comment as removed"""
-        self.createSomeComments()
+        comments = self.createSomeComments()
+        pk = comments[0].pk
         makeModerator("normaluser")
         self.client.login(username="normaluser", password="normaluser")
-        response = self.client.post("/delete/1/")
-        self.assertEqual(response["Location"], "http://testserver/deleted/?c=1")
-        c = Comment.objects.get(pk=1)
+        response = self.client.post("/delete/%d/" % pk)
+        self.assertEqual(response["Location"], "http://testserver/deleted/?c=%d" % pk)
+        c = Comment.objects.get(pk=pk)
         self.failUnless(c.is_removed)
         self.assertEqual(c.flags.filter(flag=CommentFlag.MODERATOR_DELETION, user__username="normaluser").count(), 1)
 
@@ -103,21 +109,23 @@
         self.assertEqual(received_signals, [signals.comment_was_flagged])
 
     def testDeletedView(self):
-        self.createSomeComments()
-        response = self.client.get("/deleted/", data={"c":1})
+        comments = self.createSomeComments()
+        pk = comments[0].pk        
+        response = self.client.get("/deleted/", data={"c":pk})
         self.assertTemplateUsed(response, "comments/deleted.html")
 
 class ApproveViewTests(CommentTestCase):
 
     def testApprovePermissions(self):
         """The delete view should only be accessible to 'moderators'"""
-        self.createSomeComments()
+        comments = self.createSomeComments()
+        pk = comments[0].pk        
         self.client.login(username="normaluser", password="normaluser")
-        response = self.client.get("/approve/1/")
-        self.assertEqual(response["Location"], "http://testserver/accounts/login/?next=/approve/1/")
+        response = self.client.get("/approve/%d/" % pk)
+        self.assertEqual(response["Location"], "http://testserver/accounts/login/?next=/approve/%d/" % pk)
 
         makeModerator("normaluser")
-        response = self.client.get("/approve/1/")
+        response = self.client.get("/approve/%d/" % pk)
         self.assertEqual(response.status_code, 200)
 
     def testApprovePost(self):
@@ -127,9 +135,9 @@
 
         makeModerator("normaluser")
         self.client.login(username="normaluser", password="normaluser")
-        response = self.client.post("/approve/1/")
-        self.assertEqual(response["Location"], "http://testserver/approved/?c=1")
-        c = Comment.objects.get(pk=1)
+        response = self.client.post("/approve/%d/" % c1.pk)
+        self.assertEqual(response["Location"], "http://testserver/approved/?c=%d" % c1.pk)
+        c = Comment.objects.get(pk=c1.pk)
         self.failUnless(c.is_public)
         self.assertEqual(c.flags.filter(flag=CommentFlag.MODERATOR_APPROVAL, user__username="normaluser").count(), 1)
 
@@ -146,8 +154,9 @@
         self.assertEqual(received_signals, [signals.comment_was_flagged])
 
     def testApprovedView(self):
-        self.createSomeComments()
-        response = self.client.get("/approved/", data={"c":1})
+        comments = self.createSomeComments()
+        pk = comments[0].pk        
+        response = self.client.get("/approved/", data={"c":pk})
         self.assertTemplateUsed(response, "comments/approved.html")
 
 
Index: tests/regressiontests/comment_tests/tests/comment_view_tests.py
===================================================================
--- tests/regressiontests/comment_tests/tests/comment_view_tests.py	(revision 9646)
+++ tests/regressiontests/comment_tests/tests/comment_view_tests.py	(working copy)
@@ -1,3 +1,4 @@
+import re
 from django.conf import settings
 from django.contrib.auth.models import User
 from django.contrib.comments import signals
@@ -5,6 +6,8 @@
 from regressiontests.comment_tests.models import Article
 from regressiontests.comment_tests.tests import CommentTestCase
 
+post_redirect_re = re.compile(r'^http://testserver/posted/\?c=(?P<pk>\d+$)')
+
 class CommentViewTests(CommentTestCase):
 
     def testPostCommentHTTPMethods(self):
@@ -181,18 +184,26 @@
         a = Article.objects.get(pk=1)
         data = self.getValidData(a)
         response = self.client.post("/post/", data)
-        self.assertEqual(response["Location"], "http://testserver/posted/?c=1")
-
+        location = response["Location"]
+        match = post_redirect_re.match(location)
+        self.failUnless(match != None, "Unexpected redirect location: %s" % location)
+        
         data["next"] = "/somewhere/else/"
         data["comment"] = "This is another comment"
         response = self.client.post("/post/", data)
-        self.assertEqual(response["Location"], "http://testserver/somewhere/else/?c=2")
+        location = response["Location"]        
+        match = re.search(r"^http://testserver/somewhere/else/\?c=\d+$", location)
+        self.failUnless(match != None, "Unexpected redirect location: %s" % location)
 
     def testCommentDoneView(self):
         a = Article.objects.get(pk=1)
         data = self.getValidData(a)
         response = self.client.post("/post/", data)
-        response = self.client.get("/posted/", {'c':1})
+        location = response["Location"]        
+        match = post_redirect_re.match(location)
+        self.failUnless(match != None, "Unexpected redirect location: %s" % location)
+        pk = int(match.group('pk'))
+        response = self.client.get(location)
         self.assertTemplateUsed(response, "comments/posted.html")
-        self.assertEqual(response.context[0]["comment"], Comment.objects.get(pk=1))
+        self.assertEqual(response.context[0]["comment"], Comment.objects.get(pk=pk))
 
