Skip to content

Commit 55bcc8a

Browse files
committed
[1.1.X] Fixed #12734. Deferred fields will now be properly converted to python when accessed. Backport of r12579 from trunk.
git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.1.X@12692 bcc190cf-cafb-0310-a4f2-bffc1f526a37
1 parent daec734 commit 55bcc8a

File tree

5 files changed

+117
-50
lines changed

5 files changed

+117
-50
lines changed

django/db/models/query_utils.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,11 +182,28 @@ def __get__(self, instance, owner):
182182
Retrieves and caches the value from the datastore on the first lookup.
183183
Returns the cached value.
184184
"""
185+
from django.db.models.fields import FieldDoesNotExist
186+
185187
assert instance is not None
186188
cls = self.model_ref()
187189
data = instance.__dict__
188190
if data.get(self.field_name, self) is self:
189-
data[self.field_name] = cls._base_manager.filter(pk=instance.pk).values_list(self.field_name, flat=True).get()
191+
# self.field_name is the attname of the field, but only() takes the
192+
# actual name, so we need to translate it here.
193+
try:
194+
cls._meta.get_field_by_name(self.field_name)
195+
name = self.field_name
196+
except FieldDoesNotExist:
197+
name = [f.name for f in cls._meta.fields
198+
if f.attname == self.field_name][0]
199+
# We use only() instead of values() here because we want the
200+
# various data coersion methods (to_python(), etc.) to be called
201+
# here.
202+
val = getattr(
203+
cls._base_manager.filter(pk=instance.pk).only(name).get(),
204+
self.field_name
205+
)
206+
data[self.field_name] = val
190207
return data[self.field_name]
191208

192209
def __set__(self, instance, value):
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from django.core.exceptions import FieldError
2+
from django.db import models
3+
from django.utils import simplejson as json
4+
from django.utils.encoding import force_unicode
5+
6+
7+
class Small(object):
8+
"""
9+
A simple class to show that non-trivial Python objects can be used as
10+
attributes.
11+
"""
12+
def __init__(self, first, second):
13+
self.first, self.second = first, second
14+
15+
def __unicode__(self):
16+
return u'%s%s' % (force_unicode(self.first), force_unicode(self.second))
17+
18+
def __str__(self):
19+
return unicode(self).encode('utf-8')
20+
21+
class SmallField(models.Field):
22+
"""
23+
Turns the "Small" class into a Django field. Because of the similarities
24+
with normal character fields and the fact that Small.__unicode__ does
25+
something sensible, we don't need to implement a lot here.
26+
"""
27+
__metaclass__ = models.SubfieldBase
28+
29+
def __init__(self, *args, **kwargs):
30+
kwargs['max_length'] = 2
31+
super(SmallField, self).__init__(*args, **kwargs)
32+
33+
def get_internal_type(self):
34+
return 'CharField'
35+
36+
def to_python(self, value):
37+
if isinstance(value, Small):
38+
return value
39+
return Small(value[0], value[1])
40+
41+
def get_db_prep_save(self, value):
42+
return unicode(value)
43+
44+
def get_db_prep_lookup(self, lookup_type, value):
45+
if lookup_type == 'exact':
46+
return force_unicode(value)
47+
if lookup_type == 'in':
48+
return [force_unicode(v) for v in value]
49+
if lookup_type == 'isnull':
50+
return []
51+
raise FieldError('Invalid lookup type: %r' % lookup_type)
52+
53+
54+
class JSONField(models.TextField):
55+
__metaclass__ = models.SubfieldBase
56+
57+
description = ("JSONField automatically serializes and desializes values to "
58+
"and from JSON.")
59+
60+
def to_python(self, value):
61+
if not value:
62+
return None
63+
64+
if isinstance(value, basestring):
65+
value = json.loads(value)
66+
return value
67+
68+
def get_db_prep_save(self, value):
69+
if value is None:
70+
return None
71+
return json.dumps(value)

tests/modeltests/field_subclassing/models.py

Lines changed: 5 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,56 +2,12 @@
22
Tests for field subclassing.
33
"""
44

5+
from django.core import serializers
56
from django.db import models
67
from django.utils.encoding import force_unicode
7-
from django.core import serializers
8-
from django.core.exceptions import FieldError
9-
10-
class Small(object):
11-
"""
12-
A simple class to show that non-trivial Python objects can be used as
13-
attributes.
14-
"""
15-
def __init__(self, first, second):
16-
self.first, self.second = first, second
17-
18-
def __unicode__(self):
19-
return u'%s%s' % (force_unicode(self.first), force_unicode(self.second))
20-
21-
def __str__(self):
22-
return unicode(self).encode('utf-8')
238

24-
class SmallField(models.Field):
25-
"""
26-
Turns the "Small" class into a Django field. Because of the similarities
27-
with normal character fields and the fact that Small.__unicode__ does
28-
something sensible, we don't need to implement a lot here.
29-
"""
30-
__metaclass__ = models.SubfieldBase
9+
from fields import Small, SmallField, JSONField
3110

32-
def __init__(self, *args, **kwargs):
33-
kwargs['max_length'] = 2
34-
super(SmallField, self).__init__(*args, **kwargs)
35-
36-
def get_internal_type(self):
37-
return 'CharField'
38-
39-
def to_python(self, value):
40-
if isinstance(value, Small):
41-
return value
42-
return Small(value[0], value[1])
43-
44-
def get_db_prep_save(self, value):
45-
return unicode(value)
46-
47-
def get_db_prep_lookup(self, lookup_type, value):
48-
if lookup_type == 'exact':
49-
return force_unicode(value)
50-
if lookup_type == 'in':
51-
return [force_unicode(v) for v in value]
52-
if lookup_type == 'isnull':
53-
return []
54-
raise FieldError('Invalid lookup type: %r' % lookup_type)
5511

5612
class MyModel(models.Model):
5713
name = models.CharField(max_length=10)
@@ -60,6 +16,9 @@ class MyModel(models.Model):
6016
def __unicode__(self):
6117
return force_unicode(self.name)
6218

19+
class DataModel(models.Model):
20+
data = JSONField()
21+
6322
__test__ = {'API_TESTS': ur"""
6423
# Creating a model with custom fields is done as per normal.
6524
>>> s = Small(1, 2)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from django.test import TestCase
2+
3+
from models import DataModel
4+
5+
6+
class CustomField(TestCase):
7+
def test_defer(self):
8+
d = DataModel.objects.create(data=[1, 2, 3])
9+
10+
self.assertTrue(isinstance(d.data, list))
11+
12+
d = DataModel.objects.get(pk=d.pk)
13+
self.assertTrue(isinstance(d.data, list))
14+
self.assertEqual(d.data, [1, 2, 3])
15+
16+
d = DataModel.objects.defer("data").get(pk=d.pk)
17+
d.save()
18+
19+
d = DataModel.objects.get(pk=d.pk)
20+
self.assertTrue(isinstance(d.data, list))
21+
self.assertEqual(d.data, [1, 2, 3])

tests/regressiontests/defer_regress/models.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,8 @@ def __unicode__(self):
142142
>>> sorted(get_models(models.get_app('defer_regress')), key=lambda obj: obj.__class__.__name__)
143143
[<class 'regressiontests.defer_regress.models.Item'>, <class 'regressiontests.defer_regress.models.RelatedItem'>, <class 'regressiontests.defer_regress.models.Child'>, <class 'regressiontests.defer_regress.models.Leaf'>]
144144
145-
>>> sorted(get_models(models.get_app('defer_regress'), include_deferred=True), key=lambda obj: obj.__class__.__name__)
146-
[<class 'regressiontests.defer_regress.models.Item'>, <class 'regressiontests.defer_regress.models.RelatedItem'>, <class 'regressiontests.defer_regress.models.Child'>, <class 'regressiontests.defer_regress.models.Leaf'>, <class 'regressiontests.defer_regress.models.Item_Deferred_text_value'>, <class 'regressiontests.defer_regress.models.Item_Deferred_name_other_value_text'>, <class 'regressiontests.defer_regress.models.RelatedItem_Deferred_item_id'>, <class 'regressiontests.defer_regress.models.Leaf_Deferred_second_child_value'>, <class 'regressiontests.defer_regress.models.Leaf_Deferred_name_value'>, <class 'regressiontests.defer_regress.models.Item_Deferred_name'>, <class 'regressiontests.defer_regress.models.Item_Deferred_other_value_text_value'>, <class 'regressiontests.defer_regress.models.Leaf_Deferred_value'>]
147-
145+
>>> sorted(get_models(models.get_app('defer_regress'), include_deferred=True), key=lambda obj: obj._meta.object_name)
146+
[<class 'regressiontests.defer_regress.models.Child'>, <class 'regressiontests.defer_regress.models.Item'>, <class 'regressiontests.defer_regress.models.Item_Deferred_name'>, <class 'regressiontests.defer_regress.models.Item_Deferred_name_other_value_text'>, <class 'regressiontests.defer_regress.models.Item_Deferred_name_other_value_value'>, <class 'regressiontests.defer_regress.models.Item_Deferred_other_value_text_value'>, <class 'regressiontests.defer_regress.models.Item_Deferred_text_value'>, <class 'regressiontests.defer_regress.models.Leaf'>, <class 'regressiontests.defer_regress.models.Leaf_Deferred_child_id_second_child_id_value'>, <class 'regressiontests.defer_regress.models.Leaf_Deferred_name_value'>, <class 'regressiontests.defer_regress.models.Leaf_Deferred_second_child_value'>, <class 'regressiontests.defer_regress.models.Leaf_Deferred_value'>, <class 'regressiontests.defer_regress.models.RelatedItem'>, <class 'regressiontests.defer_regress.models.RelatedItem_Deferred_'>, <class 'regressiontests.defer_regress.models.RelatedItem_Deferred_item_id'>]
148147
"""
149148
}
150149

0 commit comments

Comments
 (0)