Fixed #19635 -- Made fields pickleable
This commit is contained in:
parent
3beabb5afd
commit
f403653cf1
|
@ -10,6 +10,7 @@ from base64 import b64decode, b64encode
|
||||||
from itertools import tee
|
from itertools import tee
|
||||||
|
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
|
from django.db.models.loading import get_model
|
||||||
from django.db.models.query_utils import QueryWrapper
|
from django.db.models.query_utils import QueryWrapper
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django import forms
|
from django import forms
|
||||||
|
@ -24,6 +25,9 @@ from django.utils.encoding import smart_text, force_text, force_bytes
|
||||||
from django.utils.ipv6 import clean_ipv6_address
|
from django.utils.ipv6 import clean_ipv6_address
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
|
||||||
|
class Empty(object):
|
||||||
|
pass
|
||||||
|
|
||||||
class NOT_PROVIDED:
|
class NOT_PROVIDED:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -31,6 +35,9 @@ class NOT_PROVIDED:
|
||||||
# of most "choices" lists.
|
# of most "choices" lists.
|
||||||
BLANK_CHOICE_DASH = [("", "---------")]
|
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):
|
class FieldDoesNotExist(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -49,6 +56,11 @@ class FieldDoesNotExist(Exception):
|
||||||
#
|
#
|
||||||
# getattr(obj, opts.pk.attname)
|
# getattr(obj, opts.pk.attname)
|
||||||
|
|
||||||
|
def _empty(of_cls):
|
||||||
|
new = Empty()
|
||||||
|
new.__class__ = of_cls
|
||||||
|
return new
|
||||||
|
|
||||||
@total_ordering
|
@total_ordering
|
||||||
class Field(object):
|
class Field(object):
|
||||||
"""Base class for all field types"""
|
"""Base class for all field types"""
|
||||||
|
@ -148,6 +160,34 @@ class Field(object):
|
||||||
memodict[id(self)] = obj
|
memodict[id(self)] = obj
|
||||||
return 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):
|
def to_python(self, value):
|
||||||
"""
|
"""
|
||||||
Converts the input value into the expected Python data type, raising
|
Converts the input value into the expected Python data type, raising
|
||||||
|
|
|
@ -189,54 +189,6 @@ class Query(object):
|
||||||
memo[id(self)] = result
|
memo[id(self)] = result
|
||||||
return 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):
|
def prepare(self):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
|
@ -345,30 +345,6 @@ class Constraint(object):
|
||||||
def __init__(self, alias, col, field):
|
def __init__(self, alias, col, field):
|
||||||
self.alias, self.col, self.field = 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):
|
def prepare(self, lookup_type, value):
|
||||||
if self.field:
|
if self.field:
|
||||||
return self.field.get_prep_lookup(lookup_type, value)
|
return self.field.get_prep_lookup(lookup_type, value)
|
||||||
|
|
Loading…
Reference in New Issue