Fixed #12769, #12924 -- Corrected the pickling of curried and lazy objects, which was preventing queries with translated or related fields from being pickled. And lo, Alex Gaynor didst slayeth the dragon.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@12866 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
b31b2d4da3
commit
ad5afd6ed2
|
@ -119,34 +119,6 @@ class Field(object):
|
||||||
messages.update(error_messages or {})
|
messages.update(error_messages or {})
|
||||||
self.error_messages = messages
|
self.error_messages = messages
|
||||||
|
|
||||||
def __getstate__(self):
|
|
||||||
"""
|
|
||||||
Pickling support.
|
|
||||||
"""
|
|
||||||
from django.utils.functional import Promise
|
|
||||||
obj_dict = self.__dict__.copy()
|
|
||||||
items = []
|
|
||||||
translated_keys = []
|
|
||||||
for k, v in self.error_messages.items():
|
|
||||||
if isinstance(v, Promise):
|
|
||||||
args = getattr(v, '_proxy____args', None)
|
|
||||||
if args:
|
|
||||||
translated_keys.append(k)
|
|
||||||
v = args[0]
|
|
||||||
items.append((k,v))
|
|
||||||
obj_dict['_translated_keys'] = translated_keys
|
|
||||||
obj_dict['error_messages'] = dict(items)
|
|
||||||
return obj_dict
|
|
||||||
|
|
||||||
def __setstate__(self, obj_dict):
|
|
||||||
"""
|
|
||||||
Unpickling support.
|
|
||||||
"""
|
|
||||||
translated_keys = obj_dict.pop('_translated_keys')
|
|
||||||
self.__dict__.update(obj_dict)
|
|
||||||
for k in translated_keys:
|
|
||||||
self.error_messages[k] = _(self.error_messages[k])
|
|
||||||
|
|
||||||
def __cmp__(self, other):
|
def __cmp__(self, other):
|
||||||
# This is needed because bisect does not take a comparison function.
|
# This is needed because bisect does not take a comparison function.
|
||||||
return cmp(self.creation_counter, other.creation_counter)
|
return cmp(self.creation_counter, other.creation_counter)
|
||||||
|
|
|
@ -88,8 +88,8 @@ class RelatedField(object):
|
||||||
def contribute_to_class(self, cls, name):
|
def contribute_to_class(self, cls, name):
|
||||||
sup = super(RelatedField, self)
|
sup = super(RelatedField, self)
|
||||||
|
|
||||||
# Add an accessor to allow easy determination of the related query path for this field
|
# Store the opts for related_query_name()
|
||||||
self.related_query_name = curry(self._get_related_query_name, cls._meta)
|
self.opts = cls._meta
|
||||||
|
|
||||||
if hasattr(sup, 'contribute_to_class'):
|
if hasattr(sup, 'contribute_to_class'):
|
||||||
sup.contribute_to_class(cls, name)
|
sup.contribute_to_class(cls, name)
|
||||||
|
@ -198,12 +198,12 @@ class RelatedField(object):
|
||||||
v = v[0]
|
v = v[0]
|
||||||
return v
|
return v
|
||||||
|
|
||||||
def _get_related_query_name(self, opts):
|
def related_query_name(self):
|
||||||
# This method defines the name that can be used to identify this
|
# This method defines the name that can be used to identify this
|
||||||
# related object in a table-spanning query. It uses the lower-cased
|
# related object in a table-spanning query. It uses the lower-cased
|
||||||
# object_name by default, but this can be overridden with the
|
# object_name by default, but this can be overridden with the
|
||||||
# "related_name" option.
|
# "related_name" option.
|
||||||
return self.rel.related_name or opts.object_name.lower()
|
return self.rel.related_name or self.opts.object_name.lower()
|
||||||
|
|
||||||
class SingleRelatedObjectDescriptor(object):
|
class SingleRelatedObjectDescriptor(object):
|
||||||
# This class provides the functionality that makes the related-object
|
# This class provides the functionality that makes the related-object
|
||||||
|
|
|
@ -147,6 +147,12 @@ def lazy(func, *resultclasses):
|
||||||
the lazy evaluation code is triggered. Results are not memoized; the
|
the lazy evaluation code is triggered. Results are not memoized; the
|
||||||
function is evaluated on every access.
|
function is evaluated on every access.
|
||||||
"""
|
"""
|
||||||
|
# When lazy() is called by the __reduce_ex__ machinery to reconstitute the
|
||||||
|
# __proxy__ class it can't call with *args, so the first item will just be
|
||||||
|
# a tuple.
|
||||||
|
if len(resultclasses) == 1 and isinstance(resultclasses[0], tuple):
|
||||||
|
resultclasses = resultclasses[0]
|
||||||
|
|
||||||
class __proxy__(Promise):
|
class __proxy__(Promise):
|
||||||
"""
|
"""
|
||||||
Encapsulate a function call and act as a proxy for methods that are
|
Encapsulate a function call and act as a proxy for methods that are
|
||||||
|
@ -162,6 +168,9 @@ def lazy(func, *resultclasses):
|
||||||
if self.__dispatch is None:
|
if self.__dispatch is None:
|
||||||
self.__prepare_class__()
|
self.__prepare_class__()
|
||||||
|
|
||||||
|
def __reduce_ex__(self, protocol):
|
||||||
|
return (lazy, (self.__func, resultclasses), self.__dict__)
|
||||||
|
|
||||||
def __prepare_class__(cls):
|
def __prepare_class__(cls):
|
||||||
cls.__dispatch = {}
|
cls.__dispatch = {}
|
||||||
for resultclass in resultclasses:
|
for resultclass in resultclasses:
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
"""
|
"""
|
||||||
Internationalization support.
|
Internationalization support.
|
||||||
"""
|
"""
|
||||||
from django.utils.functional import lazy
|
from django.conf import settings
|
||||||
from django.utils.encoding import force_unicode
|
from django.utils.encoding import force_unicode
|
||||||
|
from django.utils.functional import lazy, curry
|
||||||
|
from django.utils.translation import trans_real, trans_null
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['gettext', 'gettext_noop', 'gettext_lazy', 'ngettext',
|
__all__ = ['gettext', 'gettext_noop', 'gettext_lazy', 'ngettext',
|
||||||
'ngettext_lazy', 'string_concat', 'activate', 'deactivate',
|
'ngettext_lazy', 'string_concat', 'activate', 'deactivate',
|
||||||
|
@ -19,32 +22,23 @@ __all__ = ['gettext', 'gettext_noop', 'gettext_lazy', 'ngettext',
|
||||||
# replace the functions with their real counterparts (once we do access the
|
# replace the functions with their real counterparts (once we do access the
|
||||||
# settings).
|
# settings).
|
||||||
|
|
||||||
def delayed_loader(*args, **kwargs):
|
def delayed_loader(real_name, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Replace each real_* function with the corresponding function from either
|
Call the real, underlying function. We have a level of indirection here so
|
||||||
trans_real or trans_null (e.g. real_gettext is replaced with
|
that modules can use the translation bits without actually requiring
|
||||||
trans_real.gettext or trans_null.gettext). This function is run once, the
|
Django's settings bits to be configured before import.
|
||||||
first time any i18n method is called. It replaces all the i18n methods at
|
|
||||||
once at that time.
|
|
||||||
"""
|
"""
|
||||||
import traceback
|
|
||||||
from django.conf import settings
|
|
||||||
if settings.USE_I18N:
|
if settings.USE_I18N:
|
||||||
import trans_real as trans
|
trans = trans_real
|
||||||
else:
|
else:
|
||||||
import trans_null as trans
|
trans = trans_null
|
||||||
caller = traceback.extract_stack(limit=2)[0][2]
|
|
||||||
g = globals()
|
|
||||||
for name in __all__:
|
|
||||||
if hasattr(trans, name):
|
|
||||||
g['real_%s' % name] = getattr(trans, name)
|
|
||||||
|
|
||||||
# Make the originally requested function call on the way out the door.
|
# Make the originally requested function call on the way out the door.
|
||||||
return g['real_%s' % caller](*args, **kwargs)
|
return getattr(trans, real_name)(*args, **kwargs)
|
||||||
|
|
||||||
g = globals()
|
g = globals()
|
||||||
for name in __all__:
|
for name in __all__:
|
||||||
g['real_%s' % name] = delayed_loader
|
g['real_%s' % name] = curry(delayed_loader, name)
|
||||||
del g, delayed_loader
|
del g, delayed_loader
|
||||||
|
|
||||||
def gettext_noop(message):
|
def gettext_noop(message):
|
||||||
|
@ -102,10 +96,10 @@ def templatize(src):
|
||||||
def deactivate_all():
|
def deactivate_all():
|
||||||
return real_deactivate_all()
|
return real_deactivate_all()
|
||||||
|
|
||||||
def string_concat(*strings):
|
def _string_concat(*strings):
|
||||||
"""
|
"""
|
||||||
Lazy variant of string concatenation, needed for translations that are
|
Lazy variant of string concatenation, needed for translations that are
|
||||||
constructed from multiple parts.
|
constructed from multiple parts.
|
||||||
"""
|
"""
|
||||||
return u''.join([force_unicode(s) for s in strings])
|
return u''.join([force_unicode(s) for s in strings])
|
||||||
string_concat = lazy(string_concat, unicode)
|
string_concat = lazy(_string_concat, unicode)
|
||||||
|
|
|
@ -46,7 +46,6 @@ class TranslationTests(TestCase):
|
||||||
unicode(string_concat(...)) should not raise a TypeError - #4796
|
unicode(string_concat(...)) should not raise a TypeError - #4796
|
||||||
"""
|
"""
|
||||||
import django.utils.translation
|
import django.utils.translation
|
||||||
self.assertEqual(django.utils.translation, reload(django.utils.translation))
|
|
||||||
self.assertEqual(u'django', unicode(django.utils.translation.string_concat("dja", "ngo")))
|
self.assertEqual(u'django', unicode(django.utils.translation.string_concat("dja", "ngo")))
|
||||||
|
|
||||||
def test_safe_status(self):
|
def test_safe_status(self):
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
class Group(models.Model):
|
||||||
|
name = models.CharField(_('name'), max_length=100)
|
||||||
|
|
||||||
|
class Event(models.Model):
|
||||||
|
group = models.ForeignKey(Group)
|
|
@ -0,0 +1,14 @@
|
||||||
|
import pickle
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from models import Group, Event
|
||||||
|
|
||||||
|
|
||||||
|
class PickleabilityTestCase(TestCase):
|
||||||
|
def assert_pickles(self, qs):
|
||||||
|
self.assertEqual(list(pickle.loads(pickle.dumps(qs))), list(qs))
|
||||||
|
|
||||||
|
def test_related_field(self):
|
||||||
|
g = Group.objects.create(name="Ponies Who Own Maybachs")
|
||||||
|
self.assert_pickles(Event.objects.filter(group=g.id))
|
Loading…
Reference in New Issue