Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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
Russell Keith-Magee authored March 27, 2010
28  django/db/models/fields/__init__.py
@@ -119,34 +119,6 @@ def __init__(self, verbose_name=None, name=None, primary_key=False,
119 119
         messages.update(error_messages or {})
120 120
         self.error_messages = messages
121 121
 
122  
-    def __getstate__(self):
123  
-        """
124  
-        Pickling support.
125  
-        """
126  
-        from django.utils.functional import Promise
127  
-        obj_dict = self.__dict__.copy()
128  
-        items = []
129  
-        translated_keys = []
130  
-        for k, v in self.error_messages.items():
131  
-            if isinstance(v, Promise):
132  
-                args = getattr(v, '_proxy____args', None)
133  
-                if args:
134  
-                    translated_keys.append(k)
135  
-                    v = args[0]
136  
-            items.append((k,v))
137  
-        obj_dict['_translated_keys'] = translated_keys
138  
-        obj_dict['error_messages'] = dict(items)
139  
-        return obj_dict
140  
-
141  
-    def __setstate__(self, obj_dict):
142  
-        """
143  
-        Unpickling support.
144  
-        """
145  
-        translated_keys = obj_dict.pop('_translated_keys')
146  
-        self.__dict__.update(obj_dict)
147  
-        for k in translated_keys:
148  
-            self.error_messages[k] = _(self.error_messages[k])
149  
-
150 122
     def __cmp__(self, other):
151 123
         # This is needed because bisect does not take a comparison function.
152 124
         return cmp(self.creation_counter, other.creation_counter)
8  django/db/models/fields/related.py
@@ -88,8 +88,8 @@ class RelatedField(object):
@@ -198,12 +198,12 @@ def _pk_trace(self, value, prep_func, lookup_type, **kwargs):
9  django/utils/functional.py
@@ -147,6 +147,12 @@ def lazy(func, *resultclasses):
147 147
     the lazy evaluation code is triggered. Results are not memoized; the
148 148
     function is evaluated on every access.
149 149
     """
  150
+    # When lazy() is called by the __reduce_ex__ machinery to reconstitute the
  151
+    # __proxy__ class it can't call with *args, so the first item will just be
  152
+    # a tuple.
  153
+    if len(resultclasses) == 1 and isinstance(resultclasses[0], tuple):
  154
+        resultclasses = resultclasses[0]
  155
+
150 156
     class __proxy__(Promise):
151 157
         """
152 158
         Encapsulate a function call and act as a proxy for methods that are
@@ -162,6 +168,9 @@ def __init__(self, args, kw):
162 168
             if self.__dispatch is None:
163 169
                 self.__prepare_class__()
164 170
 
  171
+        def __reduce_ex__(self, protocol):
  172
+            return (lazy, (self.__func, resultclasses), self.__dict__)
  173
+
165 174
         def __prepare_class__(cls):
166 175
             cls.__dispatch = {}
167 176
             for resultclass in resultclasses:
34  django/utils/translation/__init__.py
... ...
@@ -1,8 +1,11 @@
1 1
 """
2 2
 Internationalization support.
3 3
 """
4  
-from django.utils.functional import lazy
  4
+from django.conf import settings
5 5
 from django.utils.encoding import force_unicode
  6
+from django.utils.functional import lazy, curry
  7
+from django.utils.translation import trans_real, trans_null
  8
+
6 9
 
7 10
 __all__ = ['gettext', 'gettext_noop', 'gettext_lazy', 'ngettext',
8 11
         'ngettext_lazy', 'string_concat', 'activate', 'deactivate',
@@ -19,32 +22,23 @@
19 22
 # replace the functions with their real counterparts (once we do access the
20 23
 # settings).
21 24
 
22  
-def delayed_loader(*args, **kwargs):
  25
+def delayed_loader(real_name, *args, **kwargs):
23 26
     """
24  
-    Replace each real_* function with the corresponding function from either
25  
-    trans_real or trans_null (e.g. real_gettext is replaced with
26  
-    trans_real.gettext or trans_null.gettext). This function is run once, the
27  
-    first time any i18n method is called. It replaces all the i18n methods at
28  
-    once at that time.
  27
+    Call the real, underlying function.  We have a level of indirection here so
  28
+    that modules can use the translation bits without actually requiring
  29
+    Django's settings bits to be configured before import.
29 30
     """
30  
-    import traceback
31  
-    from django.conf import settings
32 31
     if settings.USE_I18N:
33  
-        import trans_real as trans
  32
+        trans = trans_real
34 33
     else:
35  
-        import trans_null as trans
36  
-    caller = traceback.extract_stack(limit=2)[0][2]
37  
-    g = globals()
38  
-    for name in __all__:
39  
-        if hasattr(trans, name):
40  
-            g['real_%s' % name] = getattr(trans, name)
  34
+        trans = trans_null
41 35
 
42 36
     # Make the originally requested function call on the way out the door.
43  
-    return g['real_%s' % caller](*args, **kwargs)
  37
+    return getattr(trans, real_name)(*args, **kwargs)
44 38
 
45 39
 g = globals()
46 40
 for name in __all__:
47  
-    g['real_%s' % name] = delayed_loader
  41
+    g['real_%s' % name] = curry(delayed_loader, name)
48 42
 del g, delayed_loader
49 43
 
50 44
 def gettext_noop(message):
@@ -102,10 +96,10 @@ def templatize(src):
102 96
 def deactivate_all():
103 97
     return real_deactivate_all()
104 98
 
105  
-def string_concat(*strings):
  99
+def _string_concat(*strings):
106 100
     """
107 101
     Lazy variant of string concatenation, needed for translations that are
108 102
     constructed from multiple parts.
109 103
     """
110 104
     return u''.join([force_unicode(s) for s in strings])
111  
-string_concat = lazy(string_concat, unicode)
  105
+string_concat = lazy(_string_concat, unicode)
1  tests/regressiontests/i18n/tests.py
@@ -46,7 +46,6 @@ def test_string_concat(self):
46 46
         unicode(string_concat(...)) should not raise a TypeError - #4796
47 47
         """
48 48
         import django.utils.translation
49  
-        self.assertEqual(django.utils.translation, reload(django.utils.translation))
50 49
         self.assertEqual(u'django', unicode(django.utils.translation.string_concat("dja", "ngo")))
51 50
 
52 51
     def test_safe_status(self):
0  tests/regressiontests/queryset_pickle/__init__.py
No changes.
8  tests/regressiontests/queryset_pickle/models.py
... ...
@@ -0,0 +1,8 @@
  1
+from django.db import models
  2
+from django.utils.translation import ugettext_lazy as _
  3
+
  4
+class Group(models.Model):
  5
+    name = models.CharField(_('name'), max_length=100)
  6
+
  7
+class Event(models.Model):
  8
+    group = models.ForeignKey(Group)
14  tests/regressiontests/queryset_pickle/tests.py
... ...
@@ -0,0 +1,14 @@
  1
+import pickle
  2
+
  3
+from django.test import TestCase
  4
+
  5
+from models import Group, Event
  6
+
  7
+
  8
+class PickleabilityTestCase(TestCase):
  9
+    def assert_pickles(self, qs):
  10
+        self.assertEqual(list(pickle.loads(pickle.dumps(qs))), list(qs))
  11
+
  12
+    def test_related_field(self):
  13
+        g = Group.objects.create(name="Ponies Who Own Maybachs")
  14
+        self.assert_pickles(Event.objects.filter(group=g.id))

0 notes on commit ad5afd6

Please sign in to comment.
Something went wrong with that request. Please try again.