diff options
| author | Anssi Kääriäinen <akaariai@gmail.com> | 2012-11-29 12:10:31 +0200 |
|---|---|---|
| committer | Anssi Kääriäinen <akaariai@gmail.com> | 2013-03-14 11:01:47 +0200 |
| commit | 6b4834952dcce0db5cbc1534635c00ff8573a6d8 (patch) | |
| tree | fdf79be9ab8152086cf7401b949720ca1597c35e /django | |
| parent | 8a2f5300b21c3c20a9fc6829d9fcadb023cba4a8 (diff) | |
Fixed #16649 -- Refactored save_base logic
Model.save() will use UPDATE - if not updated - INSERT instead of
SELECT - if found UPDATE else INSERT. This should save a query when
updating, but will cost a little when inserting model with PK set.
Also fixed #17341 -- made sure .save() commits transactions only after
the whole model has been saved. This wasn't the case in model
inheritance situations.
The save_base implementation was refactored into multiple methods.
A typical chain for inherited save is:
save_base()
_save_parents(self)
for each parent:
_save_parents(parent)
_save_table(parent)
_save_table(self)
Diffstat (limited to 'django')
| -rw-r--r-- | django/db/models/base.py | 214 |
1 files changed, 114 insertions, 100 deletions
diff --git a/django/db/models/base.py b/django/db/models/base.py index ab0e42d461..f3e3b76dd7 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -545,124 +545,138 @@ class Model(six.with_metaclass(ModelBase)): force_update=force_update, update_fields=update_fields) save.alters_data = True - def save_base(self, raw=False, cls=None, origin=None, force_insert=False, + def save_base(self, raw=False, force_insert=False, force_update=False, using=None, update_fields=None): """ - Does the heavy-lifting involved in saving. Subclasses shouldn't need to - override this method. It's separate from save() in order to hide the - need for overrides of save() to pass around internal-only parameters - ('raw', 'cls', and 'origin'). + Handles the parts of saving which should be done only once per save, + yet need to be done in raw saves, too. This includes some sanity + checks and signal sending. + + The 'raw' argument is telling save_base not to save any parent + models and not to do any changes to the values before save. This + is used by fixture loading. """ using = using or router.db_for_write(self.__class__, instance=self) assert not (force_insert and (force_update or update_fields)) assert update_fields is None or len(update_fields) > 0 - if cls is None: - cls = self.__class__ - meta = cls._meta - if not meta.proxy: - origin = cls - else: - meta = cls._meta - - if origin and not meta.auto_created: + cls = origin = self.__class__ + # Skip proxies, but keep the origin as the proxy model. + if cls._meta.proxy: + cls = cls._meta.concrete_model + meta = cls._meta + if not meta.auto_created: signals.pre_save.send(sender=origin, instance=self, raw=raw, using=using, update_fields=update_fields) + with transaction.commit_on_success_unless_managed(using=using, savepoint=False): + if not raw: + self._save_parents(cls, using, update_fields) + updated = self._save_table(raw, cls, force_insert, force_update, using, update_fields) + # Store the database on which the object was saved + self._state.db = using + # Once saved, this is no longer a to-be-added instance. + self._state.adding = False - # If we are in a raw save, save the object exactly as presented. - # That means that we don't try to be smart about saving attributes - # that might have come from the parent class - we just save the - # attributes we have been given to the class we have been given. - # We also go through this process to defer the save of proxy objects - # to their actual underlying model. - if not raw or meta.proxy: - if meta.proxy: - org = cls - else: - org = None - for parent, field in meta.parents.items(): - # At this point, parent's primary key field may be unknown - # (for example, from administration form which doesn't fill - # this field). If so, fill it. - if field and getattr(self, parent._meta.pk.attname) is None and getattr(self, field.attname) is not None: - setattr(self, parent._meta.pk.attname, getattr(self, field.attname)) - - self.save_base(cls=parent, origin=org, using=using, - update_fields=update_fields) - - if field: - setattr(self, field.attname, self._get_pk_val(parent._meta)) - # Since we didn't have an instance of the parent handy, we - # set attname directly, bypassing the descriptor. - # Invalidate the related object cache, in case it's been - # accidentally populated. A fresh instance will be - # re-built from the database if necessary. - cache_name = field.get_cache_name() - if hasattr(self, cache_name): - delattr(self, cache_name) - - if meta.proxy: - return - - if not meta.proxy: - non_pks = [f for f in meta.local_fields if not f.primary_key] + # Signal that the save is complete + if not meta.auto_created: + signals.post_save.send(sender=origin, instance=self, created=(not updated), + update_fields=update_fields, raw=raw, using=using) - if update_fields: - non_pks = [f for f in non_pks if f.name in update_fields or f.attname in update_fields] + save_base.alters_data = True - with transaction.commit_on_success_unless_managed(using=using): - # First, try an UPDATE. If that doesn't update anything, do an INSERT. - pk_val = self._get_pk_val(meta) - pk_set = pk_val is not None - record_exists = True - manager = cls._base_manager - if pk_set: - # Determine if we should do an update (pk already exists, forced update, - # no force_insert) - if ((force_update or update_fields) or (not force_insert and - manager.using(using).filter(pk=pk_val).exists())): - if force_update or non_pks: - values = [(f, None, (raw and getattr(self, f.attname) or f.pre_save(self, False))) for f in non_pks] - if values: - rows = manager.using(using).filter(pk=pk_val)._update(values) - if force_update and not rows: - raise DatabaseError("Forced update did not affect any rows.") - if update_fields and not rows: - raise DatabaseError("Save with update_fields did not affect any rows.") - else: - record_exists = False - if not pk_set or not record_exists: - if meta.order_with_respect_to: - # If this is a model with an order_with_respect_to - # autopopulate the _order field - field = meta.order_with_respect_to - order_value = manager.using(using).filter(**{field.name: getattr(self, field.attname)}).count() - self._order = order_value + def _save_parents(self, cls, using, update_fields): + """ + Saves all the parents of cls using values from self. + """ + meta = cls._meta + for parent, field in meta.parents.items(): + # Make sure the link fields are synced between parent and self. + if (field and getattr(self, parent._meta.pk.attname) is None + and getattr(self, field.attname) is not None): + setattr(self, parent._meta.pk.attname, getattr(self, field.attname)) + self._save_parents(cls=parent, using=using, update_fields=update_fields) + self._save_table(cls=parent, using=using, update_fields=update_fields) + # Set the parent's PK value to self. + if field: + setattr(self, field.attname, self._get_pk_val(parent._meta)) + # Since we didn't have an instance of the parent handy set + # attname directly, bypassing the descriptor. Invalidate + # the related object cache, in case it's been accidentally + # populated. A fresh instance will be re-built from the + # database if necessary. + cache_name = field.get_cache_name() + if hasattr(self, cache_name): + delattr(self, cache_name) - fields = meta.local_fields - if not pk_set: - if force_update or update_fields: - raise ValueError("Cannot force an update in save() with no primary key.") - fields = [f for f in fields if not isinstance(f, AutoField)] + def _save_table(self, raw=False, cls=None, force_insert=False, + force_update=False, using=None, update_fields=None): + """ + Does the heavy-lifting involved in saving. Updates or inserts the data + for a single table. + """ + meta = cls._meta + non_pks = [f for f in meta.local_fields if not f.primary_key] - record_exists = False + if update_fields: + non_pks = [f for f in non_pks + if f.name in update_fields or f.attname in update_fields] - update_pk = bool(meta.has_auto_field and not pk_set) - result = manager._insert([self], fields=fields, return_id=update_pk, using=using, raw=raw) + pk_val = self._get_pk_val(meta) + pk_set = pk_val is not None + if not pk_set and (force_update or update_fields): + raise ValueError("Cannot force an update in save() with no primary key.") + updated = False + # If possible, try an UPDATE. If that doesn't update anything, do an INSERT. + if pk_set and not force_insert: + base_qs = cls._base_manager.using(using) + values = [(f, None, (raw and getattr(self, f.attname) or f.pre_save(self, False))) + for f in non_pks] + if not values: + # We can end up here when saving a model in inheritance chain where + # update_fields doesn't target any field in current model. In that + # case we just say the update succeeded. Another case ending up here + # is a model with just PK - in that case check that the PK still + # exists. + updated = update_fields is not None or base_qs.filter(pk=pk_val).exists() + else: + updated = self._do_update(base_qs, using, pk_val, values) + if force_update and not updated: + raise DatabaseError("Forced update did not affect any rows.") + if update_fields and not updated: + raise DatabaseError("Save with update_fields did not affect any rows.") + if not updated: + if meta.order_with_respect_to: + # If this is a model with an order_with_respect_to + # autopopulate the _order field + field = meta.order_with_respect_to + order_value = cls._base_manager.using(using).filter( + **{field.name: getattr(self, field.attname)}).count() + self._order = order_value - if update_pk: - setattr(self, meta.pk.attname, result) + fields = meta.local_fields + if not pk_set: + fields = [f for f in fields if not isinstance(f, AutoField)] - # Store the database on which the object was saved - self._state.db = using - # Once saved, this is no longer a to-be-added instance. - self._state.adding = False + update_pk = bool(meta.has_auto_field and not pk_set) + result = self._do_insert(cls._base_manager, using, fields, update_pk, raw) + if update_pk: + setattr(self, meta.pk.attname, result) + return updated - # Signal that the save is complete - if origin and not meta.auto_created: - signals.post_save.send(sender=origin, instance=self, created=(not record_exists), - update_fields=update_fields, raw=raw, using=using) + def _do_update(self, base_qs, using, pk_val, values): + """ + This method will try to update the model. If the model was updated (in + the sense that an update query was done and a matching row was found + from the DB) the method will return True. + """ + return base_qs.filter(pk=pk_val)._update(values) > 0 - save_base.alters_data = True + def _do_insert(self, manager, using, fields, update_pk, raw): + """ + Do an INSERT. If update_pk is defined then this method should return + the new pk for the model. + """ + return manager._insert([self], fields=fields, return_id=update_pk, + using=using, raw=raw) def delete(self, using=None): using = using or router.db_for_write(self.__class__, instance=self) |
