summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorAndrei Antoukh <niwi@niwi.be>2012-05-12 10:24:20 +0300
committerAnssi Kääriäinen <akaariai@gmail.com>2012-05-12 10:29:41 +0300
commit365853da016f242937a657b488514e2f69fa6d82 (patch)
tree64db87c42886451cfdadd4a3141b606c1580cbc4 /django
parent25128856f51cd795d176c3da7808913a45e2b964 (diff)
Fixed #4102 -- Allow update of specific fields in model.save()
Added the ability to update only part of the model's fields in model.save() by introducing a new kwarg "update_fields". Thanks to all the numerous reviewers and commenters in the ticket
Diffstat (limited to 'django')
-rw-r--r--django/db/models/base.py55
-rw-r--r--django/db/models/signals.py4
2 files changed, 43 insertions, 16 deletions
diff --git a/django/db/models/base.py b/django/db/models/base.py
index 4b6683baae..a555ad9f13 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -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
diff --git a/django/db/models/signals.py b/django/db/models/signals.py
index 48872e7e7f..4666169bec 100644
--- a/django/db/models/signals.py
+++ b/django/db/models/signals.py
@@ -5,8 +5,8 @@ class_prepared = Signal(providing_args=["class"])
pre_init = Signal(providing_args=["instance", "args", "kwargs"])
post_init = Signal(providing_args=["instance"])
-pre_save = Signal(providing_args=["instance", "raw", "using"])
-post_save = Signal(providing_args=["instance", "raw", "created", "using"])
+pre_save = Signal(providing_args=["instance", "raw", "using", "update_fields"])
+post_save = Signal(providing_args=["instance", "raw", "created", "using", "update_fields"])
pre_delete = Signal(providing_args=["instance", "using"])
post_delete = Signal(providing_args=["instance", "using"])