|
@@ -545,125 +545,139 @@ 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__
|
|
|
+
|
|
|
+ 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)
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- if not raw or meta.proxy:
|
|
|
- if meta.proxy:
|
|
|
- org = cls
|
|
|
- else:
|
|
|
- org = None
|
|
|
- for parent, field in meta.parents.items():
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- 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))
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- 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]
|
|
|
-
|
|
|
- if update_fields:
|
|
|
- non_pks = [f for f in non_pks if f.name in update_fields or f.attname in update_fields]
|
|
|
-
|
|
|
- with transaction.commit_on_success_unless_managed(using=using):
|
|
|
-
|
|
|
- pk_val = self._get_pk_val(meta)
|
|
|
- pk_set = pk_val is not None
|
|
|
- record_exists = True
|
|
|
- manager = cls._base_manager
|
|
|
- if pk_set:
|
|
|
-
|
|
|
-
|
|
|
- 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:
|
|
|
-
|
|
|
-
|
|
|
- field = meta.order_with_respect_to
|
|
|
- order_value = manager.using(using).filter(**{field.name: getattr(self, field.attname)}).count()
|
|
|
- self._order = order_value
|
|
|
-
|
|
|
- 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)]
|
|
|
-
|
|
|
- record_exists = False
|
|
|
-
|
|
|
- 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)
|
|
|
-
|
|
|
- if update_pk:
|
|
|
- setattr(self, meta.pk.attname, result)
|
|
|
-
|
|
|
+ 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)
|
|
|
|
|
|
self._state.db = using
|
|
|
|
|
|
self._state.adding = False
|
|
|
|
|
|
|
|
|
- if origin and not meta.auto_created:
|
|
|
- signals.post_save.send(sender=origin, instance=self, created=(not record_exists),
|
|
|
+ if not meta.auto_created:
|
|
|
+ signals.post_save.send(sender=origin, instance=self, created=(not updated),
|
|
|
update_fields=update_fields, raw=raw, using=using)
|
|
|
|
|
|
save_base.alters_data = True
|
|
|
|
|
|
+ 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():
|
|
|
+
|
|
|
+ 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)
|
|
|
+
|
|
|
+ if field:
|
|
|
+ setattr(self, field.attname, self._get_pk_val(parent._meta))
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ cache_name = field.get_cache_name()
|
|
|
+ if hasattr(self, cache_name):
|
|
|
+ delattr(self, cache_name)
|
|
|
+
|
|
|
+ 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]
|
|
|
+
|
|
|
+ if update_fields:
|
|
|
+ non_pks = [f for f in non_pks
|
|
|
+ if f.name in update_fields or f.attname in update_fields]
|
|
|
+
|
|
|
+ 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 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:
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ 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:
|
|
|
+
|
|
|
+
|
|
|
+ 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
|
|
|
+
|
|
|
+ fields = meta.local_fields
|
|
|
+ if not pk_set:
|
|
|
+ fields = [f for f in fields if not isinstance(f, AutoField)]
|
|
|
+
|
|
|
+ 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
|
|
|
+
|
|
|
+ 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
|
|
|
+
|
|
|
+ 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)
|
|
|
assert self._get_pk_val() is not None, "%s object can't be deleted because its %s attribute is set to None." % (self._meta.object_name, self._meta.pk.attname)
|