Code

Ticket #9964: 9964-r14913-bugfix.patch

File 9964-r14913-bugfix.patch, 11.0 KB (added by shai, 4 years ago)

patch against r14913: bugfix approach

  • django/db/backends/util.py

     
    5252    def __iter__(self): 
    5353        return iter(self.cursor) 
    5454 
     55class CursorUseNotifyWrapper(object): 
     56    def __init__(self, cursor, on_use): 
     57        self.cursor = cursor 
     58        self.on_use = on_use 
     59    def __getattr__(self, attr): 
     60        self.on_use() 
     61        return getattr(self.cursor, attr) 
     62    def __iter__(self): 
     63        return iter(self.cursor) 
     64 
    5565############################################### 
    5666# Converters from database (string) to Python # 
    5767############################################### 
  • django/db/backends/__init__.py

     
    2222        self.settings_dict = settings_dict 
    2323        self.alias = alias 
    2424        self.use_debug_cursor = None 
     25        self.on_used = None 
    2526 
    2627    def __eq__(self, other): 
    2728        return self.alias == other.alias 
     
    7576    def cursor(self): 
    7677        from django.conf import settings 
    7778        cursor = self._cursor() 
     79        # Used by transaction mechanism to be notified that db was used 
     80        if self.on_used: 
     81            cursor = util.CursorUseNotifyWrapper(cursor, self.on_used) 
    7882        if (self.use_debug_cursor or 
    7983            (self.use_debug_cursor is None and settings.DEBUG)): 
    8084            return self.make_debug_cursor(cursor) 
  • django/db/transaction.py

     
    6767    if thread_ident not in dirty or using not in dirty[thread_ident]: 
    6868        dirty.setdefault(thread_ident, {}) 
    6969        dirty[thread_ident][using] = False 
     70    def set_dirty_if_managed(): 
     71        if is_managed(using): set_dirty(using) 
     72    connection.on_used = set_dirty_if_managed 
    7073    connection._enter_transaction_management(managed) 
    7174 
    7275def leave_transaction_management(using=None): 
  • tests/regressiontests/transaction_regress/tests.py

     
     1from django.test import TransactionTestCase 
     2from django.db import connection, transaction 
     3from django.db.transaction import \ 
     4    commit_on_success, commit_manually, \ 
     5    TransactionManagementError 
     6from models import Mod 
     7 
     8class Test9964(TransactionTestCase): 
     9 
     10    def test_raw_committed_on_success(self): 
     11         
     12        @commit_on_success 
     13        def raw_sql(): 
     14            cursor = connection.cursor() 
     15            cursor.execute("INSERT into transaction_regress_mod (id,fld) values (17,18)") 
     16             
     17        raw_sql() 
     18        transaction.rollback() 
     19        try: 
     20            obj = Mod.objects.get(pk=17) 
     21            self.assertEqual(obj.fld, 18) 
     22        except Mod.DoesNotExist: 
     23            self.fail("transaction with raw sql not committed") 
     24 
     25    def test_commit_manually_enforced(self): 
     26        @commit_manually 
     27        def non_comitter(): 
     28            _ = Mod.objects.count() 
     29             
     30        self.assertRaises(TransactionManagementError, non_comitter) 
     31 
     32    def test_commit_manually_commit_ok(self): 
     33        @commit_manually 
     34        def committer(): 
     35            _ = Mod.objects.count() 
     36            transaction.commit() 
     37         
     38        try: 
     39            committer() 
     40        except TransactionManagementError: 
     41            self.fail("Commit did not clear the transaction state") 
     42 
     43    def test_commit_manually_rollback_ok(self): 
     44        @commit_manually 
     45        def roller_back(): 
     46            _ = Mod.objects.count() 
     47            transaction.rollback() 
     48         
     49        try: 
     50            roller_back() 
     51        except TransactionManagementError: 
     52            self.fail("Rollback did not clear the transaction state") 
     53 
     54    def test_commit_manually_enforced_after_commit(self): 
     55        @commit_manually 
     56        def fake_committer(): 
     57            _ = Mod.objects.count() 
     58            transaction.commit() 
     59            _ = Mod.objects.count()             
     60             
     61        self.assertRaises(TransactionManagementError, fake_committer) 
     62 
     63    def test_reuse_cursor_reference(self): 
     64        @commit_on_success 
     65        def reuse_cursor_ref(): 
     66            cursor = connection.cursor() 
     67            cursor.execute("INSERT into transaction_regress_mod (id,fld) values (1,2)") 
     68            transaction.rollback() 
     69            cursor.execute("INSERT into transaction_regress_mod (id,fld) values (1,2)") 
     70             
     71        reuse_cursor_ref() 
     72        transaction.rollback() 
     73        try: 
     74            obj = Mod.objects.get(pk=1) 
     75            self.assertEquals(obj.fld, 2) 
     76        except Mod.DoesNotExist: 
     77            self.fail("After ending a transaction, cursor use no longer sets dirty") 
     78 
     79    def test_6669(self): 
     80 
     81        from django.contrib.auth.models import User 
     82 
     83        @transaction.commit_on_success 
     84        def create_system_user(): 
     85            user = User.objects.create_user(username='system', password='iamr00t', email='root@SITENAME.com') 
     86            Mod.objects.create(fld=user.id) 
     87 
     88        try: 
     89            create_system_user() 
     90        except: 
     91            pass 
     92 
     93        try: 
     94            create_system_user() 
     95        except: 
     96            pass 
     97 
     98        User.objects.all()[0] 
  • tests/regressiontests/transaction_regress/models.py

     
     1from django.db import models 
     2 
     3class Mod(models.Model): 
     4    fld = models.IntegerField() 
  • tests/regressiontests/delete_regress/tests.py

     
    6161        Book.objects.filter(pagecount__lt=250).delete() 
    6262        transaction.commit() 
    6363        self.assertEqual(1, Book.objects.count()) 
     64        transaction.commit() 
    6465 
    6566class DeleteCascadeTests(TestCase): 
    6667    def test_generic_relation_cascade(self): 
  • tests/regressiontests/fixtures_regress/tests.py

     
    610610        self.assertEqual(Thingy.objects.count(), 1) 
    611611        transaction.rollback() 
    612612        self.assertEqual(Thingy.objects.count(), 0) 
     613        transaction.commit() 
    613614 
    614615    def test_ticket_11101(self): 
    615616        """Test that fixtures can be rolled back (ticket #11101).""" 
  • docs/topics/db/sql.txt

     
    231231 
    232232Transactions and raw SQL 
    233233------------------------ 
    234 If you are using transaction decorators (such as ``commit_on_success``) to 
    235 wrap your views and provide transaction control, you don't have to make a 
    236 manual call to ``transaction.commit_unless_managed()`` -- you can manually 
    237 commit if you want to, but you aren't required to, since the decorator will 
    238 commit for you. However, if you don't manually commit your changes, you will 
    239 need to manually mark the transaction as dirty, using 
    240 ``transaction.set_dirty()``:: 
    241234 
    242     @commit_on_success 
    243     def my_custom_sql_view(request, value): 
    244         from django.db import connection, transaction 
    245         cursor = connection.cursor() 
     235.. versionchanged:: 1.3 
    246236 
    247         # Data modifying operation 
    248         cursor.execute("UPDATE bar SET foo = 1 WHERE baz = %s", [value]) 
     237If you write functions that modify the database via raw SQL, you need to 
     238consider the transaction management mode they run under. If this may be 
     239Django's default mode (by default or using the ``auto_commit`` 
     240decorator), you need to make manual calls to 
     241``transaction.commit_unless_managed()`` to make sure the changes are 
     242committed. If you are using transaction decorators (such as 
     243``commit_on_success``) to wrap your views and provide transaction 
     244control, you don't have to make such calls. 
    249245 
    250         # Since we modified data, mark the transaction as dirty 
    251         transaction.set_dirty() 
    252246 
    253         # Data retrieval operation. This doesn't dirty the transaction, 
    254         # so no call to set_dirty() is required. 
    255         cursor.execute("SELECT foo FROM bar WHERE baz = %s", [value]) 
    256         row = cursor.fetchone() 
    257  
    258         return render_to_response('template.html', {'row': row}) 
    259  
    260 The call to ``set_dirty()`` is made automatically when you use the Django ORM 
    261 to make data modifying database calls. However, when you use raw SQL, Django 
    262 has no way of knowing if your SQL modifies data or not. The manual call to 
    263 ``set_dirty()`` ensures that Django knows that there are modifications that 
    264 must be committed. 
    265  
    266247Connections and cursors 
    267248----------------------- 
    268249 
  • docs/releases/1.3.txt

     
    330330 
    331331 
    332332 
     333Transaction management 
     334~~~~~~~~~~~~~~~~~~~~~~ 
     335 
     336When using managed transactions -- that is, anything but the default 
     337autocommit mode -- it is important when a transaction is marked as 
     338"dirty". Dirty transactions are committed by the ``commit_on_success`` 
     339decorator or the ``TransactionMiddleware``, and ``commit_manually`` 
     340forces them to be closed explicitly; clean transactions "get a pass", 
     341which means they are usually rolled back at the end of a request 
     342when the connection is closed. 
     343 
     344Until Django 1.3, transactions were only marked dirty when Django 
     345was aware of a modifying operation performed in them; that is, either 
     346some model was saved, some bulk update or delete was performed, or 
     347the user explicitly called ``transaction.set_dirty()``. In Django 1.3, 
     348a transaction is marked dirty when any database operation is performed; 
     349this means you no longer need to set a transaction dirty explicitly 
     350when you execute raw SQL or use a data-modifying ``select``. On the 
     351other hand, you do need to explicitly close read-only transactions 
     352under ``commit_manually``. For example, take a look at this code:: 
     353 
     354      @transaction.commit_manually 
     355      def my_view(request, name): 
     356          obj = get_object_or_404(MyObject, name__iexact=name) 
     357          return render_to_response('template', {'object':obj}) 
     358 
     359Until Django 1.3, this works fine. With Django 1.3, this raises a 
     360``TransactionManagementError``; you need to end the transaction 
     361explicitly, one way or another. 
     362 
    333363.. _deprecated-features-1.3: 
    334364 
    335365Features deprecated in 1.3