Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Already on GitHub? Sign in to your account

Fixed #18306 -- Deferred models should automatically issue update_fields... #65

Closed
wants to merge 1 commit into
from
Jump to file or symbol
Failed to load files and symbols.
+84 −1
Split
View
@@ -479,6 +479,14 @@ def save(self, force_insert=False, force_update=False, using=None,
"model or are m2m fields: %s"
% ', '.join(non_model_fields))
+ elif self._meta.deferred_fields:
+ field_names = set([field.name for field in self._meta.fields
+ if not field.primary_key])
+ non_deferred_fields = field_names.difference(self._meta.deferred_fields)
+
+ if non_deferred_fields:
+ update_fields = frozenset(non_deferred_fields)
+
self.save_base(using=using, force_insert=force_insert,
force_update=force_update, update_fields=update_fields)
save.alters_data = True
@@ -17,7 +17,7 @@
DEFAULT_NAMES = ('verbose_name', 'verbose_name_plural', 'db_table', 'ordering',
'unique_together', 'permissions', 'get_latest_by',
'order_with_respect_to', 'app_label', 'db_tablespace',
- 'abstract', 'managed', 'proxy', 'auto_created')
+ 'abstract', 'managed', 'proxy', 'auto_created', 'deferred_fields')
class Options(object):
def __init__(self, meta, app_label=None):
@@ -53,6 +53,7 @@ def __init__(self, meta, app_label=None):
self.parents = SortedDict()
self.duplicate_targets = {}
self.auto_created = False
+ self.deferred_fields = None
# To handle various inheritance situations, we need to track where
# managers came from (concrete or abstract base classes).
@@ -109,6 +109,10 @@ def __set__(self, instance, value):
never be a database lookup involved.
"""
instance.__dict__[self.field_name] = value
+
+ # If attr is modified, remove this field from defererd field list.
+ if instance._meta.deferred_fields and self.field_name in instance._meta.deferred_fields:
+ instance._meta.deferred_fields.remove(self.field_name)
def select_related_descend(field, restricted, requested, reverse=False):
"""
@@ -149,6 +153,7 @@ def deferred_class_factory(model, attrs):
class Meta:
proxy = True
app_label = model._meta.app_label
+ deferred_fields = set(attrs)
# The app_cache wants a unique name for each model, otherwise the new class
# won't be created (we get an old one back). Therefore, we generate the
@@ -356,6 +356,13 @@ perform an update on all fields.
Specifying ``update_fields`` will force an update.
+When saving a model fetched through deferred model loading :meth:`~Model.only()`
+or :meth:`~Model.defer()` and then saving these models, ``update_fields``
+this automatically populated with not defered fields.
+
+Also, if you assign or change any difered field value, this will be managed
+properly (automatically added to the parameter update_fields).
+
Deleting objects
================
@@ -1110,6 +1110,13 @@ loading of the field that connects from the primary model to the related one
reader, is slightly faster and consumes a little less memory in the Python
process.
+.. versionadded:: 1.5
+
+.. note::
+
+ When calling :meth:`~Model.save()` in model instances with deferred fields,
+ by default only saves not defered fields.
+
only
~~~~
@@ -1151,6 +1158,13 @@ All of the cautions in the note for the :meth:`defer` documentation apply to
``only()`` as well. Use it cautiously and only after exhausting your other
options.
+.. versionadded:: 1.5
+
+.. note::
+
+ When calling :meth:`~Model.save()` in model instances with deferred fields,
+ by default only saves not defered fields.
+
using
~~~~~
@@ -13,6 +13,7 @@ class Account(models.Model):
class Person(models.Model):
name = models.CharField(max_length=20)
gender = models.CharField(max_length=1, choices=GENDER_CHOICES)
+ pid = models.IntegerField(null=True, default=None)
def __unicode__(self):
return self.name
@@ -4,6 +4,8 @@
from django.db.models.signals import pre_save, post_save
from .models import Person, Employee, ProxyEmployee, Profile, Account
+from django.db import connection
+
class UpdateOnlyFieldsTests(TestCase):
def test_update_fields_basic(self):
@@ -18,6 +20,51 @@ def test_update_fields_basic(self):
self.assertEqual(s.gender, 'F')
self.assertEqual(s.name, 'Ian')
+ def test_update_fields_defered(self):
+ s = Person.objects.create(name='Sara', gender='F', pid=22)
+ self.assertEqual(s.gender, 'F')
+
+ s1 = Person.objects.defer("gender", "pid").get(pk=s.pk)
+ s1.name = "Emily"
+ s1.gender = "M"
+
+ with self.assertNumQueries(1):
+ s1.save()
+
+ s2 = Person.objects.get(pk=s1.pk)
+ self.assertEqual(s2.name, "Emily")
+ self.assertEqual(s2.gender, "M")
+
+ def test_update_fields_only_1(self):
+ s = Person.objects.create(name='Sara', gender='F')
+ self.assertEqual(s.gender, 'F')
+
+ s1 = Person.objects.only('name').get(pk=s.pk)
+ s1.name = "Emily"
+ s1.gender = "M"
+
+ with self.assertNumQueries(1):
+ s1.save()
+
+ s2 = Person.objects.get(pk=s1.pk)
+ self.assertEqual(s2.name, "Emily")
+ self.assertEqual(s2.gender, "M")
+
+ def test_update_fields_only_2(self):
+ s = Person.objects.create(name='Sara', gender='F', pid=22)
+ self.assertEqual(s.gender, 'F')
+
+ s1 = Person.objects.only('name').get(pk=s.pk)
+ s1.name = "Emily"
+ s1.gender = "M"
+
+ with self.assertNumQueries(2):
+ s1.save(update_fields=['pid'])
+
+ s2 = Person.objects.get(pk=s1.pk)
+ self.assertEqual(s2.name, "Sara")
+ self.assertEqual(s2.gender, "F")
+
def test_update_fields_m2m(self):
profile_boss = Profile.objects.create(name='Boss', salary=3000)
e1 = Employee.objects.create(name='Sara', gender='F',