Ticket #4102: update_fields_django-1.5-3.patch
File update_fields_django-1.5-3.patch, 17.3 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..aa12adf 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 472 update_fields = ImmutableSet(update_fields) 473 field_names = set([field.name for field in self._meta.fields if not field.primary_key]) 474 not_model_fields = update_fields.difference(field_names) 475 476 if not_model_fields: 477 raise ValueError("The following fields do not exist in this model or is m2n field: %s" 478 % ', '.join(not_model_fields)) 479 480 self.save_base(using=using, force_insert=force_insert, 481 force_update=force_update, update_fields=update_fields) 465 482 save.alters_data = True 466 483 467 484 def save_base(self, raw=False, cls=None, origin=None, force_insert=False, 468 force_update=False, using=None):485 force_update=False, using=None, update_fields=None): 469 486 """ 470 487 Does the heavy-lifting involved in saving. Subclasses shouldn't need to 471 488 override this method. It's separate from save() in order to hide the … … class Model(object): 473 490 ('raw', 'cls', and 'origin'). 474 491 """ 475 492 using = using or router.db_for_write(self.__class__, instance=self) 476 assert not (force_insert and force_update) 493 assert not (force_insert and (force_update or update_fields)) 494 assert not update_fields or len(update_fields) > 0 477 495 if cls is None: 478 496 cls = self.__class__ 479 497 meta = cls._meta … … class Model(object): 483 501 meta = cls._meta 484 502 485 503 if origin and not meta.auto_created: 486 signals.pre_save.send(sender=origin, instance=self, raw=raw, using=using) 504 signals.pre_save.send(sender=origin, instance=self, raw=raw, using=using, 505 update_fields=update_fields) 487 506 488 507 # If we are in a raw save, save the object exactly as presented. 489 508 # That means that we don't try to be smart about saving attributes … … class Model(object): 503 522 if field and getattr(self, parent._meta.pk.attname) is None and getattr(self, field.attname) is not None: 504 523 setattr(self, parent._meta.pk.attname, getattr(self, field.attname)) 505 524 506 self.save_base(cls=parent, origin=org, using=using )525 self.save_base(cls=parent, origin=org, using=using, update_fields=update_fields) 507 526 508 527 if field: 509 528 setattr(self, field.attname, self._get_pk_val(parent._meta)) … … class Model(object): 513 532 if not meta.proxy: 514 533 non_pks = [f for f in meta.local_fields if not f.primary_key] 515 534 535 if update_fields: 536 non_pks = [f for f in non_pks if f.name in update_fields] 537 516 538 # First, try an UPDATE. If that doesn't update anything, do an INSERT. 517 539 pk_val = self._get_pk_val(meta) 518 540 pk_set = pk_val is not None … … class Model(object): 520 542 manager = cls._base_manager 521 543 if pk_set: 522 544 # Determine whether a record with the primary key already exists. 523 if ( force_updateor (not force_insert and545 if ((force_update or update_fields) or (not force_insert and 524 546 manager.using(using).filter(pk=pk_val).exists())): 525 547 # It does already exist, so do an UPDATE. 526 548 if force_update or non_pks: … … class Model(object): 529 551 rows = manager.using(using).filter(pk=pk_val)._update(values) 530 552 if force_update and not rows: 531 553 raise DatabaseError("Forced update did not affect any rows.") 554 if update_fields and not rows: 555 raise DatabaseError("Save with update_fields did not affect any rows.") 532 556 else: 533 557 record_exists = False 534 558 if not pk_set or not record_exists: … … class Model(object): 541 565 542 566 fields = meta.local_fields 543 567 if not pk_set: 544 if force_update :568 if force_update or update_fields: 545 569 raise ValueError("Cannot force an update in save() with no primary key.") 546 570 fields = [f for f in fields if not isinstance(f, AutoField)] 547 571 … … class Model(object): 561 585 562 586 # Signal that the save is complete 563 587 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)588 signals.post_save.send(sender=origin, instance=self, created=(not record_exists), 589 update_fields=update_fields, raw=raw, using=using) 566 590 567 591 568 592 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/models.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..e69de29 diff --git a/tests/modeltests/update_only_fields/models.py b/tests/modeltests/update_only_fields/models.py new file mode 100644 index 0000000..968dba9
- + 1 2 from django.db import models 3 4 GENDER_CHOICES = ( 5 ('M', 'Male'), 6 ('F', 'Female'), 7 ) 8 9 class Account(models.Model): 10 num = models.IntegerField() 11 12 13 class Person(models.Model): 14 name = models.CharField(max_length=20) 15 gender = models.CharField(max_length=1, choices=GENDER_CHOICES) 16 17 def __unicode__(self): 18 return self.name 19 20 21 class Employee(Person): 22 employee_num = models.IntegerField(default=0) 23 profile = models.ForeignKey('Profile', related_name='profiles', null=True) 24 accounts = models.ManyToManyField('Account', related_name='employees', blank=True, null=True) 25 26 27 class Profile(models.Model): 28 name = models.CharField(max_length=200) 29 salary = models.FloatField(default=1000.0) 30 31 def __unicode__(self): 32 return self.name 33 34 35 class ProxyEmployee(Employee): 36 class Meta: 37 proxy = True -
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..e1fcc97
- + 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, ProxyEmployee, Profile, Account 6 7 8 class UpdateOnlyFieldsTests(TestCase): 9 def test_update_fields_basic(self): 10 s = Person.objects.create(name='Sara', gender='F') 11 self.assertEqual(s.gender, 'F') 12 13 s.gender = 'M' 14 s.name = 'Ian' 15 s.save(update_fields=['name']) 16 17 s = Person.objects.get(pk=s.pk) 18 self.assertEqual(s.gender, 'F') 19 self.assertEqual(s.name, 'Ian') 20 21 def test_update_fields_m2n(self): 22 profile_boss = Profile.objects.create(name='Boss', salary=3000) 23 e1 = Employee.objects.create(name='Sara', gender='F', 24 employee_num=1, profile=profile_boss) 25 26 a1 = Account.objects.create(num=1) 27 a2 = Account.objects.create(num=2) 28 29 e1.accounts = [a1,a2] 30 31 with self.assertRaises(ValueError): 32 e1.save(update_fields=['accounts']) 33 34 def test_update_fields_inheritance(self): 35 profile_boss = Profile.objects.create(name='Boss', salary=3000) 36 profile_receptionist = Profile.objects.create(name='Receptionist', salary=1000) 37 38 e1 = Employee.objects.create(name='Sara', gender='F', 39 employee_num=1, profile=profile_boss) 40 41 e1.name = 'Ian' 42 e1.gender = 'M' 43 e1.save(update_fields=['name']) 44 45 e2 = Employee.objects.get(pk=e1.pk) 46 self.assertEqual(e2.name, 'Ian') 47 self.assertEqual(e2.gender, 'F') 48 self.assertEqual(e2.profile, profile_boss) 49 50 e2.profile = profile_receptionist 51 e2.name = 'Sara' 52 e2.save(update_fields=['profile']) 53 54 e3 = Employee.objects.get(pk=e1.pk) 55 self.assertEqual(e3.name, 'Ian') 56 self.assertEqual(e3.profile, profile_receptionist) 57 58 def test_update_fields_inheritance_with_proxy_model(self): 59 profile_boss = Profile.objects.create(name='Boss', salary=3000) 60 profile_receptionist = Profile.objects.create(name='Receptionist', salary=1000) 61 62 e1 = ProxyEmployee.objects.create(name='Sara', gender='F', 63 employee_num=1, profile=profile_boss) 64 65 e1.name = 'Ian' 66 e1.gender = 'M' 67 e1.save(update_fields=['name']) 68 69 e2 = ProxyEmployee.objects.get(pk=e1.pk) 70 self.assertEqual(e2.name, 'Ian') 71 self.assertEqual(e2.gender, 'F') 72 self.assertEqual(e2.profile, profile_boss) 73 74 e2.profile = profile_receptionist 75 e2.name = 'Sara' 76 e2.save(update_fields=['profile']) 77 78 e3 = ProxyEmployee.objects.get(pk=e1.pk) 79 self.assertEqual(e3.name, 'Ian') 80 self.assertEqual(e3.profile, profile_receptionist) 81 82 def test_update_fields_signals(self): 83 p = Person.objects.create(name='Sara', gender='F') 84 pre_save_data = [] 85 def pre_save_receiver(**kwargs): 86 pre_save_data.append(kwargs['update_fields']) 87 pre_save.connect(pre_save_receiver) 88 post_save_data = [] 89 def post_save_receiver(**kwargs): 90 post_save_data.append(kwargs['update_fields']) 91 post_save.connect(post_save_receiver) 92 p.save(update_fields=['name']) 93 self.assertEqual(len(pre_save_data), 1) 94 self.assertEqual(len(pre_save_data[0]), 1) 95 self.assertTrue('name' in pre_save_data[0]) 96 self.assertEqual(len(post_save_data), 1) 97 self.assertEqual(len(post_save_data[0]), 1) 98 self.assertTrue('name' in post_save_data[0]) 99 100 def test_update_fields_incorrect_params(self): 101 s = Person.objects.create(name='Sara', gender='F') 102 103 with self.assertRaises(ValueError): 104 s.save(update_fields=['first_name']) 105 106 with self.assertRaises(ValueError): 107 s.save(update_fields="name") 108 109 def test_empty_update_fields(self): 110 s = Person.objects.create(name='Sara', gender='F') 111 pre_save_data = [] 112 def pre_save_receiver(**kwargs): 113 pre_save_data.append(kwargs['update_fields']) 114 pre_save.connect(pre_save_receiver) 115 post_save_data = [] 116 def post_save_receiver(**kwargs): 117 post_save_data.append(kwargs['update_fields']) 118 post_save.connect(post_save_receiver) 119 # Save is skipped. 120 with self.assertNumQueries(0): 121 s.save(update_fields=[]) 122 # Signals were skipped, too... 123 self.assertEqual(len(pre_save_data), 0) 124 self.assertEqual(len(post_save_data), 0) 125 126 def test_num_queries_inheritance(self): 127 s = Employee.objects.create(name='Sara', gender='F') 128 s.employee_num = 1 129 s.name = 'Emily' 130 with self.assertNumQueries(1): 131 s.save(update_fields=['employee_num']) 132 s = Employee.objects.get(pk=s.pk) 133 self.assertEqual(s.employee_num, 1) 134 self.assertEqual(s.name, 'Sara') 135 s.employee_num = 2 136 s.name = 'Emily' 137 with self.assertNumQueries(1): 138 s.save(update_fields=['name']) 139 s = Employee.objects.get(pk=s.pk) 140 self.assertEqual(s.name, 'Emily') 141 self.assertEqual(s.employee_num, 1) 142 # A little sanity check that we actually did updates... 143 self.assertEqual(Employee.objects.count(), 1) 144 self.assertEqual(Person.objects.count(), 1) 145 with self.assertNumQueries(2): 146 s.save(update_fields=['name', 'employee_num'])