Author: russellm
Date: 2010-03-27 10:54:31 -0500 (Sat, 27 Mar 2010)
New Revision: 12866

Added:
   django/trunk/tests/regressiontests/queryset_pickle/
   django/trunk/tests/regressiontests/queryset_pickle/__init__.py
   django/trunk/tests/regressiontests/queryset_pickle/models.py
   django/trunk/tests/regressiontests/queryset_pickle/tests.py
Modified:
   django/trunk/django/db/models/fields/__init__.py
   django/trunk/django/db/models/fields/related.py
   django/trunk/django/utils/functional.py
   django/trunk/django/utils/translation/__init__.py
   django/trunk/tests/regressiontests/i18n/tests.py
Log:
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.

Modified: django/trunk/django/db/models/fields/__init__.py
===================================================================
--- django/trunk/django/db/models/fields/__init__.py    2010-03-27 15:16:27 UTC 
(rev 12865)
+++ django/trunk/django/db/models/fields/__init__.py    2010-03-27 15:54:31 UTC 
(rev 12866)
@@ -119,34 +119,6 @@
         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)

Modified: django/trunk/django/db/models/fields/related.py
===================================================================
--- django/trunk/django/db/models/fields/related.py     2010-03-27 15:16:27 UTC 
(rev 12865)
+++ django/trunk/django/db/models/fields/related.py     2010-03-27 15:54:31 UTC 
(rev 12866)
@@ -88,8 +88,8 @@
     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 @@
             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

Modified: django/trunk/django/utils/functional.py
===================================================================
--- django/trunk/django/utils/functional.py     2010-03-27 15:16:27 UTC (rev 
12865)
+++ django/trunk/django/utils/functional.py     2010-03-27 15:54:31 UTC (rev 
12866)
@@ -147,6 +147,12 @@
     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 @@
             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:

Modified: django/trunk/django/utils/translation/__init__.py
===================================================================
--- django/trunk/django/utils/translation/__init__.py   2010-03-27 15:16:27 UTC 
(rev 12865)
+++ django/trunk/django/utils/translation/__init__.py   2010-03-27 15:54:31 UTC 
(rev 12866)
@@ -1,9 +1,12 @@
 """
 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',
         'get_language', 'get_language_bidi', 'get_date_formats',
@@ -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 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)

Modified: django/trunk/tests/regressiontests/i18n/tests.py
===================================================================
--- django/trunk/tests/regressiontests/i18n/tests.py    2010-03-27 15:16:27 UTC 
(rev 12865)
+++ django/trunk/tests/regressiontests/i18n/tests.py    2010-03-27 15:54:31 UTC 
(rev 12866)
@@ -46,7 +46,6 @@
         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):

Added: django/trunk/tests/regressiontests/queryset_pickle/__init__.py
===================================================================

Added: django/trunk/tests/regressiontests/queryset_pickle/models.py
===================================================================
--- django/trunk/tests/regressiontests/queryset_pickle/models.py                
                (rev 0)
+++ django/trunk/tests/regressiontests/queryset_pickle/models.py        
2010-03-27 15:54:31 UTC (rev 12866)
@@ -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)

Added: django/trunk/tests/regressiontests/queryset_pickle/tests.py
===================================================================
--- django/trunk/tests/regressiontests/queryset_pickle/tests.py                 
        (rev 0)
+++ django/trunk/tests/regressiontests/queryset_pickle/tests.py 2010-03-27 
15:54:31 UTC (rev 12866)
@@ -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))

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en.

Reply via email to