Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #19635 -- Made fields pickleable

  • Loading branch information...
commit f403653cf146384946e5c879ad2a351768ebc226 1 parent 3beabb5
Anssi Kääriäinen authored January 19, 2013
40  django/db/models/fields/__init__.py
@@ -10,6 +10,7 @@
10 10
 from itertools import tee
11 11
 
12 12
 from django.db import connection
  13
+from django.db.models.loading import get_model
13 14
 from django.db.models.query_utils import QueryWrapper
14 15
 from django.conf import settings
15 16
 from django import forms
@@ -24,6 +25,9 @@
24 25
 from django.utils.ipv6 import clean_ipv6_address
25 26
 from django.utils import six
26 27
 
  28
+class Empty(object):
  29
+    pass
  30
+
27 31
 class NOT_PROVIDED:
28 32
     pass
29 33
 
@@ -31,6 +35,9 @@ class NOT_PROVIDED:
31 35
 # of most "choices" lists.
32 36
 BLANK_CHOICE_DASH = [("", "---------")]
33 37
 
  38
+def _load_field(app_label, model_name, field_name):
  39
+    return get_model(app_label, model_name)._meta.get_field_by_name(field_name)[0]
  40
+
34 41
 class FieldDoesNotExist(Exception):
35 42
     pass
36 43
 
@@ -49,6 +56,11 @@ class FieldDoesNotExist(Exception):
49 56
 #
50 57
 #     getattr(obj, opts.pk.attname)
51 58
 
  59
+def _empty(of_cls):
  60
+    new = Empty()
  61
+    new.__class__ = of_cls
  62
+    return new
  63
+
52 64
 @total_ordering
53 65
 class Field(object):
54 66
     """Base class for all field types"""
@@ -148,6 +160,34 @@ def __deepcopy__(self, memodict):
148 160
         memodict[id(self)] = obj
149 161
         return obj
150 162
 
  163
+    def __copy__(self):
  164
+        # We need to avoid hitting __reduce__, so define this
  165
+        # slightly weird copy construct.
  166
+        obj = Empty()
  167
+        obj.__class__ = self.__class__
  168
+        obj.__dict__ = self.__dict__.copy()
  169
+        return obj
  170
+
  171
+    def __reduce__(self):
  172
+        """
  173
+        Pickling should return the model._meta.fields instance of the field,
  174
+        not a new copy of that field. So, we use the app cache to load the
  175
+        model and then the field back.
  176
+        """
  177
+        if not hasattr(self, 'model'):
  178
+            # Fields are sometimes used without attaching them to models (for
  179
+            # example in aggregation). In this case give back a plain field
  180
+            # instance. The code below will create a new empty instance of
  181
+            # class self.__class__, then update its dict with self.__dict__
  182
+            # values - so, this is very close to normal pickle.
  183
+            return _empty, (self.__class__,), self.__dict__
  184
+        if self.model._deferred:
  185
+            # Deferred model will not be found from the app cache. This could
  186
+            # be fixed by reconstructing the deferred model on unpickle.
  187
+            raise RuntimeError("Fields of deferred models can't be reduced")
  188
+        return _load_field, (self.model._meta.app_label, self.model._meta.object_name,
  189
+                             self.name)
  190
+
151 191
     def to_python(self, value):
152 192
         """
153 193
         Converts the input value into the expected Python data type, raising
48  django/db/models/sql/query.py
@@ -189,54 +189,6 @@ def __deepcopy__(self, memo):
189 189
         memo[id(self)] = result
190 190
         return result
191 191
 
192  
-    def __getstate__(self):
193  
-        """
194  
-        Pickling support.
195  
-        """
196  
-        obj_dict = self.__dict__.copy()
197  
-        obj_dict['related_select_cols'] = []
198  
-
199  
-        # Fields can't be pickled, so if a field list has been
200  
-        # specified, we pickle the list of field names instead.
201  
-        # None is also a possible value; that can pass as-is
202  
-        obj_dict['select'] = [
203  
-            (s.col, s.field is not None and s.field.name or None)
204  
-            for s in obj_dict['select']
205  
-        ]
206  
-        # alias_map can also contain references to fields.
207  
-        new_alias_map = {}
208  
-        for alias, join_info in obj_dict['alias_map'].items():
209  
-            if join_info.join_field is None:
210  
-                new_alias_map[alias] = join_info
211  
-            else:
212  
-                model = join_info.join_field.model._meta
213  
-                field_id = (model.app_label, model.object_name, join_info.join_field.name)
214  
-                new_alias_map[alias] = join_info._replace(join_field=field_id)
215  
-        obj_dict['alias_map'] = new_alias_map
216  
-        return obj_dict
217  
-
218  
-    def __setstate__(self, obj_dict):
219  
-        """
220  
-        Unpickling support.
221  
-        """
222  
-        # Rebuild list of field instances
223  
-        opts = obj_dict['model']._meta
224  
-        obj_dict['select'] = [
225  
-            SelectInfo(tpl[0], tpl[1] is not None and opts.get_field(tpl[1]) or None)
226  
-            for tpl in obj_dict['select']
227  
-        ]
228  
-        new_alias_map = {}
229  
-        for alias, join_info in obj_dict['alias_map'].items():
230  
-            if join_info.join_field is None:
231  
-                new_alias_map[alias] = join_info
232  
-            else:
233  
-                field_id = join_info.join_field
234  
-                new_alias_map[alias] = join_info._replace(
235  
-                    join_field=get_model(field_id[0], field_id[1])._meta.get_field(field_id[2]))
236  
-        obj_dict['alias_map'] = new_alias_map
237  
-
238  
-        self.__dict__.update(obj_dict)
239  
-
240 192
     def prepare(self):
241 193
         return self
242 194
 
24  django/db/models/sql/where.py
@@ -345,30 +345,6 @@ class Constraint(object):
345 345
     def __init__(self, alias, col, field):
346 346
         self.alias, self.col, self.field = alias, col, field
347 347
 
348  
-    def __getstate__(self):
349  
-        """Save the state of the Constraint for pickling.
350  
-
351  
-        Fields aren't necessarily pickleable, because they can have
352  
-        callable default values. So, instead of pickling the field
353  
-        store a reference so we can restore it manually
354  
-        """
355  
-        obj_dict = self.__dict__.copy()
356  
-        if self.field:
357  
-            obj_dict['model'] = self.field.model
358  
-            obj_dict['field_name'] = self.field.name
359  
-        del obj_dict['field']
360  
-        return obj_dict
361  
-
362  
-    def __setstate__(self, data):
363  
-        """Restore the constraint """
364  
-        model = data.pop('model', None)
365  
-        field_name = data.pop('field_name', None)
366  
-        self.__dict__.update(data)
367  
-        if model is not None:
368  
-            self.field = model._meta.get_field(field_name)
369  
-        else:
370  
-            self.field = None
371  
-
372 348
     def prepare(self, lookup_type, value):
373 349
         if self.field:
374 350
             return self.field.get_prep_lookup(lookup_type, value)

0 notes on commit f403653

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