diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index 467d13a..281963f 100644
a
|
b
|
class Field(object):
|
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) |
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index 8329792..9235a67 100644
a
|
b
|
signals.class_prepared.connect(do_pending_lookups)
|
87 | 87 | class RelatedField(object): |
88 | 88 | def contribute_to_class(self, cls, name): |
89 | 89 | sup = super(RelatedField, self) |
90 | | |
91 | | # Add an accessor to allow easy determination of the related query path for this field |
92 | | self.related_query_name = curry(self._get_related_query_name, cls._meta) |
| 90 | |
| 91 | # Store the opts for related_query_name() |
| 92 | self.opts = cls._meta |
93 | 93 | |
94 | 94 | if hasattr(sup, 'contribute_to_class'): |
95 | 95 | sup.contribute_to_class(cls, name) |
… |
… |
class RelatedField(object):
|
174 | 174 | return [] |
175 | 175 | raise TypeError("Related Field has invalid lookup: %s" % lookup_type) |
176 | 176 | |
177 | | def _get_related_query_name(self, opts): |
| 177 | def related_query_name(self): |
178 | 178 | # This method defines the name that can be used to identify this |
179 | 179 | # related object in a table-spanning query. It uses the lower-cased |
180 | 180 | # object_name by default, but this can be overridden with the |
181 | 181 | # "related_name" option. |
182 | | return self.rel.related_name or opts.object_name.lower() |
| 182 | return self.rel.related_name or self.opts.object_name.lower() |
183 | 183 | |
184 | 184 | class SingleRelatedObjectDescriptor(object): |
185 | 185 | # This class provides the functionality that makes the related-object |
diff --git a/django/utils/functional.py b/django/utils/functional.py
index e52ab76..4539497 100644
a
|
b
|
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 |
… |
… |
def lazy(func, *resultclasses):
|
161 | 167 | self.__kw = 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: |
diff --git a/django/utils/translation/__init__.py b/django/utils/translation/__init__.py
index c0a0df9..54fb86d 100644
a
|
b
|
|
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', |
… |
… |
__all__ = ['gettext', 'gettext_noop', 'gettext_lazy', 'ngettext',
|
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): |
… |
… |
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) |
diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py
index 31150a6..941f66f 100644
a
|
b
|
class TranslationTests(TestCase):
|
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): |
diff --git a/tests/regressiontests/queryset_pickle/__init__.py b/tests/regressiontests/queryset_pickle/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/regressiontests/queryset_pickle/models.py b/tests/regressiontests/queryset_pickle/models.py
new file mode 100644
index 0000000..1c7dc4f
-
|
+
|
|
| 1 | from django.db import models |
| 2 | |
| 3 | |
| 4 | class Group(models.Model): |
| 5 | name = models.CharField(max_length=100) |
| 6 | |
| 7 | class Event(models.Model): |
| 8 | group = models.ForeignKey(Group) |
diff --git a/tests/regressiontests/queryset_pickle/tests.py b/tests/regressiontests/queryset_pickle/tests.py
new file mode 100644
index 0000000..cc0d0a6
-
|
+
|
|
| 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)) |