diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 6a808e6a51..c210cfabf6 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -10,6 +10,7 @@ from base64 import b64decode, b64encode 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.encoding import smart_text, force_text, force_bytes 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 @@ class Field(object): 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 diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 2fd67ff0ca..8085a6b237 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -189,54 +189,6 @@ class Query(object): 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 diff --git a/django/db/models/sql/where.py b/django/db/models/sql/where.py index ced5325754..754e33292d 100644 --- a/django/db/models/sql/where.py +++ b/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)