Code

Ticket #4102: update_fields_django-1.5-1.patch

File update_fields_django-1.5-1.patch, 15.8 KB (added by niwi, 2 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 
    22import sys 
    33from functools import update_wrapper 
    44from itertools import izip 
     5from sets import ImmutableSet 
    56 
    67import django.db.models.manager     # Imported to register signal handler. 
    78from django.conf import settings 
    from django.core import validators 
    1112from django.db.models.fields import AutoField, FieldDoesNotExist 
    1213from django.db.models.fields.related import (ManyToOneRel, 
    1314    OneToOneField, add_lazy_relation) 
    14 from django.db import (connections, router, transaction, DatabaseError, 
     15from django.db import (router, transaction, DatabaseError, 
    1516    DEFAULT_DB_ALIAS) 
    1617from django.db.models.query import Q 
    1718from django.db.models.query_utils import DeferredAttribute 
    class Model(object): 
    449450            return getattr(self, field_name) 
    450451        return getattr(self, field.attname) 
    451452 
    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): 
    453454        """ 
    454455        Saves the current instance. Override this in a subclass if you want to 
    455456        control the saving process. 
    class Model(object): 
    458459        that the "save" must be an SQL insert or update (or equivalent for 
    459460        non-SQL backends), respectively. Normally, they should not be set. 
    460461        """ 
    461         if force_insert and force_update: 
     462        if force_insert and (force_update or update_fields): 
    462463            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) 
    464464 
     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) 
    465481    save.alters_data = True 
    466482 
    467483    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): 
    469485        """ 
    470486        Does the heavy-lifting involved in saving. Subclasses shouldn't need to 
    471487        override this method. It's separate from save() in order to hide the 
    class Model(object): 
    473489        ('raw', 'cls', and 'origin'). 
    474490        """ 
    475491        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 
    477494        if cls is None: 
    478495            cls = self.__class__ 
    479496            meta = cls._meta 
    class Model(object): 
    483500            meta = cls._meta 
    484501 
    485502        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) 
    487505 
    488506        # If we are in a raw save, save the object exactly as presented. 
    489507        # That means that we don't try to be smart about saving attributes 
    class Model(object): 
    503521                if field and getattr(self, parent._meta.pk.attname) is None and getattr(self, field.attname) is not None: 
    504522                    setattr(self, parent._meta.pk.attname, getattr(self, field.attname)) 
    505523 
    506                 self.save_base(cls=parent, origin=org, using=using) 
     524                self.save_base(cls=parent, origin=org, using=using, update_fields=update_fields) 
    507525 
    508526                if field: 
    509527                    setattr(self, field.attname, self._get_pk_val(parent._meta)) 
    class Model(object): 
    513531        if not meta.proxy: 
    514532            non_pks = [f for f in meta.local_fields if not f.primary_key] 
    515533 
     534            if update_fields: 
     535                non_pks = [f for f in non_pks if f.name in update_fields] 
     536 
    516537            # First, try an UPDATE. If that doesn't update anything, do an INSERT. 
    517538            pk_val = self._get_pk_val(meta) 
    518539            pk_set = pk_val is not None 
    class Model(object): 
    520541            manager = cls._base_manager 
    521542            if pk_set: 
    522543                # Determine whether a record with the primary key already exists. 
    523                 if (force_update or (not force_insert and 
     544                if ((force_update or update_fields) or (not force_insert and 
    524545                        manager.using(using).filter(pk=pk_val).exists())): 
    525546                    # It does already exist, so do an UPDATE. 
    526547                    if force_update or non_pks: 
    class Model(object): 
    529550                            rows = manager.using(using).filter(pk=pk_val)._update(values) 
    530551                            if force_update and not rows: 
    531552                                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.") 
    532555                else: 
    533556                    record_exists = False 
    534557            if not pk_set or not record_exists: 
    class Model(object): 
    541564 
    542565                fields = meta.local_fields 
    543566                if not pk_set: 
    544                     if force_update: 
     567                    if force_update or update_fields: 
    545568                        raise ValueError("Cannot force an update in save() with no primary key.") 
    546569                    fields = [f for f in fields if not isinstance(f, AutoField)] 
    547570 
    class Model(object): 
    561584 
    562585        # Signal that the save is complete 
    563586        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) 
    566589 
    567590 
    568591    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"]) 
    55pre_init = Signal(providing_args=["instance", "args", "kwargs"]) 
    66post_init = Signal(providing_args=["instance"]) 
    77 
    8 pre_save = Signal(providing_args=["instance", "raw", "using"]) 
    9 post_save = Signal(providing_args=["instance", "raw", "created", "using"]) 
     8pre_save = Signal(providing_args=["instance", "raw", "using", "update_fields"]) 
     9post_save = Signal(providing_args=["instance", "raw", "created", "using", "update_fields"]) 
    1010 
    1111pre_delete = Signal(providing_args=["instance", "using"]) 
    1212post_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 
    135135 
    136136To save an object back to the database, call ``save()``: 
    137137 
    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]) 
    139139 
    140140.. versionadded:: 1.2 
    141141   The ``using`` argument was added. 
    For more details, see the documentation on :ref:`F() expressions 
    334334<query-expressions>` and their :ref:`use in update queries 
    335335<topics-db-queries-update>`. 
    336336 
     337Specifying which fields to save 
     338------------------------------- 
     339 
     340.. versionadded:: 1.5 
     341 
     342If ``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 
     344database. This may be desirable if you want to update just one or a few fields 
     345on an object, as there will be a slight performance benefit from preventing 
     346all 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 
     352The ``update_fields`` argument can be any iterable containing strings. An 
     353not None, but empty ``update_fields`` skips the save completely (no signals 
     354sent). Specifying ``update_fields`` will force an update. 
     355 
    337356Deleting objects 
    338357================ 
    339358 
  • 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: 
    123123``using`` 
    124124    The database alias being used. 
    125125 
     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 
    126130post_save 
    127131--------- 
    128132 
    Arguments sent with this signal: 
    154158``using`` 
    155159    The database alias being used. 
    156160 
     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 
    157165pre_delete 
    158166---------- 
    159167 
  • 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: 
    4444* :mod:`django.utils.timezone` provides a helper for converting aware 
    4545  datetimes between time zones. See :func:`~django.utils.timezone.localtime`. 
    4646 
     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 
    4751Backwards incompatible changes in 1.5 
    4852===================================== 
    4953 
  • 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 
     2from django.db import models 
     3 
     4GENDER_CHOICES = ( 
     5    ('M', 'Male'), 
     6    ('F', 'Female'), 
     7) 
     8 
     9class 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 
     16class Employee(Person): 
     17    employee_num = models.IntegerField(default=0) 
     18    profile = models.ForeignKey('Profile', related_name='profiles', null=True) 
     19 
     20 
     21class 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
    - +  
     1from __future__ import absolute_import 
     2 
     3from django.test import TestCase 
     4from django.db.models.signals import pre_save, post_save 
     5from .models import Person, Employee, Profile 
     6 
     7class 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'])