Ticket #17341: 17341.3.diff

File 17341.3.diff, 12.3 KB (added by Anssi Kääriäinen, 12 years ago)
  • django/db/models/base.py

    diff --git a/django/db/models/base.py b/django/db/models/base.py
    index ebd67be..826b211 100644
    a b class Model(object):  
    465465
    466466    save.alters_data = True
    467467
    468     def save_base(self, raw=False, cls=None, origin=None, force_insert=False,
    469             force_update=False, using=None):
     468    def save_base(self, raw=False, force_insert=False, force_update=False, using=None):
    470469        """
    471         Does the heavy-lifting involved in saving. Subclasses shouldn't need to
    472         override this method. It's separate from save() in order to hide the
    473         need for overrides of save() to pass around internal-only parameters
    474         ('raw', 'cls', and 'origin').
     470        This method does some setup unique to the first save (signals,
     471        transaction handling etc).
     472
     473        The actual saving is delegated to _save_base.
    475474        """
    476         using = using or router.db_for_write(self.__class__, instance=self)
    477475        assert not (force_insert and force_update)
    478         if cls is None:
    479             cls = self.__class__
    480             meta = cls._meta
    481             if not meta.proxy:
    482                 origin = cls
    483         else:
    484             meta = cls._meta
     476        cls = self.__class__
     477        using = using or router.db_for_write(cls, instance=self)
     478        if not cls._meta.auto_created:
     479            signals.pre_save.send(sender=cls, instance=self, raw=raw, using=using)
     480
     481        record_exists = self._save_base(raw, cls, force_insert, force_update, using)
     482        # Store the database on which the object was saved
     483        self._state.db = using
     484        # Once saved, this is no longer a to-be-added instance.
     485        self._state.adding = False
    485486
    486         if origin and not meta.auto_created:
    487             signals.pre_save.send(sender=origin, instance=self, raw=raw, using=using)
     487        # Commit the transaction & signal that the save is complete.
     488        transaction.commit_unless_managed(using=using)
     489        if not cls._meta.auto_created:
     490            signals.post_save.send(sender=cls, instance=self,
     491                created=(not record_exists), raw=raw, using=using)
     492
     493    save_base.alters_data = True
     494
     495    def _save_base(self, raw, cls, force_insert, force_update, using):
     496        """
     497        Does the heavy-lifting involved in saving. Subclasses shouldn't need to
     498        override this method.
     499
     500        Internal parameters:
     501         - raw: If set, save just the table used by self.__class__. That is, do
     502           not follow inheritance chains, except for proxy-skipping.
     503         - cls: The class we are currently saving. self.__class__ or one of its
     504           parents.
     505        """
     506        meta = cls._meta
     507        # Skip proxy chains. Defer the save of proxy objects to their actual
     508        # underlying model.
     509        if meta.proxy:
     510            # A proxy model can have only one parent.
     511            parent, field = meta.parents.items()[0]
     512            return self._save_base(raw, parent, force_insert, force_update, using)
    488513
    489514        # If we are in a raw save, save the object exactly as presented.
    490515        # That means that we don't try to be smart about saving attributes
    491516        # that might have come from the parent class - we just save the
    492517        # attributes we have been given to the class we have been given.
    493         # We also go through this process to defer the save of proxy objects
    494         # to their actual underlying model.
    495         if not raw or meta.proxy:
    496             if meta.proxy:
    497                 org = cls
    498             else:
    499                 org = None
     518        if not raw:
    500519            for parent, field in meta.parents.items():
    501520                # At this point, parent's primary key field may be unknown
    502521                # (for example, from administration form which doesn't fill
    503522                # this field). If so, fill it.
    504                 if field and getattr(self, parent._meta.pk.attname) is None and getattr(self, field.attname) is not None:
     523                if (field and getattr(self, parent._meta.pk.attname) is None
     524                        and getattr(self, field.attname) is not None):
    505525                    setattr(self, parent._meta.pk.attname, getattr(self, field.attname))
    506526
    507                 self.save_base(cls=parent, origin=org, using=using)
    508 
     527                # For multitable inheritance, the force_insert and force_update
     528                # are in effect only for the topmost save. So override them to
     529                # False.
     530                self._save_base(raw, parent, False, False, using)
    509531                if field:
    510532                    setattr(self, field.attname, self._get_pk_val(parent._meta))
    511             if meta.proxy:
    512                 return
    513 
    514         if not meta.proxy:
    515             non_pks = [f for f in meta.local_fields if not f.primary_key]
    516 
    517             # First, try an UPDATE. If that doesn't update anything, do an INSERT.
    518             pk_val = self._get_pk_val(meta)
    519             pk_set = pk_val is not None
    520             record_exists = True
    521             manager = cls._base_manager
    522             if pk_set:
    523                 # Determine whether a record with the primary key already exists.
    524                 if (force_update or (not force_insert and
    525                         manager.using(using).filter(pk=pk_val).exists())):
    526                     # It does already exist, so do an UPDATE.
    527                     if force_update or non_pks:
    528                         values = [(f, None, (raw and getattr(self, f.attname) or f.pre_save(self, False))) for f in non_pks]
    529                         if values:
    530                             rows = manager.using(using).filter(pk=pk_val)._update(values)
    531                             if force_update and not rows:
    532                                 raise DatabaseError("Forced update did not affect any rows.")
    533                 else:
    534                     record_exists = False
    535             if not pk_set or not record_exists:
    536                 if meta.order_with_respect_to:
    537                     # If this is a model with an order_with_respect_to
    538                     # autopopulate the _order field
    539                     field = meta.order_with_respect_to
    540                     order_value = manager.using(using).filter(**{field.name: getattr(self, field.attname)}).count()
    541                     self._order = order_value
    542 
    543                 fields = meta.local_fields
    544                 if not pk_set:
    545                     if force_update:
    546                         raise ValueError("Cannot force an update in save() with no primary key.")
    547                     fields = [f for f in fields if not isinstance(f, AutoField)]
    548533
     534        non_pks = [f for f in meta.local_fields if not f.primary_key]
     535
     536        pk_val = self._get_pk_val(meta)
     537        pk_set = pk_val is not None
     538        record_exists = True
     539        manager = cls._base_manager
     540        if pk_set:
     541            # Determine whether a record with the primary key already exists.
     542            if (force_update or (not force_insert and
     543                    manager.using(using).filter(pk=pk_val).exists())):
     544                # It does already exist, so do an UPDATE.
     545                if force_update or non_pks:
     546                    values = [(f, None, (raw and getattr(self, f.attname) or f.pre_save(self, False))) for f in non_pks]
     547                    if values:
     548                        rows = manager.using(using).filter(pk=pk_val)._update(values)
     549                        if force_update and not rows:
     550                            raise DatabaseError("Forced update did not affect any rows.")
     551            else:
    549552                record_exists = False
    550 
    551                 update_pk = bool(meta.has_auto_field and not pk_set)
    552                 result = manager._insert([self], fields=fields, return_id=update_pk, using=using, raw=raw)
    553 
    554                 if update_pk:
    555                     setattr(self, meta.pk.attname, result)
    556             transaction.commit_unless_managed(using=using)
    557 
    558         # Store the database on which the object was saved
    559         self._state.db = using
    560         # Once saved, this is no longer a to-be-added instance.
    561         self._state.adding = False
    562 
    563         # Signal that the save is complete
    564         if origin and not meta.auto_created:
    565             signals.post_save.send(sender=origin, instance=self,
    566                 created=(not record_exists), raw=raw, using=using)
    567 
    568 
    569     save_base.alters_data = True
     553        if not pk_set or not record_exists:
     554            if meta.order_with_respect_to:
     555                # If this is a model with an order_with_respect_to
     556                # autopopulate the _order field
     557                field = meta.order_with_respect_to
     558                order_value = manager.using(using).filter(**{field.name: getattr(self, field.attname)}).count()
     559                self._order = order_value
     560
     561            fields = meta.local_fields
     562            if not pk_set:
     563                if force_update:
     564                    raise ValueError("Cannot force an update in save() with no primary key.")
     565                fields = [f for f in fields if not isinstance(f, AutoField)]
     566
     567            record_exists = False
     568
     569            update_pk = bool(meta.has_auto_field and not pk_set)
     570            result = manager._insert([self], fields=fields, return_id=update_pk, using=using, raw=raw)
     571
     572            if update_pk:
     573                setattr(self, meta.pk.attname, result)
     574        return record_exists
     575
     576    _save_base.alters_data = True
    570577
    571578    def delete(self, using=None):
    572579        using = using or router.db_for_write(self.__class__, instance=self)
  • tests/modeltests/proxy_models/tests.py

    diff --git a/tests/modeltests/proxy_models/tests.py b/tests/modeltests/proxy_models/tests.py
    index 3ec8465..13b6a36 100644
    a b class ProxyModelTests(TestCase):  
    247247        resp = [u.name for u in UserProxy.objects.all()]
    248248        self.assertEqual(resp, ['Bruce'])
    249249
     250    def test_proxy_force_insert_update(self):
     251        with self.assertNumQueries(1):
     252            u = UserProxy(name='George')
     253            u.save(force_insert=True)
     254        with self.assertNumQueries(1):
     255            u.name = 'Bruce'
     256            u.save(force_update=True)
     257
    250258    def test_select_related(self):
    251259        """
    252260        We can still use `select_related()` to include related models in our
  • tests/modeltests/transactions/models.py

    diff --git a/tests/modeltests/transactions/models.py b/tests/modeltests/transactions/models.py
    index 66acb9f..b0bad3c 100644
    a b class Reporter(models.Model):  
    1919        ordering = ('first_name', 'last_name')
    2020
    2121    def __unicode__(self):
    22         return u"%s %s" % (self.first_name, self.last_name)
    23  No newline at end of file
     22        return u"%s %s" % (self.first_name, self.last_name)
     23
     24class BaseModel(models.Model):
     25    pass
     26
     27class InheritedModel(BaseModel):
     28    uniq_id = models.IntegerField(unique=True)
  • tests/modeltests/transactions/tests.py

    diff --git a/tests/modeltests/transactions/tests.py b/tests/modeltests/transactions/tests.py
    index ed416e2..f4c6328 100644
    a b from __future__ import with_statement, absolute_import  
    33from django.db import connection, transaction, IntegrityError
    44from django.test import TransactionTestCase, skipUnlessDBFeature
    55
    6 from .models import Reporter
     6from .models import Reporter, InheritedModel, BaseModel
    77
    88
    99class TransactionTests(TransactionTestCase):
    class TransactionTests(TransactionTestCase):  
    160160            using_manually_managed_mistake
    161161        )
    162162
     163    @skipUnlessDBFeature('supports_transactions')
     164    def test_nonmanaged_inheritance_save(self):
     165        """
     166        When running in nonmanaged mode, Django commits transactions after
     167        each save. It multitable inheritance case, test that the commit is
     168        done only once, after all tables have been saved. Check it by making
     169        forcing an IntegrityError, and see what happens.
     170        """
     171        InheritedModel(uniq_id=1).save()
     172        try:
     173            InheritedModel(uniq_id=1).save()
     174            # Make sure there is an error!
     175            self.assertFalse(True)
     176        except IntegrityError:
     177            pass
     178        # Now, rollback the connection and see what got saved.
     179        connection.rollback()
     180        self.assertEquals(InheritedModel.objects.count(), 1)
     181        self.assertEquals(BaseModel.objects.count(), 1)
     182
    163183
    164184class TransactionRollbackTests(TransactionTestCase):
    165185    def execute_bad_sql(self):
Back to Top