Ticket #4102: update_fields_django-1.4-4.patch

File update_fields_django-1.4-4.patch, 9.9 KB (added by Andrei Antoukh, 12 years ago)
  • docs/ref/signals.txt

     
    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
     
    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/ref/models/instances.txt

     
    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.
     
    334334<query-expressions>` and their :ref:`use in update queries
    335335<topics-db-queries-update>`.
    336336
     337Specifying which fields to save
     338-------------------------------
     339
     340If ``save()`` is passed a list of field names as keyword argument ``update_fields``,
     341only the fields named in that list will be saved to the database. This may be
     342desirable if you want to update just one or a few fields on an object, as there
     343will be a slight performance benefit from preventing all of the model fields
     344from being updated in the database. For example:
     345
     346    product.name = 'Name changed again'
     347    product.save(update_fields=['name'])
     348
     349
     350``update_fields`` must be a list, tuple or None. And this parameter implies ``force_update=True``.
     351
     352
    337353Deleting objects
    338354================
    339355
  • django/db/models/signals.py

     
    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"])
  • django/db/models/base.py

     
    449449            return getattr(self, field_name)
    450450        return getattr(self, field.attname)
    451451
    452     def save(self, force_insert=False, force_update=False, using=None):
     452    def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
    453453        """
    454454        Saves the current instance. Override this in a subclass if you want to
    455455        control the saving process.
     
    460460        """
    461461        if force_insert and force_update:
    462462            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)
    464463
     464        if update_fields is not None:
     465            if not isinstance(update_fields, (list, tuple)):
     466                raise ValueError("update_fields must be a list or tuple")
     467
     468            # if update_fields is empty, does nothink
     469            if len(update_fields) == 0:
     470                return
     471
     472            field_names = self._meta.get_all_field_names()
     473            for field_name in update_fields:
     474                if field_name not in field_names:
     475                    raise ValueError("%s field does not exist in this model" % (field_name))
     476
     477            force_update = True
     478
     479        self.save_base(using=using, force_insert=force_insert,
     480                        force_update=force_update, update_fields=update_fields)
     481
    465482    save.alters_data = True
    466483
    467484    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):
    469486        """
    470487        Does the heavy-lifting involved in saving. Subclasses shouldn't need to
    471488        override this method. It's separate from save() in order to hide the
     
    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
     
    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))
     
    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
     
    561582
    562583        # Signal that the save is complete
    563584        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)
     585            signals.post_save.send(sender=origin, instance=self, created=(not record_exists),
     586                                                update_fields=update_fields, raw=raw, using=using)
    566587
    567588
    568589    save_base.alters_data = True
  • tests/modeltests/update_only_fields/__init__.py

     
     1# -*- coding: utf-8 -*-
     2
     3
  • tests/modeltests/update_only_fields/tests.py

     
     1from __future__ import absolute_import
     2from __future__ import with_statement
     3
     4from django.test import TestCase
     5from .models import Person, Employee, Profile
     6
     7class UpdateOnlyFieldsTests(TestCase):
     8    def test_simple_update_fields(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_field_with_inherited(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_field_with_incorrect_params(self):
     45        s = Person.objects.create(name='Sara', gender='F')
     46
     47        with self.assertRaises(ValueError):
     48            s.save(update_fields=['first_name'])
     49
     50        with self.assertRaises(ValueError):
     51            s.save(update_fields="name")
     52
     53    def test_num_querys_on_save_with_empty_update_fields(self):
     54        s = Person.objects.create(name='Sara', gender='F')
     55
     56        with self.assertNumQueries(0):
     57            s.save(update_fields=[])
  • tests/modeltests/update_only_fields/models.py

     
     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')
     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
Back to Top