Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Ticket #4102 - Allow UPDATE of only specific fields in model.save() #41

Closed
wants to merge 1 commit into from

4 participants

Andrey Antukh Alex Gaynor Anssi Kääriäinen Russell Keith-Magee
django/db/models/base.py
... ...
@@ -2,6 +2,7 @@
2 2
 import sys
3 3
 from functools import update_wrapper
4 4
 from itertools import izip
  5
+from sets import ImmutableSet
1
Alex Gaynor Owner
alex added a note May 03, 2012

You don't need this, it's just frozenset.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Alex Gaynor
Owner
alex commented May 03, 2012

A thought that just popped into my head: what's the interaction between this and defer()/only(). If I use one of those should the save automatically only save fields that I fetched?

Andrey Antukh
niwibe commented May 03, 2012

No, first get all the fields that have deferred and then save everything.

Example:

>>> a = User.objects.defer("username").get(pk=1)
>>> a.save()

This generates the following queries:

(0.000) SELECT "auth_user"."id", "auth_user"."first_name", "auth_user"."last_name", "auth_user"."email", "auth_user"."password", "auth_user"."is_staff", "auth_user"."is_active", "auth_user"."is_superuser", "auth_user"."last_login", "auth_user"."date_joined" FROM "auth_user" WHERE "auth_user"."id" = 1 ; args=(1,)
(0.000) SELECT (1) AS "a" FROM "auth_user" WHERE "auth_user"."id" = 1  LIMIT 1; args=(1,)
(0.000) SELECT "auth_user"."id", "auth_user"."username" FROM "auth_user" WHERE "auth_user"."id" = 1 ; args=(1,)
(0.000) UPDATE "auth_user" SET "username" = andrei, "first_name" = Andrei, "last_name" = Antoukh, "email" = niwi@niwi.be ....
Anssi Kääriäinen
Owner

The idea is that .only()/.defer() interaction will be handled later on. No need to handle it in this patch yet, but definitely should be done before 1.5.

Andrey Antukh
niwibe commented May 11, 2012

Sorry, I misread the question! The interaction with "only" and "difer" would be interesting to be implemented for 1.5.

I'll work on it, the next free time!

Andrey Antukh
niwibe commented May 11, 2012

Now patch updated to django master and ImmutableSet replaced with frozenset!

Andrey Antukh
niwibe commented May 11, 2012

I just added a possible candidate for the interaction of parameter update_fields with methods only() and defer().

Anssi Kääriäinen
Owner

Lets defer the only() and defer() interaction in a separate pull request (I am just finishing off some last changes to this patch, will send you a pull request).

Andrey Antukh
niwibe commented May 11, 2012

Well! I have reverted the last commit.

Anssi Kääriäinen
Owner

Scratch that pull request: for some reason I can't find your github repo to make that pull... So, if you can manually download the commit d66527f into your branch, rebase it to just one commit and remove the only()/defer() interactions for now. I suggest this as the sole commit message:

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

(to download the commit:
curl https://github.com/akaariai/django/commit/d66527f79d1555abb04dd933019db4ca8d949988.patch | git am
)

Andrey Antukh
niwibe commented May 11, 2012

Already updated the pull-request with your patch! Thank you for your corrections.

Anssi Kääriäinen
Owner

Thanks a lot! As far as I am concerned this is 100% ready for checkin. A final cursory check (especially of the doc changes) would be welcome.

niwibe: thanks for your patience. We all are still trying to figure how to best work using Trac + github together, and how to use pull requests, so you have been a test subject... :) If you have any opinions to share about this process please post them! It would be informative to hear how this process looks from your perspective...

Andrey Antukh
niwibe commented May 11, 2012

I am very happy to have django on github, I'm much more easy to track changes.

The combination of using trac and github is not bad. At first I was confused, did not know if what I did was right or wrong, but gradually I have been adapting and learning.

I, on this issue, I have kept updated, patches in trac and pull-request at github. In my opinion there should be only a single site where they are patches. In github or traction, but not both at once.

Now, once this patch is part of the official repo django, I'm thinking of alex proposal (defer / only methods) and really do not know if I have to create a ticket in trac or not. Or just pull it with a request.

I think it should be (if not present) a site that explains the basic procedures to contribute patches, now that django is on github.

thanks

Anssi Kääriäinen
Owner

I think I have volunteered to write those guidelines... At the risk of totally side-tracking this pull request, my suggestion is the following: you should create a ticket (the addition is complex enough to warrant one), you should work in a branch of your github repo, and you can announce that latest work from you is available from that branch in the trac. Once the work nears completition, create a pull request. That is, branches are used to work on stuff, pull requests are used only when you really thing you have something ready for a pull.

docs/ref/signals.txt
... ...
@@ -154,6 +158,10 @@ 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.
1
Anssi Kääriäinen Owner
akaariai added a note May 11, 2012

Still one error here - sorry for not spotting this earlier. This should of course say a frozenset of fields to update... I can fix this when committing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
tests/modeltests/update_only_fields/tests.py
((6 lines not shown))
  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):
1
Russell Keith-Magee Owner
freakboy3742 added a note May 11, 2012

Typo - m2m not m2n

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Russell Keith-Magee freakboy3742 commented on the diff May 11, 2012
docs/ref/models/instances.txt
... ...
@@ -334,6 +336,24 @@ For more details, see the documentation on :ref:`F() expressions
334 336
 <query-expressions>` and their :ref:`use in update queries
335 337
 <topics-db-queries-update>`.
336 338
 
  339
+Specifying which fields to save
  340
+-------------------------------
  341
+
  342
+.. versionadded:: 1.5
  343
+
  344
+If ``save()`` is passed a list of field names in keyword argument
  345
+``update_fields``, only the fields named in that list will be updated.
  346
+This may be desirable if you want to update just one or a few fields on
  347
+an object. There will be a slight performance benefit from preventing
  348
+all of the model fields from being updated in the database. For example:
  349
+
  350
+    product.name = 'Name changed again'
  351
+    product.save(update_fields=['name'])
  352
+
  353
+The ``update_fields`` argument can be any iterable containing strings. An
1
Russell Keith-Magee Owner
freakboy3742 added a note May 11, 2012

"An not None" should be "A non-None"; but the whole sentence is a bit confusing anyway. Consider rewording the whole sentence. "An empty update_fields iterable will skip the save. A value of None will perform an update on all fields".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Andrey Antukh 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
b671e27
Andrey Antukh
niwibe commented May 12, 2012

Now I fixed this typos. ;)

Anssi Kääriäinen akaariai closed this May 12, 2012
Anssi Kääriäinen
Owner

Closed in commit 365853d - some final editorializing done based on the above comments.

Thanks for everybody participating. Of course, biggest thanks go to niwibe.

Anssi Kääriäinen
Owner

I created a ticket for .only()/.defer() interaction, see #18306.

I missed your latest changes and instead did the final cleanup myself - concurrent working has its downsides.

Andrey Antukh
niwibe commented May 12, 2012

Well! I responded on the ticket, and indicated where my proposal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 1 unique commit by 1 author.

May 12, 2012
Andrey Antukh 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
b671e27
This page is out of date. Refresh to see the latest.
55  django/db/models/base.py
@@ -11,7 +11,7 @@
11 11
 from django.db.models.fields import AutoField, FieldDoesNotExist
12 12
 from django.db.models.fields.related import (ManyToOneRel,
13 13
     OneToOneField, add_lazy_relation)
14  
-from django.db import (connections, router, transaction, DatabaseError,
  14
+from django.db import (router, transaction, DatabaseError,
15 15
     DEFAULT_DB_ALIAS)
16 16
 from django.db.models.query import Q
17 17
 from django.db.models.query_utils import DeferredAttribute
@@ -449,7 +449,8 @@ def serializable_value(self, field_name):
449 449
             return getattr(self, field_name)
450 450
         return getattr(self, field.attname)
451 451
 
452  
-    def save(self, force_insert=False, force_update=False, using=None):
  452
+    def save(self, force_insert=False, force_update=False, using=None,
  453
+             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.
@@ -458,14 +459,32 @@ def save(self, force_insert=False, force_update=False, using=None):
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 = frozenset(update_fields)
  473
+            field_names = set([field.name for field in self._meta.fields
  474
+                               if not field.primary_key])
  475
+            non_model_fields = update_fields.difference(field_names)
  476
+
  477
+            if non_model_fields:
  478
+                raise ValueError("The following fields do not exist in this "
  479
+                                 "model or are m2m fields: %s"
  480
+                                 % ', '.join(non_model_fields))
  481
+
  482
+        self.save_base(using=using, force_insert=force_insert,
  483
+                       force_update=force_update, update_fields=update_fields)
465 484
     save.alters_data = True
466 485
 
467 486
     def save_base(self, raw=False, cls=None, origin=None, force_insert=False,
468  
-            force_update=False, using=None):
  487
+                  force_update=False, using=None, update_fields=None):
469 488
         """
470 489
         Does the heavy-lifting involved in saving. Subclasses shouldn't need to
471 490
         override this method. It's separate from save() in order to hide the
@@ -473,7 +492,8 @@ def save_base(self, raw=False, cls=None, origin=None, force_insert=False,
473 492
         ('raw', 'cls', and 'origin').
474 493
         """
475 494
         using = using or router.db_for_write(self.__class__, instance=self)
476  
-        assert not (force_insert and force_update)
  495
+        assert not (force_insert and (force_update or update_fields))
  496
+        assert update_fields is None or len(update_fields) > 0
477 497
         if cls is None:
478 498
             cls = self.__class__
479 499
             meta = cls._meta
@@ -483,7 +503,8 @@ def save_base(self, raw=False, cls=None, origin=None, force_insert=False,
483 503
             meta = cls._meta
484 504
 
485 505
         if origin and not meta.auto_created:
486  
-            signals.pre_save.send(sender=origin, instance=self, raw=raw, using=using)
  506
+            signals.pre_save.send(sender=origin, instance=self, raw=raw, using=using,
  507
+                                  update_fields=update_fields)
487 508
 
488 509
         # If we are in a raw save, save the object exactly as presented.
489 510
         # That means that we don't try to be smart about saving attributes
@@ -503,7 +524,8 @@ def save_base(self, raw=False, cls=None, origin=None, force_insert=False,
503 524
                 if field and getattr(self, parent._meta.pk.attname) is None and getattr(self, field.attname) is not None:
504 525
                     setattr(self, parent._meta.pk.attname, getattr(self, field.attname))
505 526
 
506  
-                self.save_base(cls=parent, origin=org, using=using)
  527
+                self.save_base(cls=parent, origin=org, using=using,
  528
+                               update_fields=update_fields)
507 529
 
508 530
                 if field:
509 531
                     setattr(self, field.attname, self._get_pk_val(parent._meta))
@@ -513,22 +535,27 @@ def save_base(self, raw=False, cls=None, origin=None, force_insert=False,
513 535
         if not meta.proxy:
514 536
             non_pks = [f for f in meta.local_fields if not f.primary_key]
515 537
 
  538
+            if update_fields:
  539
+                non_pks = [f for f in non_pks if f.name in update_fields]
  540
+
516 541
             # First, try an UPDATE. If that doesn't update anything, do an INSERT.
517 542
             pk_val = self._get_pk_val(meta)
518 543
             pk_set = pk_val is not None
519 544
             record_exists = True
520 545
             manager = cls._base_manager
521 546
             if pk_set:
522  
-                # Determine whether a record with the primary key already exists.
523  
-                if (force_update or (not force_insert and
  547
+                # Determine if we should do an update (pk already exists, forced update,
  548
+                # no force_insert)
  549
+                if ((force_update or update_fields) or (not force_insert and
524 550
                         manager.using(using).filter(pk=pk_val).exists())):
525  
-                    # It does already exist, so do an UPDATE.
526 551
                     if force_update or non_pks:
527 552
                         values = [(f, None, (raw and getattr(self, f.attname) or f.pre_save(self, False))) for f in non_pks]
528 553
                         if values:
529 554
                             rows = manager.using(using).filter(pk=pk_val)._update(values)
530 555
                             if force_update and not rows:
531 556
                                 raise DatabaseError("Forced update did not affect any rows.")
  557
+                            if update_fields and not rows:
  558
+                                raise DatabaseError("Save with update_fields did not affect any rows.")
532 559
                 else:
533 560
                     record_exists = False
534 561
             if not pk_set or not record_exists:
@@ -541,7 +568,7 @@ def save_base(self, raw=False, cls=None, origin=None, force_insert=False,
541 568
 
542 569
                 fields = meta.local_fields
543 570
                 if not pk_set:
544  
-                    if force_update:
  571
+                    if force_update or update_fields:
545 572
                         raise ValueError("Cannot force an update in save() with no primary key.")
546 573
                     fields = [f for f in fields if not isinstance(f, AutoField)]
547 574
 
@@ -561,8 +588,8 @@ def save_base(self, raw=False, cls=None, origin=None, force_insert=False,
561 588
 
562 589
         # Signal that the save is complete
563 590
         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)
  591
+            signals.post_save.send(sender=origin, instance=self, created=(not record_exists),
  592
+                                   update_fields=update_fields, raw=raw, using=using)
566 593
 
567 594
 
568 595
     save_base.alters_data = True
4  django/db/models/signals.py
@@ -5,8 +5,8 @@
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"])
22  docs/ref/models/instances.txt
@@ -135,7 +135,7 @@ 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.
@@ -289,6 +289,8 @@ almost always do the right thing and trying to override that will lead to
289 289
 errors that are difficult to track down. This feature is for advanced use
290 290
 only.
291 291
 
  292
+Using ``update_fields`` will force an update similarly to ``force_update``.
  293
+
292 294
 Updating attributes based on existing fields
293 295
 --------------------------------------------
294 296
 
@@ -334,6 +336,24 @@ For more details, see the documentation on :ref:`F() expressions
334 336
 <query-expressions>` and their :ref:`use in update queries
335 337
 <topics-db-queries-update>`.
336 338
 
  339
+Specifying which fields to save
  340
+-------------------------------
  341
+
  342
+.. versionadded:: 1.5
  343
+
  344
+If ``save()`` is passed a list of field names in keyword argument
  345
+``update_fields``, only the fields named in that list will be updated.
  346
+This may be desirable if you want to update just one or a few fields on
  347
+an object. There will be a slight performance benefit from preventing
  348
+all of the model fields from being updated in the database. For example:
  349
+
  350
+    product.name = 'Name changed again'
  351
+    product.save(update_fields=['name'])
  352
+
  353
+The ``update_fields`` argument can be any iterable containing strings. An 
  354
+empty ``update_fields`` iterable will skip the save. A value of None will 
  355
+perform an update on all fields.
  356
+
337 357
 Deleting objects
338 358
 ================
339 359
 
8  docs/ref/signals.txt
@@ -123,6 +123,10 @@ Arguments sent with this signal:
123 123
 ``using``
124 124
     The database alias being used.
125 125
 
  126
+``update_fields``
  127
+    A frozenset 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
 
@@ -154,6 +158,10 @@ Arguments sent with this signal:
154 158
 ``using``
155 159
     The database alias being used.
156 160
 
  161
+``update_fields``
  162
+    A frozenset 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
 
11  docs/releases/1.5.txt
@@ -33,6 +33,17 @@ version compatible with Python 2.6.
33 33
 What's new in Django 1.5
34 34
 ========================
35 35
 
  36
+Support for saving a subset of model's fields
  37
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  38
+
  39
+The method :meth:`Model.save() <django.db.models.Model.save()>` has a new
  40
+keyword argument ``update_fields``. By using this argument it is possible to
  41
+save only a select list of model's fields. This can be useful for performance
  42
+reasons or when trying to avoid overwriting concurrent changes.
  43
+
  44
+See the :meth:`Model.save() <django.db.models.Model.save()>` documentation for
  45
+more details.
  46
+
36 47
 Minor features
37 48
 ~~~~~~~~~~~~~~
38 49
 
0  tests/modeltests/update_only_fields/__init__.py
No changes.
37  tests/modeltests/update_only_fields/models.py
... ...
@@ -0,0 +1,37 @@
  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
146  tests/modeltests/update_only_fields/tests.py
... ...
@@ -0,0 +1,146 @@
  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_m2m(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'])
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.