Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Fixed #12769, #12924 -- Corrected the pickling of curried and lazy ob…

…jects, 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
  • Loading branch information...
commit ad5afd6ed220ed50a2b48d7ccf9786ac0e52f807 1 parent b31b2d4
@freakboy3742 freakboy3742 authored
View
28 django/db/models/fields/__init__.py
@@ -119,34 +119,6 @@ def __init__(self, verbose_name=None, name=None, primary_key=False,
messages.update(error_messages or {})
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):
# This is needed because bisect does not take a comparison function.
return cmp(self.creation_counter, other.creation_counter)
View
8 django/db/models/fields/related.py
@@ -88,8 +88,8 @@ class RelatedField(object):
def contribute_to_class(self, cls, name):
sup = super(RelatedField, self)
- # Add an accessor to allow easy determination of the related query path for this field
- self.related_query_name = curry(self._get_related_query_name, cls._meta)
+ # Store the opts for related_query_name()
+ self.opts = cls._meta
if hasattr(sup, 'contribute_to_class'):
sup.contribute_to_class(cls, name)
@@ -198,12 +198,12 @@ def _pk_trace(self, value, prep_func, lookup_type, **kwargs):
v = v[0]
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
# related object in a table-spanning query. It uses the lower-cased
# object_name by default, but this can be overridden with the
# "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):
# This class provides the functionality that makes the related-object
View
9 django/utils/functional.py
@@ -147,6 +147,12 @@ def lazy(func, *resultclasses):
the lazy evaluation code is triggered. Results are not memoized; the
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):
"""
Encapsulate a function call and act as a proxy for methods that are
@@ -162,6 +168,9 @@ def __init__(self, args, kw):
if self.__dispatch is None:
self.__prepare_class__()
+ def __reduce_ex__(self, protocol):
+ return (lazy, (self.__func, resultclasses), self.__dict__)
+
def __prepare_class__(cls):
cls.__dispatch = {}
for resultclass in resultclasses:
View
34 django/utils/translation/__init__.py
@@ -1,8 +1,11 @@
"""
Internationalization support.
"""
-from django.utils.functional import lazy
+from django.conf import settings
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',
'ngettext_lazy', 'string_concat', 'activate', 'deactivate',
@@ -19,32 +22,23 @@
# replace the functions with their real counterparts (once we do access the
# settings).
-def delayed_loader(*args, **kwargs):
+def delayed_loader(real_name, *args, **kwargs):
"""
- Replace each real_* function with the corresponding function from either
- trans_real or trans_null (e.g. real_gettext is replaced with
- trans_real.gettext or trans_null.gettext). This function is run once, the
- first time any i18n method is called. It replaces all the i18n methods at
- once at that time.
+ Call the real, underlying function. We have a level of indirection here so
+ that modules can use the translation bits without actually requiring
+ Django's settings bits to be configured before import.
"""
- import traceback
- from django.conf import settings
if settings.USE_I18N:
- import trans_real as trans
+ trans = trans_real
else:
- import trans_null as trans
- 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)
+ trans = trans_null
# 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()
for name in __all__:
- g['real_%s' % name] = delayed_loader
+ g['real_%s' % name] = curry(delayed_loader, name)
del g, delayed_loader
def gettext_noop(message):
@@ -102,10 +96,10 @@ def templatize(src):
def deactivate_all():
return real_deactivate_all()
-def string_concat(*strings):
+def _string_concat(*strings):
"""
Lazy variant of string concatenation, needed for translations that are
constructed from multiple parts.
"""
return u''.join([force_unicode(s) for s in strings])
-string_concat = lazy(string_concat, unicode)
+string_concat = lazy(_string_concat, unicode)
View
1  tests/regressiontests/i18n/tests.py
@@ -46,7 +46,6 @@ def test_string_concat(self):
unicode(string_concat(...)) should not raise a TypeError - #4796
"""
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")))
def test_safe_status(self):
View
0  tests/regressiontests/queryset_pickle/__init__.py
No changes.
View
8 tests/regressiontests/queryset_pickle/models.py
@@ -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)
View
14 tests/regressiontests/queryset_pickle/tests.py
@@ -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))
Please sign in to comment.
Something went wrong with that request. Please try again.