import sys from django.db import connection, transaction, IntegrityError, DEFAULT_DB_ALIAS from django.conf import settings from django.test import TransactionTestCase, skipUnlessDBFeature from models import Reporter if sys.version_info >= (2, 5): from tests_25 import TransactionContextManagerTests class TransactionTests(TransactionTestCase): def create_a_reporter_then_fail(self, first, last): a = Reporter(first_name=first, last_name=last) a.save() raise Exception("I meant to do that") def remove_a_reporter(self, first_name): r = Reporter.objects.get(first_name="Alice") r.delete() def manually_managed(self): r = Reporter(first_name="Dirk", last_name="Gently") r.save() transaction.commit() def manually_managed_mistake(self): r = Reporter(first_name="Edward", last_name="Woodward") r.save() # Oops, I forgot to commit/rollback! @skipUnlessDBFeature('supports_transactions') def test_autocommit(self): """ The default behavior is to autocommit after each save() action. """ self.assertRaises(Exception, self.create_a_reporter_then_fail, "Alice", "Smith" ) # The object created before the exception still exists self.assertEqual(Reporter.objects.count(), 1) @skipUnlessDBFeature('supports_transactions') def test_autocommit_decorator(self): """ The autocommit decorator works exactly the same as the default behavior. """ autocomitted_create_then_fail = transaction.autocommit( self.create_a_reporter_then_fail ) self.assertRaises(Exception, autocomitted_create_then_fail, "Alice", "Smith" ) # Again, the object created before the exception still exists self.assertEqual(Reporter.objects.count(), 1) @skipUnlessDBFeature('supports_transactions') def test_autocommit_decorator_with_using(self): """ The autocommit decorator also works with a using argument. """ autocomitted_create_then_fail = transaction.autocommit(using='default')( self.create_a_reporter_then_fail ) self.assertRaises(Exception, autocomitted_create_then_fail, "Alice", "Smith" ) # Again, the object created before the exception still exists self.assertEqual(Reporter.objects.count(), 1) @skipUnlessDBFeature('supports_transactions') def test_commit_on_success(self): """ With the commit_on_success decorator, the transaction is only committed if the function doesn't throw an exception. """ committed_on_success = transaction.commit_on_success( self.create_a_reporter_then_fail) self.assertRaises(Exception, committed_on_success, "Dirk", "Gently") # This time the object never got saved self.assertEqual(Reporter.objects.count(), 0) @skipUnlessDBFeature('supports_transactions') def test_commit_on_success_with_using(self): """ The commit_on_success decorator also works with a using argument. """ using_committed_on_success = transaction.commit_on_success(using='default')( self.create_a_reporter_then_fail ) self.assertRaises(Exception, using_committed_on_success, "Dirk", "Gently" ) # This time the object never got saved self.assertEqual(Reporter.objects.count(), 0) @skipUnlessDBFeature('supports_transactions') def test_commit_on_success_succeed(self): """ If there aren't any exceptions, the data will get saved. """ Reporter.objects.create(first_name="Alice", last_name="Smith") remove_comitted_on_success = transaction.commit_on_success( self.remove_a_reporter ) remove_comitted_on_success("Alice") self.assertEqual(list(Reporter.objects.all()), []) @skipUnlessDBFeature('supports_transactions') def test_commit_on_success_exit(self): @transaction.autocommit() def gen_reporter(): @transaction.commit_on_success def create_reporter(): Reporter.objects.create(first_name="Bobby", last_name="Tables") create_reporter() # Much more formal r = Reporter.objects.get() r.first_name = "Robert" r.save() gen_reporter() r = Reporter.objects.get() self.assertEqual(r.first_name, "Robert") @skipUnlessDBFeature('supports_transactions') def test_manually_managed(self): """ You can manually manage transactions if you really want to, but you have to remember to commit/rollback. """ manually_managed = transaction.commit_manually(self.manually_managed) manually_managed() self.assertEqual(Reporter.objects.count(), 1) @skipUnlessDBFeature('supports_transactions') def test_manually_managed_mistake(self): """ If you forget, you'll get bad errors. """ manually_managed_mistake = transaction.commit_manually( self.manually_managed_mistake ) self.assertRaises(transaction.TransactionManagementError, manually_managed_mistake) @skipUnlessDBFeature('supports_transactions') def test_manually_managed_with_using(self): """ The commit_manually function also works with a using argument. """ using_manually_managed_mistake = transaction.commit_manually(using='default')( self.manually_managed_mistake ) self.assertRaises(transaction.TransactionManagementError, using_manually_managed_mistake ) class TransactionRollbackTests(TransactionTestCase): def execute_bad_sql(self): cursor = connection.cursor() cursor.execute("INSERT INTO transactions_reporter (first_name, last_name) VALUES ('Douglas', 'Adams');") transaction.set_dirty() @skipUnlessDBFeature('requires_rollback_on_dirty_transaction') def test_bad_sql(self): """ Regression for #11900: If a function wrapped by commit_on_success writes a transaction that can't be committed, that transaction should be rolled back. The bug is only visible using the psycopg2 backend, though the fix is generally a good idea. """ execute_bad_sql = transaction.commit_on_success(self.execute_bad_sql) self.assertRaises(IntegrityError, execute_bad_sql) transaction.rollback()