diff options
| author | Andrei Antoukh <niwi@niwi.be> | 2012-05-12 10:24:20 +0300 |
|---|---|---|
| committer | Anssi Kääriäinen <akaariai@gmail.com> | 2012-05-12 10:29:41 +0300 |
| commit | 365853da016f242937a657b488514e2f69fa6d82 (patch) | |
| tree | 64db87c42886451cfdadd4a3141b606c1580cbc4 /django | |
| parent | 25128856f51cd795d176c3da7808913a45e2b964 (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.py | 55 | ||||
| -rw-r--r-- | django/db/models/signals.py | 4 |
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"]) |
