|
@@ -11,7 +11,7 @@ from django.core import validators
|
|
|
from django.db.models.fields import AutoField, FieldDoesNotExist
|
|
|
from django.db.models.fields.related import (ManyToOneRel,
|
|
|
OneToOneField, add_lazy_relation)
|
|
|
-from django.db import (connections, router, transaction, DatabaseError,
|
|
|
+from django.db import (router, transaction, DatabaseError,
|
|
|
DEFAULT_DB_ALIAS)
|
|
|
from django.db.models.query import Q
|
|
|
from django.db.models.query_utils import DeferredAttribute
|
|
@@ -449,7 +449,8 @@ class Model(object):
|
|
|
return getattr(self, field_name)
|
|
|
return getattr(self, field.attname)
|
|
|
|
|
|
- def save(self, force_insert=False, force_update=False, using=None):
|
|
|
+ def save(self, force_insert=False, force_update=False, using=None,
|
|
|
+ update_fields=None):
|
|
|
"""
|
|
|
Saves the current instance. Override this in a subclass if you want to
|
|
|
control the saving process.
|
|
@@ -458,14 +459,32 @@ class Model(object):
|
|
|
that the "save" must be an SQL insert or update (or equivalent for
|
|
|
non-SQL backends), respectively. Normally, they should not be set.
|
|
|
"""
|
|
|
- if force_insert and force_update:
|
|
|
+ if force_insert and (force_update or update_fields):
|
|
|
raise ValueError("Cannot force both insert and updating in model saving.")
|
|
|
- self.save_base(using=using, force_insert=force_insert, force_update=force_update)
|
|
|
|
|
|
+ if update_fields is not None:
|
|
|
+ # If update_fields is empty, skip the save. We do also check for
|
|
|
+ # no-op saves later on for inheritance cases. This bailout is
|
|
|
+ # still needed for skipping signal sending.
|
|
|
+ if len(update_fields) == 0:
|
|
|
+ return
|
|
|
+
|
|
|
+ update_fields = frozenset(update_fields)
|
|
|
+ field_names = set([field.name for field in self._meta.fields
|
|
|
+ if not field.primary_key])
|
|
|
+ non_model_fields = update_fields.difference(field_names)
|
|
|
+
|
|
|
+ if non_model_fields:
|
|
|
+ raise ValueError("The following fields do not exist in this "
|
|
|
+ "model or are m2m fields: %s"
|
|
|
+ % ', '.join(non_model_fields))
|
|
|
+
|
|
|
+ self.save_base(using=using, force_insert=force_insert,
|
|
|
+ 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,
|
|
|
- force_update=False, using=None):
|
|
|
+ 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
|
|
@@ -473,7 +492,8 @@ class Model(object):
|
|
|
('raw', 'cls', and 'origin').
|
|
|
"""
|
|
|
using = using or router.db_for_write(self.__class__, instance=self)
|
|
|
- assert not (force_insert and force_update)
|
|
|
+ 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
|
|
@@ -483,7 +503,8 @@ class Model(object):
|
|
|
meta = cls._meta
|
|
|
|
|
|
if origin and not meta.auto_created:
|
|
|
- signals.pre_save.send(sender=origin, instance=self, raw=raw, using=using)
|
|
|
+ signals.pre_save.send(sender=origin, instance=self, raw=raw, using=using,
|
|
|
+ update_fields=update_fields)
|
|
|
|
|
|
# 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
|
|
@@ -503,7 +524,8 @@ class Model(object):
|
|
|
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)
|
|
|
+ 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))
|
|
@@ -513,22 +535,27 @@ class Model(object):
|
|
|
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]
|
|
|
+
|
|
|
# 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 whether a record with the primary key already exists.
|
|
|
- if (force_update or (not force_insert and
|
|
|
+ # 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())):
|
|
|
- # It does already exist, so do an UPDATE.
|
|
|
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:
|
|
@@ -541,7 +568,7 @@ class Model(object):
|
|
|
|
|
|
fields = meta.local_fields
|
|
|
if not pk_set:
|
|
|
- if force_update:
|
|
|
+ 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)]
|
|
|
|
|
@@ -561,8 +588,8 @@ class Model(object):
|
|
|
|
|
|
# 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), raw=raw, using=using)
|
|
|
+ signals.post_save.send(sender=origin, instance=self, created=(not record_exists),
|
|
|
+ update_fields=update_fields, raw=raw, using=using)
|
|
|
|
|
|
|
|
|
save_base.alters_data = True
|