Fixed #12734. Deferred fields will now be properly converted to python when accessed. Thanks, Alex Gaynor.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@12579 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Joseph Kocherhans 2010-02-24 19:06:59 +00:00
parent 16fe73d918
commit ff963358d7
5 changed files with 117 additions and 49 deletions

View File

@ -183,11 +183,29 @@ class DeferredAttribute(object):
Retrieves and caches the value from the datastore on the first lookup. Retrieves and caches the value from the datastore on the first lookup.
Returns the cached value. Returns the cached value.
""" """
from django.db.models.fields import FieldDoesNotExist
assert instance is not None assert instance is not None
cls = self.model_ref() cls = self.model_ref()
data = instance.__dict__ data = instance.__dict__
if data.get(self.field_name, self) is self: if data.get(self.field_name, self) is self:
data[self.field_name] = cls._base_manager.filter(pk=instance.pk).values_list(self.field_name, flat=True).using(instance._state.db).get() # self.field_name is the attname of the field, but only() takes the
# actual name, so we need to translate it here.
try:
cls._meta.get_field_by_name(self.field_name)
name = self.field_name
except FieldDoesNotExist:
name = [f.name for f in cls._meta.fields
if f.attname == self.field_name][0]
# We use only() instead of values() here because we want the
# various data coersion methods (to_python(), etc.) to be called
# here.
val = getattr(
cls._base_manager.filter(pk=instance.pk).only(name).using(
instance._state.db).get(),
self.field_name
)
data[self.field_name] = val
return data[self.field_name] return data[self.field_name]
def __set__(self, instance, value): def __set__(self, instance, value):

View File

@ -0,0 +1,71 @@
from django.core.exceptions import FieldError
from django.db import models
from django.utils import simplejson as json
from django.utils.encoding import force_unicode
class Small(object):
"""
A simple class to show that non-trivial Python objects can be used as
attributes.
"""
def __init__(self, first, second):
self.first, self.second = first, second
def __unicode__(self):
return u'%s%s' % (force_unicode(self.first), force_unicode(self.second))
def __str__(self):
return unicode(self).encode('utf-8')
class SmallField(models.Field):
"""
Turns the "Small" class into a Django field. Because of the similarities
with normal character fields and the fact that Small.__unicode__ does
something sensible, we don't need to implement a lot here.
"""
__metaclass__ = models.SubfieldBase
def __init__(self, *args, **kwargs):
kwargs['max_length'] = 2
super(SmallField, self).__init__(*args, **kwargs)
def get_internal_type(self):
return 'CharField'
def to_python(self, value):
if isinstance(value, Small):
return value
return Small(value[0], value[1])
def get_db_prep_save(self, value):
return unicode(value)
def get_db_prep_lookup(self, lookup_type, value):
if lookup_type == 'exact':
return force_unicode(value)
if lookup_type == 'in':
return [force_unicode(v) for v in value]
if lookup_type == 'isnull':
return []
raise FieldError('Invalid lookup type: %r' % lookup_type)
class JSONField(models.TextField):
__metaclass__ = models.SubfieldBase
description = ("JSONField automatically serializes and desializes values to "
"and from JSON.")
def to_python(self, value):
if not value:
return None
if isinstance(value, basestring):
value = json.loads(value)
return value
def get_db_prep_save(self, value):
if value is None:
return None
return json.dumps(value)

View File

@ -2,56 +2,12 @@
Tests for field subclassing. Tests for field subclassing.
""" """
from django.core import serializers
from django.db import models from django.db import models
from django.utils.encoding import force_unicode from django.utils.encoding import force_unicode
from django.core import serializers
from django.core.exceptions import FieldError
class Small(object): from fields import Small, SmallField, JSONField
"""
A simple class to show that non-trivial Python objects can be used as
attributes.
"""
def __init__(self, first, second):
self.first, self.second = first, second
def __unicode__(self):
return u'%s%s' % (force_unicode(self.first), force_unicode(self.second))
def __str__(self):
return unicode(self).encode('utf-8')
class SmallField(models.Field):
"""
Turns the "Small" class into a Django field. Because of the similarities
with normal character fields and the fact that Small.__unicode__ does
something sensible, we don't need to implement a lot here.
"""
__metaclass__ = models.SubfieldBase
def __init__(self, *args, **kwargs):
kwargs['max_length'] = 2
super(SmallField, self).__init__(*args, **kwargs)
def get_internal_type(self):
return 'CharField'
def to_python(self, value):
if isinstance(value, Small):
return value
return Small(value[0], value[1])
def get_db_prep_save(self, value):
return unicode(value)
def get_db_prep_lookup(self, lookup_type, value):
if lookup_type == 'exact':
return force_unicode(value)
if lookup_type == 'in':
return [force_unicode(v) for v in value]
if lookup_type == 'isnull':
return []
raise FieldError('Invalid lookup type: %r' % lookup_type)
class MyModel(models.Model): class MyModel(models.Model):
name = models.CharField(max_length=10) name = models.CharField(max_length=10)
@ -60,6 +16,9 @@ class MyModel(models.Model):
def __unicode__(self): def __unicode__(self):
return force_unicode(self.name) return force_unicode(self.name)
class DataModel(models.Model):
data = JSONField()
__test__ = {'API_TESTS': ur""" __test__ = {'API_TESTS': ur"""
# Creating a model with custom fields is done as per normal. # Creating a model with custom fields is done as per normal.
>>> s = Small(1, 2) >>> s = Small(1, 2)

View File

@ -0,0 +1,21 @@
from django.test import TestCase
from models import DataModel
class CustomField(TestCase):
def test_defer(self):
d = DataModel.objects.create(data=[1, 2, 3])
self.assertTrue(isinstance(d.data, list))
d = DataModel.objects.get(pk=d.pk)
self.assertTrue(isinstance(d.data, list))
self.assertEqual(d.data, [1, 2, 3])
d = DataModel.objects.defer("data").get(pk=d.pk)
d.save()
d = DataModel.objects.get(pk=d.pk)
self.assertTrue(isinstance(d.data, list))
self.assertEqual(d.data, [1, 2, 3])

View File

@ -142,8 +142,7 @@ False
[<class 'regressiontests.defer_regress.models.Child'>, <class 'regressiontests.defer_regress.models.Item'>, <class 'regressiontests.defer_regress.models.Leaf'>, <class 'regressiontests.defer_regress.models.RelatedItem'>, <class 'regressiontests.defer_regress.models.ResolveThis'>] [<class 'regressiontests.defer_regress.models.Child'>, <class 'regressiontests.defer_regress.models.Item'>, <class 'regressiontests.defer_regress.models.Leaf'>, <class 'regressiontests.defer_regress.models.RelatedItem'>, <class 'regressiontests.defer_regress.models.ResolveThis'>]
>>> sorted(get_models(models.get_app('defer_regress'), include_deferred=True), key=lambda obj: obj._meta.object_name) >>> sorted(get_models(models.get_app('defer_regress'), include_deferred=True), key=lambda obj: obj._meta.object_name)
[<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_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_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_item_id'>, <class 'regressiontests.defer_regress.models.ResolveThis'>, <class 'regressiontests.defer_regress.models.ResolveThis_Deferred_num'>] [<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'>, <class 'regressiontests.defer_regress.models.ResolveThis'>, <class 'regressiontests.defer_regress.models.ResolveThis_Deferred_num'>]
""" """
} }