Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Fixed #19635 -- Made fields pickleable

  • Loading branch information...
commit f403653cf146384946e5c879ad2a351768ebc226 1 parent 3beabb5
@akaariai akaariai authored
View
40 django/db/models/fields/__init__.py
@@ -10,6 +10,7 @@
from itertools import tee
from django.db import connection
+from django.db.models.loading import get_model
from django.db.models.query_utils import QueryWrapper
from django.conf import settings
from django import forms
@@ -24,6 +25,9 @@
from django.utils.ipv6 import clean_ipv6_address
from django.utils import six
+class Empty(object):
+ pass
+
class NOT_PROVIDED:
pass
@@ -31,6 +35,9 @@ class NOT_PROVIDED:
# of most "choices" lists.
BLANK_CHOICE_DASH = [("", "---------")]
+def _load_field(app_label, model_name, field_name):
+ return get_model(app_label, model_name)._meta.get_field_by_name(field_name)[0]
+
class FieldDoesNotExist(Exception):
pass
@@ -49,6 +56,11 @@ class FieldDoesNotExist(Exception):
#
# getattr(obj, opts.pk.attname)
+def _empty(of_cls):
+ new = Empty()
+ new.__class__ = of_cls
+ return new
+
@total_ordering
class Field(object):
"""Base class for all field types"""
@@ -148,6 +160,34 @@ def __deepcopy__(self, memodict):
memodict[id(self)] = obj
return obj
+ def __copy__(self):
+ # We need to avoid hitting __reduce__, so define this
+ # slightly weird copy construct.
+ obj = Empty()
+ obj.__class__ = self.__class__
+ obj.__dict__ = self.__dict__.copy()
+ return obj
+
+ def __reduce__(self):
+ """
+ Pickling should return the model._meta.fields instance of the field,
+ not a new copy of that field. So, we use the app cache to load the
+ model and then the field back.
+ """
+ if not hasattr(self, 'model'):
+ # Fields are sometimes used without attaching them to models (for
+ # example in aggregation). In this case give back a plain field
+ # instance. The code below will create a new empty instance of
+ # class self.__class__, then update its dict with self.__dict__
+ # values - so, this is very close to normal pickle.
+ return _empty, (self.__class__,), self.__dict__
+ if self.model._deferred:
+ # Deferred model will not be found from the app cache. This could
+ # be fixed by reconstructing the deferred model on unpickle.
+ raise RuntimeError("Fields of deferred models can't be reduced")
+ return _load_field, (self.model._meta.app_label, self.model._meta.object_name,
+ self.name)
+
def to_python(self, value):
"""
Converts the input value into the expected Python data type, raising
View
48 django/db/models/sql/query.py
@@ -189,54 +189,6 @@ def __deepcopy__(self, memo):
memo[id(self)] = result
return result
- def __getstate__(self):
- """
- Pickling support.
- """
- obj_dict = self.__dict__.copy()
- obj_dict['related_select_cols'] = []
-
- # Fields can't be pickled, so if a field list has been
- # specified, we pickle the list of field names instead.
- # None is also a possible value; that can pass as-is
- obj_dict['select'] = [
- (s.col, s.field is not None and s.field.name or None)
- for s in obj_dict['select']
- ]
- # alias_map can also contain references to fields.
- new_alias_map = {}
- for alias, join_info in obj_dict['alias_map'].items():
- if join_info.join_field is None:
- new_alias_map[alias] = join_info
- else:
- model = join_info.join_field.model._meta
- field_id = (model.app_label, model.object_name, join_info.join_field.name)
- new_alias_map[alias] = join_info._replace(join_field=field_id)
- obj_dict['alias_map'] = new_alias_map
- return obj_dict
-
- def __setstate__(self, obj_dict):
- """
- Unpickling support.
- """
- # Rebuild list of field instances
- opts = obj_dict['model']._meta
- obj_dict['select'] = [
- SelectInfo(tpl[0], tpl[1] is not None and opts.get_field(tpl[1]) or None)
- for tpl in obj_dict['select']
- ]
- new_alias_map = {}
- for alias, join_info in obj_dict['alias_map'].items():
- if join_info.join_field is None:
- new_alias_map[alias] = join_info
- else:
- field_id = join_info.join_field
- new_alias_map[alias] = join_info._replace(
- join_field=get_model(field_id[0], field_id[1])._meta.get_field(field_id[2]))
- obj_dict['alias_map'] = new_alias_map
-
- self.__dict__.update(obj_dict)
-
def prepare(self):
return self
View
24 django/db/models/sql/where.py
@@ -345,30 +345,6 @@ class Constraint(object):
def __init__(self, alias, col, field):
self.alias, self.col, self.field = alias, col, field
- def __getstate__(self):
- """Save the state of the Constraint for pickling.
-
- Fields aren't necessarily pickleable, because they can have
- callable default values. So, instead of pickling the field
- store a reference so we can restore it manually
- """
- obj_dict = self.__dict__.copy()
- if self.field:
- obj_dict['model'] = self.field.model
- obj_dict['field_name'] = self.field.name
- del obj_dict['field']
- return obj_dict
-
- def __setstate__(self, data):
- """Restore the constraint """
- model = data.pop('model', None)
- field_name = data.pop('field_name', None)
- self.__dict__.update(data)
- if model is not None:
- self.field = model._meta.get_field(field_name)
- else:
- self.field = None
-
def prepare(self, lookup_type, value):
if self.field:
return self.field.get_prep_lookup(lookup_type, value)

0 comments on commit f403653

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