Ticket #4102: update_fields_django-1.5-1.patch
File update_fields_django-1.5-1.patch, 15.8 KB (added by , 13 years ago) |
---|
-
django/db/models/base.py
diff --git a/django/db/models/base.py b/django/db/models/base.py index 7df4f6d..8bd49f1 100644
a b import copy 2 2 import sys 3 3 from functools import update_wrapper 4 4 from itertools import izip 5 from sets import ImmutableSet 5 6 6 7 import django.db.models.manager # Imported to register signal handler. 7 8 from django.conf import settings … … from django.core import validators 11 12 from django.db.models.fields import AutoField, FieldDoesNotExist 12 13 from django.db.models.fields.related import (ManyToOneRel, 13 14 OneToOneField, add_lazy_relation) 14 from django.db import ( connections,router, transaction, DatabaseError,15 from django.db import (router, transaction, DatabaseError, 15 16 DEFAULT_DB_ALIAS) 16 17 from django.db.models.query import Q 17 18 from django.db.models.query_utils import DeferredAttribute … … class Model(object): 449 450 return getattr(self, field_name) 450 451 return getattr(self, field.attname) 451 452 452 def save(self, force_insert=False, force_update=False, using=None ):453 def save(self, force_insert=False, force_update=False, using=None, update_fields=None): 453 454 """ 454 455 Saves the current instance. Override this in a subclass if you want to 455 456 control the saving process. … … class Model(object): 458 459 that the "save" must be an SQL insert or update (or equivalent for 459 460 non-SQL backends), respectively. Normally, they should not be set. 460 461 """ 461 if force_insert and force_update:462 if force_insert and (force_update or update_fields): 462 463 raise ValueError("Cannot force both insert and updating in model saving.") 463 self.save_base(using=using, force_insert=force_insert, force_update=force_update)464 464 465 if update_fields is not None: 466 # If update_fields is empty, skip the save. We do also check for 467 # no-op saves later on for inheritance cases. This bailout is 468 # still needed for skipping signal sending. 469 if len(update_fields) == 0: 470 return 471 update_fields = ImmutableSet(update_fields) 472 473 field_names = set(self._meta.get_all_field_names()) 474 not_model_fields = update_fields.difference(field_names) 475 if not_model_fields: 476 raise ValueError("The following fields do not exist in this model: %s" 477 % ', '.join(not_model_fields)) 478 479 self.save_base(using=using, force_insert=force_insert, 480 force_update=force_update, update_fields=update_fields) 465 481 save.alters_data = True 466 482 467 483 def save_base(self, raw=False, cls=None, origin=None, force_insert=False, 468 force_update=False, using=None):484 force_update=False, using=None, update_fields=None): 469 485 """ 470 486 Does the heavy-lifting involved in saving. Subclasses shouldn't need to 471 487 override this method. It's separate from save() in order to hide the … … class Model(object): 473 489 ('raw', 'cls', and 'origin'). 474 490 """ 475 491 using = using or router.db_for_write(self.__class__, instance=self) 476 assert not (force_insert and force_update) 492 assert not (force_insert and (force_update or update_fields)) 493 assert not update_fields or len(update_fields) > 0 477 494 if cls is None: 478 495 cls = self.__class__ 479 496 meta = cls._meta … … class Model(object): 483 500 meta = cls._meta 484 501 485 502 if origin and not meta.auto_created: 486 signals.pre_save.send(sender=origin, instance=self, raw=raw, using=using) 503 signals.pre_save.send(sender=origin, instance=self, raw=raw, using=using, 504 update_fields=update_fields) 487 505 488 506 # If we are in a raw save, save the object exactly as presented. 489 507 # That means that we don't try to be smart about saving attributes … … class Model(object): 503 521 if field and getattr(self, parent._meta.pk.attname) is None and getattr(self, field.attname) is not None: 504 522 setattr(self, parent._meta.pk.attname, getattr(self, field.attname)) 505 523 506 self.save_base(cls=parent, origin=org, using=using )524 self.save_base(cls=parent, origin=org, using=using, update_fields=update_fields) 507 525 508 526 if field: 509 527 setattr(self, field.attname, self._get_pk_val(parent._meta)) … … class Model(object): 513 531 if not meta.proxy: 514 532 non_pks = [f for f in meta.local_fields if not f.primary_key] 515 533 534 if update_fields: 535 non_pks = [f for f in non_pks if f.name in update_fields] 536 516 537 # First, try an UPDATE. If that doesn't update anything, do an INSERT. 517 538 pk_val = self._get_pk_val(meta) 518 539 pk_set = pk_val is not None … … class Model(object): 520 541 manager = cls._base_manager 521 542 if pk_set: 522 543 # Determine whether a record with the primary key already exists. 523 if ( force_updateor (not force_insert and544 if ((force_update or update_fields) or (not force_insert and 524 545 manager.using(using).filter(pk=pk_val).exists())): 525 546 # It does already exist, so do an UPDATE. 526 547 if force_update or non_pks: … … class Model(object): 529 550 rows = manager.using(using).filter(pk=pk_val)._update(values) 530 551 if force_update and not rows: 531 552 raise DatabaseError("Forced update did not affect any rows.") 553 if update_fields and not rows: 554 raise DatabaseError("Save with update_fields did not affect any rows.") 532 555 else: 533 556 record_exists = False 534 557 if not pk_set or not record_exists: … … class Model(object): 541 564 542 565 fields = meta.local_fields 543 566 if not pk_set: 544 if force_update :567 if force_update or update_fields: 545 568 raise ValueError("Cannot force an update in save() with no primary key.") 546 569 fields = [f for f in fields if not isinstance(f, AutoField)] 547 570 … … class Model(object): 561 584 562 585 # Signal that the save is complete 563 586 if origin and not meta.auto_created: 564 signals.post_save.send(sender=origin, instance=self, 565 created=(not record_exists), raw=raw, using=using)587 signals.post_save.send(sender=origin, instance=self, created=(not record_exists), 588 update_fields=update_fields, raw=raw, using=using) 566 589 567 590 568 591 save_base.alters_data = True -
django/db/models/signals.py
diff --git a/django/db/models/signals.py b/django/db/models/signals.py index 48872e7..4666169 100644
a b class_prepared = Signal(providing_args=["class"]) 5 5 pre_init = Signal(providing_args=["instance", "args", "kwargs"]) 6 6 post_init = Signal(providing_args=["instance"]) 7 7 8 pre_save = Signal(providing_args=["instance", "raw", "using" ])9 post_save = Signal(providing_args=["instance", "raw", "created", "using" ])8 pre_save = Signal(providing_args=["instance", "raw", "using", "update_fields"]) 9 post_save = Signal(providing_args=["instance", "raw", "created", "using", "update_fields"]) 10 10 11 11 pre_delete = Signal(providing_args=["instance", "using"]) 12 12 post_delete = Signal(providing_args=["instance", "using"]) -
docs/ref/models/instances.txt
diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt index 3c84be5..c934977 100644
a b Saving objects 135 135 136 136 To save an object back to the database, call ``save()``: 137 137 138 .. method:: Model.save([force_insert=False, force_update=False, using=DEFAULT_DB_ALIAS ])138 .. method:: Model.save([force_insert=False, force_update=False, using=DEFAULT_DB_ALIAS, update_fields=None]) 139 139 140 140 .. versionadded:: 1.2 141 141 The ``using`` argument was added. … … For more details, see the documentation on :ref:`F() expressions 334 334 <query-expressions>` and their :ref:`use in update queries 335 335 <topics-db-queries-update>`. 336 336 337 Specifying which fields to save 338 ------------------------------- 339 340 .. versionadded:: 1.5 341 342 If ``save()`` is passed a list of field names as keyword argument 343 ``update_fields``, only the fields named in that list will be saved to the 344 database. This may be desirable if you want to update just one or a few fields 345 on an object, as there will be a slight performance benefit from preventing 346 all of the model fields from being updated in the database. For example: 347 348 product.name = 'Name changed again' 349 product.save(update_fields=['name']) 350 351 352 The ``update_fields`` argument can be any iterable containing strings. An 353 not None, but empty ``update_fields`` skips the save completely (no signals 354 sent). Specifying ``update_fields`` will force an update. 355 337 356 Deleting objects 338 357 ================ 339 358 -
docs/ref/signals.txt
diff --git a/docs/ref/signals.txt b/docs/ref/signals.txt index 3917b52..e047d17 100644
a b Arguments sent with this signal: 123 123 ``using`` 124 124 The database alias being used. 125 125 126 ``update_fields`` 127 List or tuple of fields to update explicitly specified in the ``save()`` method. 128 ``None`` if you do not use this parameter when you call ``save()``. 129 126 130 post_save 127 131 --------- 128 132 … … Arguments sent with this signal: 154 158 ``using`` 155 159 The database alias being used. 156 160 161 ``update_fields`` 162 List or tuple of fields to update explicitly specified in the ``save()`` method. 163 ``None`` if you do not use this parameter when you call ``save()``. 164 157 165 pre_delete 158 166 ---------- 159 167 -
docs/releases/1.5.txt
diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index 5a92e7d..82e13b9 100644
a b Django 1.5 also includes several smaller improvements worth noting: 44 44 * :mod:`django.utils.timezone` provides a helper for converting aware 45 45 datetimes between time zones. See :func:`~django.utils.timezone.localtime`. 46 46 47 * :meth:`Model.save() <django.db.models.Model.save()>` has a new argument 48 ``update_fields``. Using this argument allows restricting the set of fields 49 to update when saving model instances. 50 47 51 Backwards incompatible changes in 1.5 48 52 ===================================== 49 53 -
new file tests/modeltests/update_only_fields/__init__.py
diff --git a/tests/modeltests/update_only_fields/__init__.py b/tests/modeltests/update_only_fields/__init__.py new file mode 100644 index 0000000..faaaf79
- + 1 # -*- coding: utf-8 -*- 2 3 -
new file tests/modeltests/update_only_fields/models.py
diff --git a/tests/modeltests/update_only_fields/models.py b/tests/modeltests/update_only_fields/models.py new file mode 100644 index 0000000..3f56ed6
- + 1 2 from django.db import models 3 4 GENDER_CHOICES = ( 5 ('M', 'Male'), 6 ('F', 'Female'), 7 ) 8 9 class Person(models.Model): 10 name = models.CharField(max_length=20) 11 gender = models.CharField(max_length=1, choices=GENDER_CHOICES) 12 13 def __unicode__(self): 14 return self.name 15 16 class Employee(Person): 17 employee_num = models.IntegerField(default=0) 18 profile = models.ForeignKey('Profile', related_name='profiles', null=True) 19 20 21 class Profile(models.Model): 22 name = models.CharField(max_length=200) 23 salary = models.FloatField(default=1000.0) 24 25 def __unicode__(self): 26 return self.name -
new file tests/modeltests/update_only_fields/tests.py
diff --git a/tests/modeltests/update_only_fields/tests.py b/tests/modeltests/update_only_fields/tests.py new file mode 100644 index 0000000..739ead6
- + 1 from __future__ import absolute_import 2 3 from django.test import TestCase 4 from django.db.models.signals import pre_save, post_save 5 from .models import Person, Employee, Profile 6 7 class UpdateOnlyFieldsTests(TestCase): 8 def test_update_fields_basic(self): 9 s = Person.objects.create(name='Sara', gender='F') 10 self.assertEqual(s.gender, 'F') 11 12 s.gender = 'M' 13 s.name = 'Ian' 14 s.save(update_fields=['name']) 15 16 s = Person.objects.get(pk=s.pk) 17 self.assertEqual(s.gender, 'F') 18 self.assertEqual(s.name, 'Ian') 19 20 def test_update_fields_inheritance(self): 21 profile_boss = Profile.objects.create(name='Boss', salary=3000) 22 profile_receptionist = Profile.objects.create(name='Receptionist', salary=1000) 23 24 e1 = Employee.objects.create(name='Sara', gender='F', 25 employee_num=1, profile=profile_boss) 26 27 e1.name = 'Ian' 28 e1.gender = 'M' 29 e1.save(update_fields=['name']) 30 31 e2 = Employee.objects.get(pk=e1.pk) 32 self.assertEqual(e2.name, 'Ian') 33 self.assertEqual(e2.gender, 'F') 34 self.assertEqual(e2.profile, profile_boss) 35 36 e2.profile = profile_receptionist 37 e2.name = 'Sara' 38 e2.save(update_fields=['profile']) 39 40 e3 = Employee.objects.get(pk=e1.pk) 41 self.assertEqual(e3.name, 'Ian') 42 self.assertEqual(e3.profile, profile_receptionist) 43 44 def test_update_fields_signals(self): 45 p = Person.objects.create(name='Sara', gender='F') 46 pre_save_data = [] 47 def pre_save_receiver(**kwargs): 48 pre_save_data.append(kwargs['update_fields']) 49 pre_save.connect(pre_save_receiver) 50 post_save_data = [] 51 def post_save_receiver(**kwargs): 52 post_save_data.append(kwargs['update_fields']) 53 post_save.connect(post_save_receiver) 54 p.save(update_fields=['name']) 55 self.assertEqual(len(pre_save_data), 1) 56 self.assertEqual(len(pre_save_data[0]), 1) 57 self.assertTrue('name' in pre_save_data[0]) 58 self.assertEqual(len(post_save_data), 1) 59 self.assertEqual(len(post_save_data[0]), 1) 60 self.assertTrue('name' in post_save_data[0]) 61 62 def test_update_fields_incorrect_params(self): 63 s = Person.objects.create(name='Sara', gender='F') 64 65 with self.assertRaises(ValueError): 66 s.save(update_fields=['first_name']) 67 68 with self.assertRaises(ValueError): 69 s.save(update_fields="name") 70 71 def test_empty_update_fields(self): 72 s = Person.objects.create(name='Sara', gender='F') 73 pre_save_data = [] 74 def pre_save_receiver(**kwargs): 75 pre_save_data.append(kwargs['update_fields']) 76 pre_save.connect(pre_save_receiver) 77 post_save_data = [] 78 def post_save_receiver(**kwargs): 79 post_save_data.append(kwargs['update_fields']) 80 post_save.connect(post_save_receiver) 81 # Save is skipped. 82 with self.assertNumQueries(0): 83 s.save(update_fields=[]) 84 # Signals were skipped, too... 85 self.assertEqual(len(pre_save_data), 0) 86 self.assertEqual(len(post_save_data), 0) 87 88 def test_num_queries_inheritance(self): 89 s = Employee.objects.create(name='Sara', gender='F') 90 s.employee_num = 1 91 s.name = 'Emily' 92 with self.assertNumQueries(1): 93 s.save(update_fields=['employee_num']) 94 s = Employee.objects.get(pk=s.pk) 95 self.assertEqual(s.employee_num, 1) 96 self.assertEqual(s.name, 'Sara') 97 s.employee_num = 2 98 s.name = 'Emily' 99 with self.assertNumQueries(1): 100 s.save(update_fields=['name']) 101 s = Employee.objects.get(pk=s.pk) 102 self.assertEqual(s.name, 'Emily') 103 self.assertEqual(s.employee_num, 1) 104 # A little sanity check that we actually did updates... 105 self.assertEqual(Employee.objects.count(), 1) 106 self.assertEqual(Person.objects.count(), 1) 107 with self.assertNumQueries(2): 108 s.save(update_fields=['name', 'employee_num'])