Ticket #17341: 17341.2.diff

File 17341.2.diff, 11.0 KB (added by Anssi Kääriäinen, 12 years ago)

Cleanup of base_save() + tests

  • django/db/models/base.py

    diff --git a/django/db/models/base.py b/django/db/models/base.py
    index ebd67be..ee3df5e 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, cls=None, topmost_concrete=True,
     469                  force_insert=False, force_update=False, using=None):
    470470        """
    471471        Does the heavy-lifting involved in saving. Subclasses shouldn't need to
    472472        override this method. It's separate from save() in order to hide the
    473473        need for overrides of save() to pass around internal-only parameters
    474         ('raw', 'cls', and 'origin').
     474        ('raw', 'topmost_concrete', and 'origin').
     475
     476        Internal parameters:
     477         - raw: If set, save just the table used by self.__class__. That is, do
     478           not follow inheritance chains, except for proxy-skipping.
     479         - topmost_concrete: Kept True as long as we are skipping the outmost
     480           proxy models. For parents of a concrete model (that is multitable
     481           inherited model's parents), this will be False.
     482         - cls: This is the current class under save. In inheritance chains this
     483           is given from the previous save_base, but in the topmost save this
     484           will be given in as None.
    475485        """
    476486        using = using or router.db_for_write(self.__class__, instance=self)
    477487        assert not (force_insert and force_update)
     488
    478489        if cls is None:
    479490            cls = self.__class__
    480             meta = cls._meta
    481             if not meta.proxy:
    482                 origin = cls
    483         else:
    484             meta = cls._meta
     491        meta = cls._meta
     492
     493        # Skip proxy chains. Defer the save of proxy objects to their actual
     494        # underlying model.
     495        if meta.proxy:
     496            parent, field = meta.parents.items()[0]
     497            self.save_base(cls=parent, topmost_concrete=topmost_concrete, using=using)
     498            return
    485499
    486         if origin and not meta.auto_created:
    487             signals.pre_save.send(sender=origin, instance=self, raw=raw, using=using)
     500        if topmost_concrete and not meta.auto_created:
     501            signals.pre_save.send(sender=self.__class__, instance=self,
     502                                  raw=raw, using=using)
    488503
    489504        # If we are in a raw save, save the object exactly as presented.
    490505        # That means that we don't try to be smart about saving attributes
    491506        # that might have come from the parent class - we just save the
    492507        # 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
     508        if not raw:
    500509            for parent, field in meta.parents.items():
    501510                # At this point, parent's primary key field may be unknown
    502511                # (for example, from administration form which doesn't fill
    503512                # 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:
     513                if (field and getattr(self, parent._meta.pk.attname) is None
     514                        and getattr(self, field.attname) is not None):
    505515                    setattr(self, parent._meta.pk.attname, getattr(self, field.attname))
    506516
    507                 self.save_base(cls=parent, origin=org, using=using)
    508 
     517                self.save_base(cls=parent, topmost_concrete=False, using=using)
    509518                if field:
    510519                    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)]
    548520
     521        non_pks = [f for f in meta.local_fields if not f.primary_key]
     522
     523        # First, try an UPDATE. If that doesn't update anything, do an INSERT.
     524        pk_val = self._get_pk_val(meta)
     525        pk_set = pk_val is not None
     526        record_exists = True
     527        manager = cls._base_manager
     528        if pk_set:
     529            # Determine whether a record with the primary key already exists.
     530            if (force_update or (not force_insert and
     531                    manager.using(using).filter(pk=pk_val).exists())):
     532                # It does already exist, so do an UPDATE.
     533                if force_update or non_pks:
     534                    values = [(f, None, (raw and getattr(self, f.attname) or f.pre_save(self, False))) for f in non_pks]
     535                    if values:
     536                        rows = manager.using(using).filter(pk=pk_val)._update(values)
     537                        if force_update and not rows:
     538                            raise DatabaseError("Forced update did not affect any rows.")
     539            else:
    549540                record_exists = False
     541        if not pk_set or not record_exists:
     542            if meta.order_with_respect_to:
     543                # If this is a model with an order_with_respect_to
     544                # autopopulate the _order field
     545                field = meta.order_with_respect_to
     546                order_value = manager.using(using).filter(**{field.name: getattr(self, field.attname)}).count()
     547                self._order = order_value
    550548
    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)
     549            fields = meta.local_fields
     550            if not pk_set:
     551                if force_update:
     552                    raise ValueError("Cannot force an update in save() with no primary key.")
     553                fields = [f for f in fields if not isinstance(f, AutoField)]
    553554
    554                 if update_pk:
    555                     setattr(self, meta.pk.attname, result)
    556             transaction.commit_unless_managed(using=using)
     555            record_exists = False
     556
     557            update_pk = bool(meta.has_auto_field and not pk_set)
     558            result = manager._insert([self], fields=fields, return_id=update_pk, using=using, raw=raw)
     559
     560            if update_pk:
     561                setattr(self, meta.pk.attname, result)
    557562
    558563        # Store the database on which the object was saved
    559564        self._state.db = using
    560565        # Once saved, this is no longer a to-be-added instance.
    561566        self._state.adding = False
    562567
    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)
     568        # Commit the transaction & signal that the save is complete.
     569        if topmost_concrete:
     570            transaction.commit_unless_managed(using=using)
     571            if not meta.auto_created:
     572                signals.post_save.send(sender=self.__class__, instance=self,
     573                    created=(not record_exists), raw=raw, using=using)
    567574
    568575
    569576    save_base.alters_data = True
  • 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