Fixed #21414 -- Removed RelatedObject and deprecated Field.related.
This commit is contained in:
parent
6e08bde8c4
commit
f233bf47dd
|
@ -8,7 +8,7 @@ certain test -- e.g. being a DateField or ForeignKey.
|
|||
import datetime
|
||||
|
||||
from django.db import models
|
||||
from django.db.models.fields.related import ManyToManyField
|
||||
from django.db.models.fields.related import ForeignObjectRel, ManyToManyField
|
||||
from django.core.exceptions import ImproperlyConfigured, ValidationError
|
||||
from django.utils.encoding import smart_text, force_text
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
@ -180,7 +180,7 @@ class RelatedFieldListFilter(FieldListFilter):
|
|||
self.title = self.lookup_title
|
||||
|
||||
def has_output(self):
|
||||
if (isinstance(self.field, models.related.RelatedObject) and
|
||||
if (isinstance(self.field, ForeignObjectRel) and
|
||||
self.field.field.null or hasattr(self.field, 'rel') and
|
||||
self.field.null):
|
||||
extra = 1
|
||||
|
@ -210,7 +210,7 @@ class RelatedFieldListFilter(FieldListFilter):
|
|||
}, [self.lookup_kwarg_isnull]),
|
||||
'display': val,
|
||||
}
|
||||
if (isinstance(self.field, models.related.RelatedObject) and
|
||||
if (isinstance(self.field, ForeignObjectRel) and
|
||||
(self.field.field.null or isinstance(self.field.field, ManyToManyField)) or
|
||||
hasattr(self.field, 'rel') and (self.field.null or isinstance(self.field, ManyToManyField))):
|
||||
yield {
|
||||
|
@ -223,7 +223,7 @@ class RelatedFieldListFilter(FieldListFilter):
|
|||
|
||||
FieldListFilter.register(lambda f: (
|
||||
bool(f.rel) if hasattr(f, 'rel') else
|
||||
isinstance(f, models.related.RelatedObject)), RelatedFieldListFilter)
|
||||
isinstance(f, ForeignObjectRel)), RelatedFieldListFilter)
|
||||
|
||||
|
||||
class BooleanFieldListFilter(FieldListFilter):
|
||||
|
|
|
@ -25,8 +25,8 @@ from django.core.paginator import Paginator
|
|||
from django.core.urlresolvers import reverse
|
||||
from django.db import models, transaction, router
|
||||
from django.db.models.constants import LOOKUP_SEP
|
||||
from django.db.models.related import RelatedObject
|
||||
from django.db.models.fields import BLANK_CHOICE_DASH, FieldDoesNotExist
|
||||
from django.db.models.fields.related import ForeignObjectRel
|
||||
from django.db.models.sql.constants import QUERY_TERMS
|
||||
from django.forms.formsets import all_valid, DELETION_FIELD_NAME
|
||||
from django.forms.models import (modelform_factory, modelformset_factory,
|
||||
|
@ -421,7 +421,7 @@ class BaseModelAdmin(six.with_metaclass(forms.MediaDefiningClass)):
|
|||
rel_name = field.rel.get_related_field().name
|
||||
else:
|
||||
rel_name = None
|
||||
elif isinstance(field, RelatedObject):
|
||||
elif isinstance(field, ForeignObjectRel):
|
||||
model = field.model
|
||||
rel_name = model._meta.pk.name
|
||||
else:
|
||||
|
|
|
@ -304,7 +304,7 @@ def label_for_field(name, model, model_admin=None, return_attr=False):
|
|||
try:
|
||||
label = field.verbose_name
|
||||
except AttributeError:
|
||||
# field is likely a RelatedObject
|
||||
# field is likely a ForeignObjectRel
|
||||
label = field.opts.verbose_name
|
||||
except models.FieldDoesNotExist:
|
||||
if name == "__unicode__":
|
||||
|
|
|
@ -9,7 +9,7 @@ from django.db import models, router, transaction, DEFAULT_DB_ALIAS
|
|||
from django.db.models import signals, FieldDoesNotExist, DO_NOTHING
|
||||
from django.db.models.base import ModelBase
|
||||
from django.db.models.fields.related import ForeignObject, ForeignObjectRel
|
||||
from django.db.models.related import PathInfo
|
||||
from django.db.models.query_utils import PathInfo
|
||||
from django.db.models.expressions import Col
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.encoding import smart_text, python_2_unicode_compatible
|
||||
|
@ -27,6 +27,7 @@ class GenericForeignKey(object):
|
|||
self.fk_field = fk_field
|
||||
self.for_concrete_model = for_concrete_model
|
||||
self.editable = False
|
||||
self.rel = None
|
||||
|
||||
def contribute_to_class(self, cls, name, **kwargs):
|
||||
self.name = name
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from operator import attrgetter
|
||||
import warnings
|
||||
|
||||
from django.apps import apps
|
||||
from django.core import checks
|
||||
|
@ -9,13 +10,15 @@ from django.db.backends import utils
|
|||
from django.db.models import signals, Q
|
||||
from django.db.models.deletion import SET_NULL, SET_DEFAULT, CASCADE
|
||||
from django.db.models.fields import (AutoField, Field, IntegerField,
|
||||
PositiveIntegerField, PositiveSmallIntegerField, FieldDoesNotExist)
|
||||
PositiveIntegerField, PositiveSmallIntegerField, FieldDoesNotExist,
|
||||
BLANK_CHOICE_DASH)
|
||||
from django.db.models.lookups import IsNull
|
||||
from django.db.models.related import RelatedObject, PathInfo
|
||||
from django.db.models.query import QuerySet
|
||||
from django.db.models.query_utils import PathInfo
|
||||
from django.db.models.expressions import Col
|
||||
from django.utils.encoding import force_text, smart_text
|
||||
from django.utils import six
|
||||
from django.utils.deprecation import RemovedInDjango20Warning
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.functional import curry, cached_property
|
||||
from django.core import exceptions
|
||||
|
@ -178,7 +181,7 @@ class RelatedField(Field):
|
|||
return []
|
||||
|
||||
try:
|
||||
self.related
|
||||
self.rel
|
||||
except AttributeError:
|
||||
return []
|
||||
|
||||
|
@ -195,13 +198,13 @@ class RelatedField(Field):
|
|||
|
||||
rel_opts = self.rel.to._meta
|
||||
# rel_opts.object_name == "Target"
|
||||
rel_name = self.related.get_accessor_name() # i. e. "model_set"
|
||||
rel_name = self.rel.get_accessor_name() # i. e. "model_set"
|
||||
rel_query_name = self.related_query_name() # i. e. "model"
|
||||
field_name = "%s.%s" % (opts.object_name,
|
||||
self.name) # i. e. "Model.field"
|
||||
|
||||
# Check clashes between accessor or reverse query name of `field`
|
||||
# and any other field name -- i. e. accessor for Model.foreign is
|
||||
# and any other field name -- i.e. accessor for Model.foreign is
|
||||
# model_set and it clashes with Target.model_set.
|
||||
potential_clashes = rel_opts.fields + rel_opts.many_to_many
|
||||
for clash_field in potential_clashes:
|
||||
|
@ -324,11 +327,17 @@ class RelatedField(Field):
|
|||
self.verbose_name = self.rel.to._meta.verbose_name
|
||||
self.rel.set_field_name()
|
||||
|
||||
@property
|
||||
def related(self):
|
||||
warnings.warn(
|
||||
"Usage of field.related has been deprecated. Use field.rel instead.",
|
||||
RemovedInDjango20Warning, 2)
|
||||
return self.rel
|
||||
|
||||
def do_related_class(self, other, cls):
|
||||
self.set_attributes_from_rel()
|
||||
self.related = RelatedObject(other, cls, self)
|
||||
if not cls._meta.abstract:
|
||||
self.contribute_to_related_class(other, self.related)
|
||||
self.contribute_to_related_class(other, self.rel)
|
||||
|
||||
def get_limit_choices_to(self):
|
||||
"""Returns 'limit_choices_to' for this model field.
|
||||
|
@ -554,7 +563,7 @@ class ReverseSingleRelatedObjectDescriptor(object):
|
|||
# Since we're going to assign directly in the cache,
|
||||
# we must manage the reverse relation cache manually.
|
||||
if not self.field.rel.multiple:
|
||||
rel_obj_cache_name = self.field.related.get_cache_name()
|
||||
rel_obj_cache_name = self.field.rel.get_cache_name()
|
||||
for rel_obj in queryset:
|
||||
instance = instances_dict[rel_obj_attr(rel_obj)]
|
||||
setattr(rel_obj, rel_obj_cache_name, instance)
|
||||
|
@ -583,7 +592,7 @@ class ReverseSingleRelatedObjectDescriptor(object):
|
|||
# Assuming the database enforces foreign keys, this won't fail.
|
||||
rel_obj = qs.get()
|
||||
if not self.field.rel.multiple:
|
||||
setattr(rel_obj, self.field.related.get_cache_name(), instance)
|
||||
setattr(rel_obj, self.field.rel.get_cache_name(), instance)
|
||||
setattr(instance, self.cache_name, rel_obj)
|
||||
if rel_obj is None and not self.field.null:
|
||||
raise self.RelatedObjectDoesNotExist(
|
||||
|
@ -635,7 +644,7 @@ class ReverseSingleRelatedObjectDescriptor(object):
|
|||
# cache. This cache also might not exist if the related object
|
||||
# hasn't been accessed yet.
|
||||
if related is not None:
|
||||
setattr(related, self.field.related.get_cache_name(), None)
|
||||
setattr(related, self.field.rel.get_cache_name(), None)
|
||||
|
||||
for lh_field, rh_field in self.field.related_fields:
|
||||
setattr(instance, lh_field.attname, None)
|
||||
|
@ -656,7 +665,7 @@ class ReverseSingleRelatedObjectDescriptor(object):
|
|||
# object you just set.
|
||||
setattr(instance, self.cache_name, value)
|
||||
if value is not None and not self.field.rel.multiple:
|
||||
setattr(value, self.field.related.get_cache_name(), instance)
|
||||
setattr(value, self.field.rel.get_cache_name(), instance)
|
||||
|
||||
|
||||
def create_foreign_related_manager(superclass, rel_field, rel_model):
|
||||
|
@ -1248,13 +1257,6 @@ class ReverseManyRelatedObjectsDescriptor(object):
|
|||
class ForeignObjectRel(object):
|
||||
def __init__(self, field, to, related_name=None, limit_choices_to=None,
|
||||
parent_link=False, on_delete=None, related_query_name=None):
|
||||
try:
|
||||
to._meta
|
||||
except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
|
||||
assert isinstance(to, six.string_types), (
|
||||
"'to' must be either a model, a model name or the string %r" % RECURSIVE_RELATIONSHIP_CONSTANT
|
||||
)
|
||||
|
||||
self.field = field
|
||||
self.to = to
|
||||
self.related_name = related_name
|
||||
|
@ -1263,6 +1265,56 @@ class ForeignObjectRel(object):
|
|||
self.multiple = True
|
||||
self.parent_link = parent_link
|
||||
self.on_delete = on_delete
|
||||
self.symmetrical = False
|
||||
|
||||
# This and the following cached_properties can't be initialized in
|
||||
# __init__ as the field doesn't have its model yet. Calling these methods
|
||||
# before field.contribute_to_class() has been called will result in
|
||||
# AttributeError
|
||||
@cached_property
|
||||
def model(self):
|
||||
if not self.field.model:
|
||||
raise AttributeError(
|
||||
"This property can't be accessed before self.field.contribute_to_class has been called.")
|
||||
return self.field.model
|
||||
|
||||
@cached_property
|
||||
def opts(self):
|
||||
return self.model._meta
|
||||
|
||||
@cached_property
|
||||
def to_opts(self):
|
||||
return self.to._meta
|
||||
|
||||
@cached_property
|
||||
def parent_model(self):
|
||||
return self.to
|
||||
|
||||
@cached_property
|
||||
def name(self):
|
||||
return '%s.%s' % (self.opts.app_label, self.opts.model_name)
|
||||
|
||||
def get_choices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH,
|
||||
limit_to_currently_related=False):
|
||||
"""
|
||||
Returns choices with a default blank choices included, for use as
|
||||
SelectField choices for this field.
|
||||
|
||||
Analog of django.db.models.fields.Field.get_choices(), provided
|
||||
initially for utilization by RelatedFieldListFilter.
|
||||
"""
|
||||
first_choice = blank_choice if include_blank else []
|
||||
queryset = self.model._default_manager.all()
|
||||
if limit_to_currently_related:
|
||||
queryset = queryset.complex_filter(
|
||||
{'%s__isnull' % self.parent_model._meta.model_name: False}
|
||||
)
|
||||
lst = [(x._get_pk_val(), smart_text(x)) for x in queryset]
|
||||
return first_choice + lst
|
||||
|
||||
def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False):
|
||||
# Defer to the actual field definition for db prep
|
||||
return self.field.get_db_prep_lookup(lookup_type, value, connection=connection, prepared=prepared)
|
||||
|
||||
def is_hidden(self):
|
||||
"Should the related object be hidden?"
|
||||
|
@ -1289,6 +1341,34 @@ class ForeignObjectRel(object):
|
|||
return self.field.get_lookup_constraint(constraint_class, alias, targets, sources,
|
||||
lookup_type, raw_value)
|
||||
|
||||
def get_accessor_name(self, model=None):
|
||||
# This method encapsulates the logic that decides what name to give an
|
||||
# accessor descriptor that retrieves related many-to-one or
|
||||
# many-to-many objects. It uses the lower-cased object_name + "_set",
|
||||
# but this can be overridden with the "related_name" option.
|
||||
# Due to backwards compatibility ModelForms need to be able to provide
|
||||
# an alternate model. See BaseInlineFormSet.get_default_prefix().
|
||||
opts = model._meta if model else self.opts
|
||||
model = model or self.model
|
||||
if self.multiple:
|
||||
# If this is a symmetrical m2m relation on self, there is no reverse accessor.
|
||||
if self.symmetrical and model == self.to:
|
||||
return None
|
||||
if self.related_name:
|
||||
return self.related_name
|
||||
if opts.default_related_name:
|
||||
return self.opts.default_related_name % {
|
||||
'model_name': self.opts.model_name.lower(),
|
||||
'app_label': self.opts.app_label.lower(),
|
||||
}
|
||||
return opts.model_name + ('_set' if self.multiple else '')
|
||||
|
||||
def get_cache_name(self):
|
||||
return "_%s_cache" % self.get_accessor_name()
|
||||
|
||||
def get_path_info(self):
|
||||
return self.field.get_reverse_path_info()
|
||||
|
||||
|
||||
class ManyToOneRel(ForeignObjectRel):
|
||||
def __init__(self, field, to, field_name, related_name=None, limit_choices_to=None,
|
||||
|
@ -1322,30 +1402,23 @@ class OneToOneRel(ManyToOneRel):
|
|||
self.multiple = False
|
||||
|
||||
|
||||
class ManyToManyRel(object):
|
||||
def __init__(self, to, related_name=None, limit_choices_to=None,
|
||||
class ManyToManyRel(ForeignObjectRel):
|
||||
def __init__(self, field, to, related_name=None, limit_choices_to=None,
|
||||
symmetrical=True, through=None, through_fields=None,
|
||||
db_constraint=True, related_query_name=None):
|
||||
if through and not db_constraint:
|
||||
raise ValueError("Can't supply a through model and db_constraint=False")
|
||||
if through_fields and not through:
|
||||
raise ValueError("Cannot specify through_fields without a through model")
|
||||
self.to = to
|
||||
self.related_name = related_name
|
||||
self.related_query_name = related_query_name
|
||||
if limit_choices_to is None:
|
||||
limit_choices_to = {}
|
||||
self.limit_choices_to = limit_choices_to
|
||||
super(ManyToManyRel, self).__init__(
|
||||
field, to, related_name=related_name,
|
||||
limit_choices_to=limit_choices_to, related_query_name=related_query_name)
|
||||
self.symmetrical = symmetrical
|
||||
self.multiple = True
|
||||
self.through = through
|
||||
self.through_fields = through_fields
|
||||
self.db_constraint = db_constraint
|
||||
|
||||
def is_hidden(self):
|
||||
"Should the related object be hidden?"
|
||||
return self.related_name and self.related_name[-1] == '+'
|
||||
|
||||
def get_related_field(self):
|
||||
"""
|
||||
Returns the field in the 'to' object to which this relationship is tied.
|
||||
|
@ -1402,7 +1475,7 @@ class ForeignObject(RelatedField):
|
|||
return []
|
||||
|
||||
try:
|
||||
self.related
|
||||
self.rel
|
||||
except AttributeError:
|
||||
return []
|
||||
|
||||
|
@ -1979,7 +2052,8 @@ class ManyToManyField(RelatedField):
|
|||
to = str(to)
|
||||
|
||||
kwargs['verbose_name'] = kwargs.get('verbose_name', None)
|
||||
kwargs['rel'] = ManyToManyRel(to,
|
||||
kwargs['rel'] = ManyToManyRel(
|
||||
self, to,
|
||||
related_name=kwargs.pop('related_name', None),
|
||||
related_query_name=kwargs.pop('related_query_name', None),
|
||||
limit_choices_to=kwargs.pop('limit_choices_to', None),
|
||||
|
|
|
@ -408,7 +408,7 @@ class Options(object):
|
|||
the Field instance for the given name, model is the model containing
|
||||
this field (None for local fields), direct is True if the field exists
|
||||
on this model, and m2m is True for many-to-many relations. When
|
||||
'direct' is False, 'field_object' is the corresponding RelatedObject
|
||||
'direct' is False, 'field_object' is the corresponding ForeignObjectRel
|
||||
for this field (since the field doesn't have an instance associated
|
||||
with it).
|
||||
|
||||
|
@ -456,7 +456,7 @@ class Options(object):
|
|||
for f, model in self.get_fields_with_model():
|
||||
cache[f.name] = cache[f.attname] = (f, model, True, False)
|
||||
for f in self.virtual_fields:
|
||||
if hasattr(f, 'related'):
|
||||
if f.rel:
|
||||
cache[f.name] = cache[f.attname] = (
|
||||
f, None if f.model == self.model else f.model, True, False)
|
||||
if apps.ready:
|
||||
|
@ -508,10 +508,10 @@ class Options(object):
|
|||
if (hasattr(f, 'rel') and f.rel and not isinstance(f.rel.to, six.string_types)
|
||||
and f.generate_reverse_relation):
|
||||
if self == f.rel.to._meta:
|
||||
cache[f.related] = None
|
||||
proxy_cache[f.related] = None
|
||||
cache[f.rel] = None
|
||||
proxy_cache[f.rel] = None
|
||||
elif self.concrete_model == f.rel.to._meta.concrete_model:
|
||||
proxy_cache[f.related] = None
|
||||
proxy_cache[f.rel] = None
|
||||
self._related_objects_cache = cache
|
||||
self._related_objects_proxy_cache = proxy_cache
|
||||
|
||||
|
@ -552,7 +552,7 @@ class Options(object):
|
|||
if (f.rel
|
||||
and not isinstance(f.rel.to, six.string_types)
|
||||
and self == f.rel.to._meta):
|
||||
cache[f.related] = None
|
||||
cache[f.rel] = None
|
||||
if apps.ready:
|
||||
self._related_many_to_many_cache = cache
|
||||
return cache
|
||||
|
|
|
@ -1477,7 +1477,7 @@ def get_cached_row(row, index_start, using, klass_info, offset=0,
|
|||
if f.unique and rel_obj is not None:
|
||||
# If the field is unique, populate the
|
||||
# reverse descriptor cache on the related object
|
||||
setattr(rel_obj, f.related.get_cache_name(), obj)
|
||||
setattr(rel_obj, f.rel.get_cache_name(), obj)
|
||||
|
||||
# Now do the same, but for reverse related objects.
|
||||
# Only handle the restricted case - i.e., don't do a depth
|
||||
|
@ -1497,7 +1497,7 @@ def get_cached_row(row, index_start, using, klass_info, offset=0,
|
|||
rel_obj, index_end = cached_row
|
||||
if obj is not None:
|
||||
# populate the reverse descriptor cache
|
||||
setattr(obj, f.related.get_cache_name(), rel_obj)
|
||||
setattr(obj, f.rel.get_cache_name(), rel_obj)
|
||||
if rel_obj is not None:
|
||||
# If the related object exists, populate
|
||||
# the descriptor cache.
|
||||
|
|
|
@ -7,6 +7,8 @@ circular import difficulties.
|
|||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
from django.apps import apps
|
||||
from django.db.backends import utils
|
||||
from django.db.models.constants import LOOKUP_SEP
|
||||
|
@ -14,6 +16,12 @@ from django.utils import six
|
|||
from django.utils import tree
|
||||
|
||||
|
||||
# PathInfo is used when converting lookups (fk__somecol). The contents
|
||||
# describe the relation in Model terms (model Options and Fields for both
|
||||
# sides of the relation. The join_field is the field backing the relation.
|
||||
PathInfo = namedtuple('PathInfo', 'from_opts to_opts target_fields join_field m2m direct')
|
||||
|
||||
|
||||
class InvalidQuery(Exception):
|
||||
"""
|
||||
The query passed to raw isn't a safe query to use with raw.
|
||||
|
|
|
@ -1,75 +0,0 @@
|
|||
from collections import namedtuple
|
||||
|
||||
from django.utils.encoding import smart_text
|
||||
from django.db.models.fields import BLANK_CHOICE_DASH
|
||||
|
||||
# PathInfo is used when converting lookups (fk__somecol). The contents
|
||||
# describe the relation in Model terms (model Options and Fields for both
|
||||
# sides of the relation. The join_field is the field backing the relation.
|
||||
PathInfo = namedtuple('PathInfo',
|
||||
'from_opts to_opts target_fields join_field '
|
||||
'm2m direct')
|
||||
|
||||
|
||||
class RelatedObject(object):
|
||||
def __init__(self, parent_model, model, field):
|
||||
self.parent_model = parent_model
|
||||
self.model = model
|
||||
self.opts = model._meta
|
||||
self.field = field
|
||||
self.name = '%s:%s' % (self.opts.app_label, self.opts.model_name)
|
||||
self.var_name = self.opts.model_name
|
||||
|
||||
def get_choices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH,
|
||||
limit_to_currently_related=False):
|
||||
"""Returns choices with a default blank choices included, for use
|
||||
as SelectField choices for this field.
|
||||
|
||||
Analogue of django.db.models.fields.Field.get_choices, provided
|
||||
initially for utilization by RelatedFieldListFilter.
|
||||
"""
|
||||
first_choice = blank_choice if include_blank else []
|
||||
queryset = self.model._default_manager.all()
|
||||
if limit_to_currently_related:
|
||||
queryset = queryset.complex_filter(
|
||||
{'%s__isnull' % self.parent_model._meta.model_name: False})
|
||||
lst = [(x._get_pk_val(), smart_text(x)) for x in queryset]
|
||||
return first_choice + lst
|
||||
|
||||
def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False):
|
||||
# Defer to the actual field definition for db prep
|
||||
return self.field.get_db_prep_lookup(lookup_type, value,
|
||||
connection=connection, prepared=prepared)
|
||||
|
||||
def editable_fields(self):
|
||||
"Get the fields in this class that should be edited inline."
|
||||
return [f for f in self.opts.fields + self.opts.many_to_many if f.editable and f != self.field]
|
||||
|
||||
def __repr__(self):
|
||||
return "<RelatedObject: %s related to %s>" % (self.name, self.field.name)
|
||||
|
||||
def get_accessor_name(self):
|
||||
# This method encapsulates the logic that decides what name to give an
|
||||
# accessor descriptor that retrieves related many-to-one or
|
||||
# many-to-many objects. It uses the lower-cased object_name + "_set",
|
||||
# but this can be overridden with the "related_name" option.
|
||||
if self.field.rel.multiple:
|
||||
# If this is a symmetrical m2m relation on self, there is no reverse accessor.
|
||||
if getattr(self.field.rel, 'symmetrical', False) and self.model == self.parent_model:
|
||||
return None
|
||||
if self.field.rel.related_name:
|
||||
return self.field.rel.related_name
|
||||
if self.opts.default_related_name:
|
||||
return self.opts.default_related_name % {
|
||||
'model_name': self.opts.model_name.lower(),
|
||||
'app_label': self.opts.app_label.lower(),
|
||||
}
|
||||
return self.opts.model_name + '_set'
|
||||
else:
|
||||
return self.field.rel.related_name or (self.opts.model_name)
|
||||
|
||||
def get_cache_name(self):
|
||||
return "_%s_cache" % self.get_accessor_name()
|
||||
|
||||
def get_path_info(self):
|
||||
return self.field.get_reverse_path_info()
|
|
@ -55,7 +55,7 @@ class Join(object):
|
|||
# A list of 2-tuples to use in the ON clause of the JOIN.
|
||||
# Each 2-tuple will create one join condition in the ON clause.
|
||||
self.join_cols = join_field.get_joining_columns()
|
||||
# Along which field (or RelatedObject in the reverse join case)
|
||||
# Along which field (or ForeignObjectRel in the reverse join case)
|
||||
self.join_field = join_field
|
||||
# Is this join nullabled?
|
||||
self.nullable = nullable
|
||||
|
|
|
@ -13,12 +13,11 @@ import warnings
|
|||
|
||||
from django.core.exceptions import FieldError
|
||||
from django.db import connections, DEFAULT_DB_ALIAS
|
||||
from django.db.models.aggregates import Count
|
||||
from django.db.models.constants import LOOKUP_SEP
|
||||
from django.db.models.expressions import Col, Ref
|
||||
from django.db.models.fields import FieldDoesNotExist
|
||||
from django.db.models.query_utils import Q, refs_aggregate
|
||||
from django.db.models.related import PathInfo
|
||||
from django.db.models.aggregates import Count
|
||||
from django.db.models.query_utils import PathInfo, Q, refs_aggregate
|
||||
from django.db.models.sql.constants import (QUERY_TERMS, ORDER_DIR, SINGLE,
|
||||
ORDER_PATTERN, SelectInfo, INNER, LOUTER)
|
||||
from django.db.models.sql.datastructures import (
|
||||
|
|
|
@ -883,8 +883,7 @@ class BaseInlineFormSet(BaseModelFormSet):
|
|||
|
||||
@classmethod
|
||||
def get_default_prefix(cls):
|
||||
from django.db.models.fields.related import RelatedObject
|
||||
return RelatedObject(cls.fk.rel.to, cls.model, cls.fk).get_accessor_name().replace('+', '')
|
||||
return cls.fk.rel.get_accessor_name(model=cls.model).replace('+', '')
|
||||
|
||||
def save_new(self, form, commit=True):
|
||||
# Use commit=False so we can assign the parent key afterwards, then
|
||||
|
|
|
@ -99,6 +99,8 @@ details on these changes.
|
|||
``'django.contrib.auth.middleware.SessionAuthenticationMiddleware'`` is in
|
||||
``MIDDLEWARE_CLASSES``.
|
||||
|
||||
* Private attribute ``django.db.models.Field.related`` will be removed.
|
||||
|
||||
.. _deprecation-removed-in-1.9:
|
||||
|
||||
1.9
|
||||
|
|
|
@ -1226,6 +1226,16 @@ to your ``MIDDLEWARE_CLASSES`` sometime before then to opt-in. Please read the
|
|||
``django.contrib.flatpages.sitemaps.FlatPageSitemap``. The old import location
|
||||
is deprecated and will be removed in Django 1.9.
|
||||
|
||||
Model ``Field.related``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Private attribute ``django.db.models.Field.related`` is deprecated in favor
|
||||
of ``Field.rel``. The latter is an instance of
|
||||
``django.db.models.fields.related.ForeignObjectRel`` which replaces
|
||||
``django.db.models.related.RelatedObject``. The ``django.db.models.related``
|
||||
module has been removed and the ``Field.related`` attribute will be removed in
|
||||
Django 2.0.
|
||||
|
||||
.. removed-features-1.8:
|
||||
|
||||
Features removed in 1.8
|
||||
|
|
|
@ -2,11 +2,13 @@ from __future__ import unicode_literals
|
|||
|
||||
from datetime import datetime, timedelta
|
||||
import threading
|
||||
import warnings
|
||||
|
||||
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
|
||||
from django.db import connections, DEFAULT_DB_ALIAS
|
||||
from django.db import DatabaseError
|
||||
from django.db.models.fields import Field
|
||||
from django.db.models.fields.related import ForeignObjectRel
|
||||
from django.db.models.manager import BaseManager
|
||||
from django.db.models.query import QuerySet, EmptyQuerySet, ValuesListQuerySet, MAX_GET_RESULTS
|
||||
from django.test import TestCase, TransactionTestCase, skipIfDBFeature, skipUnlessDBFeature
|
||||
|
@ -770,3 +772,16 @@ class ModelRefreshTests(TestCase):
|
|||
a = Article.objects.create(pub_date=self._truncate_ms(datetime.now()))
|
||||
with self.assertNumQueries(0):
|
||||
a.refresh_from_db(fields=[])
|
||||
|
||||
|
||||
class TestRelatedObjectDeprecation(TestCase):
|
||||
def test_field_related_deprecation(self):
|
||||
field = SelfRef._meta.get_field_by_name('selfref')[0]
|
||||
with warnings.catch_warnings(record=True) as warns:
|
||||
warnings.simplefilter('always')
|
||||
self.assertIsInstance(field.related, ForeignObjectRel)
|
||||
self.assertEqual(len(warns), 1)
|
||||
self.assertEqual(
|
||||
str(warns.pop().message),
|
||||
'Usage of field.related has been deprecated. Use field.rel instead.'
|
||||
)
|
||||
|
|
|
@ -233,114 +233,114 @@ TEST_RESULTS = {
|
|||
},
|
||||
'get_all_related_objects_with_model_hidden': {
|
||||
BasePerson: (
|
||||
('model_meta:baseperson_friends_base', None),
|
||||
('model_meta:baseperson_friends_base', None),
|
||||
('model_meta:baseperson_m2m_base', None),
|
||||
('model_meta:baseperson_following_base', None),
|
||||
('model_meta:baseperson_following_base', None),
|
||||
('model_meta:baseperson_m2m_abstract', None),
|
||||
('model_meta:baseperson_friends_abstract', None),
|
||||
('model_meta:baseperson_friends_abstract', None),
|
||||
('model_meta:baseperson_following_abstract', None),
|
||||
('model_meta:baseperson_following_abstract', None),
|
||||
('model_meta:person', None),
|
||||
('model_meta:relating_basepeople', None),
|
||||
('model_meta:relating_basepeople_hidden', None),
|
||||
('model_meta:relating', None),
|
||||
('model_meta:relating', None),
|
||||
('model_meta.baseperson_friends_base', None),
|
||||
('model_meta.baseperson_friends_base', None),
|
||||
('model_meta.baseperson_m2m_base', None),
|
||||
('model_meta.baseperson_following_base', None),
|
||||
('model_meta.baseperson_following_base', None),
|
||||
('model_meta.baseperson_m2m_abstract', None),
|
||||
('model_meta.baseperson_friends_abstract', None),
|
||||
('model_meta.baseperson_friends_abstract', None),
|
||||
('model_meta.baseperson_following_abstract', None),
|
||||
('model_meta.baseperson_following_abstract', None),
|
||||
('model_meta.person', None),
|
||||
('model_meta.relating_basepeople', None),
|
||||
('model_meta.relating_basepeople_hidden', None),
|
||||
('model_meta.relating', None),
|
||||
('model_meta.relating', None),
|
||||
),
|
||||
Person: (
|
||||
('model_meta:baseperson_friends_base', BasePerson),
|
||||
('model_meta:baseperson_friends_base', BasePerson),
|
||||
('model_meta:baseperson_m2m_base', BasePerson),
|
||||
('model_meta:baseperson_following_base', BasePerson),
|
||||
('model_meta:baseperson_following_base', BasePerson),
|
||||
('model_meta:baseperson_m2m_abstract', BasePerson),
|
||||
('model_meta:baseperson_friends_abstract', BasePerson),
|
||||
('model_meta:baseperson_friends_abstract', BasePerson),
|
||||
('model_meta:baseperson_following_abstract', BasePerson),
|
||||
('model_meta:baseperson_following_abstract', BasePerson),
|
||||
('model_meta:relating_basepeople', BasePerson),
|
||||
('model_meta:relating_basepeople_hidden', BasePerson),
|
||||
('model_meta:relating', BasePerson),
|
||||
('model_meta:relating', BasePerson),
|
||||
('model_meta:person_m2m_inherited', None),
|
||||
('model_meta:person_friends_inherited', None),
|
||||
('model_meta:person_friends_inherited', None),
|
||||
('model_meta:person_following_inherited', None),
|
||||
('model_meta:person_following_inherited', None),
|
||||
('model_meta:relating_people', None),
|
||||
('model_meta:relating_people_hidden', None),
|
||||
('model_meta:relating', None),
|
||||
('model_meta:relating', None),
|
||||
('model_meta.baseperson_friends_base', BasePerson),
|
||||
('model_meta.baseperson_friends_base', BasePerson),
|
||||
('model_meta.baseperson_m2m_base', BasePerson),
|
||||
('model_meta.baseperson_following_base', BasePerson),
|
||||
('model_meta.baseperson_following_base', BasePerson),
|
||||
('model_meta.baseperson_m2m_abstract', BasePerson),
|
||||
('model_meta.baseperson_friends_abstract', BasePerson),
|
||||
('model_meta.baseperson_friends_abstract', BasePerson),
|
||||
('model_meta.baseperson_following_abstract', BasePerson),
|
||||
('model_meta.baseperson_following_abstract', BasePerson),
|
||||
('model_meta.relating_basepeople', BasePerson),
|
||||
('model_meta.relating_basepeople_hidden', BasePerson),
|
||||
('model_meta.relating', BasePerson),
|
||||
('model_meta.relating', BasePerson),
|
||||
('model_meta.person_m2m_inherited', None),
|
||||
('model_meta.person_friends_inherited', None),
|
||||
('model_meta.person_friends_inherited', None),
|
||||
('model_meta.person_following_inherited', None),
|
||||
('model_meta.person_following_inherited', None),
|
||||
('model_meta.relating_people', None),
|
||||
('model_meta.relating_people_hidden', None),
|
||||
('model_meta.relating', None),
|
||||
('model_meta.relating', None),
|
||||
),
|
||||
Relation: (
|
||||
('model_meta:baseperson_m2m_base', None),
|
||||
('model_meta:baseperson_m2m_abstract', None),
|
||||
('model_meta:baseperson', None),
|
||||
('model_meta:baseperson', None),
|
||||
('model_meta:baseperson', None),
|
||||
('model_meta:baseperson', None),
|
||||
('model_meta:baseperson', None),
|
||||
('model_meta:baseperson', None),
|
||||
('model_meta:person_m2m_inherited', None),
|
||||
('model_meta:person', None),
|
||||
('model_meta:person', None),
|
||||
('model_meta:person', None),
|
||||
('model_meta:person', None),
|
||||
('model_meta:person', None),
|
||||
('model_meta:proxyperson', None),
|
||||
('model_meta:proxyperson', None),
|
||||
('model_meta:proxyperson', None),
|
||||
('model_meta.baseperson_m2m_base', None),
|
||||
('model_meta.baseperson_m2m_abstract', None),
|
||||
('model_meta.baseperson', None),
|
||||
('model_meta.baseperson', None),
|
||||
('model_meta.baseperson', None),
|
||||
('model_meta.baseperson', None),
|
||||
('model_meta.baseperson', None),
|
||||
('model_meta.baseperson', None),
|
||||
('model_meta.person_m2m_inherited', None),
|
||||
('model_meta.person', None),
|
||||
('model_meta.person', None),
|
||||
('model_meta.person', None),
|
||||
('model_meta.person', None),
|
||||
('model_meta.person', None),
|
||||
('model_meta.proxyperson', None),
|
||||
('model_meta.proxyperson', None),
|
||||
('model_meta.proxyperson', None),
|
||||
),
|
||||
},
|
||||
'get_all_related_objects_with_model_hidden_local': {
|
||||
BasePerson: (
|
||||
('model_meta:baseperson_friends_base', None),
|
||||
('model_meta:baseperson_friends_base', None),
|
||||
('model_meta:baseperson_m2m_base', None),
|
||||
('model_meta:baseperson_following_base', None),
|
||||
('model_meta:baseperson_following_base', None),
|
||||
('model_meta:baseperson_m2m_abstract', None),
|
||||
('model_meta:baseperson_friends_abstract', None),
|
||||
('model_meta:baseperson_friends_abstract', None),
|
||||
('model_meta:baseperson_following_abstract', None),
|
||||
('model_meta:baseperson_following_abstract', None),
|
||||
('model_meta:person', None),
|
||||
('model_meta:relating_basepeople', None),
|
||||
('model_meta:relating_basepeople_hidden', None),
|
||||
('model_meta:relating', None),
|
||||
('model_meta:relating', None),
|
||||
('model_meta.baseperson_friends_base', None),
|
||||
('model_meta.baseperson_friends_base', None),
|
||||
('model_meta.baseperson_m2m_base', None),
|
||||
('model_meta.baseperson_following_base', None),
|
||||
('model_meta.baseperson_following_base', None),
|
||||
('model_meta.baseperson_m2m_abstract', None),
|
||||
('model_meta.baseperson_friends_abstract', None),
|
||||
('model_meta.baseperson_friends_abstract', None),
|
||||
('model_meta.baseperson_following_abstract', None),
|
||||
('model_meta.baseperson_following_abstract', None),
|
||||
('model_meta.person', None),
|
||||
('model_meta.relating_basepeople', None),
|
||||
('model_meta.relating_basepeople_hidden', None),
|
||||
('model_meta.relating', None),
|
||||
('model_meta.relating', None),
|
||||
),
|
||||
Person: (
|
||||
('model_meta:person_m2m_inherited', None),
|
||||
('model_meta:person_friends_inherited', None),
|
||||
('model_meta:person_friends_inherited', None),
|
||||
('model_meta:person_following_inherited', None),
|
||||
('model_meta:person_following_inherited', None),
|
||||
('model_meta:relating_people', None),
|
||||
('model_meta:relating_people_hidden', None),
|
||||
('model_meta:relating', None),
|
||||
('model_meta:relating', None),
|
||||
('model_meta.person_m2m_inherited', None),
|
||||
('model_meta.person_friends_inherited', None),
|
||||
('model_meta.person_friends_inherited', None),
|
||||
('model_meta.person_following_inherited', None),
|
||||
('model_meta.person_following_inherited', None),
|
||||
('model_meta.relating_people', None),
|
||||
('model_meta.relating_people_hidden', None),
|
||||
('model_meta.relating', None),
|
||||
('model_meta.relating', None),
|
||||
),
|
||||
Relation: (
|
||||
('model_meta:baseperson_m2m_base', None),
|
||||
('model_meta:baseperson_m2m_abstract', None),
|
||||
('model_meta:baseperson', None),
|
||||
('model_meta:baseperson', None),
|
||||
('model_meta:baseperson', None),
|
||||
('model_meta:baseperson', None),
|
||||
('model_meta:baseperson', None),
|
||||
('model_meta:baseperson', None),
|
||||
('model_meta:person_m2m_inherited', None),
|
||||
('model_meta:person', None),
|
||||
('model_meta:person', None),
|
||||
('model_meta:person', None),
|
||||
('model_meta:person', None),
|
||||
('model_meta:person', None),
|
||||
('model_meta:proxyperson', None),
|
||||
('model_meta:proxyperson', None),
|
||||
('model_meta:proxyperson', None),
|
||||
('model_meta.baseperson_m2m_base', None),
|
||||
('model_meta.baseperson_m2m_abstract', None),
|
||||
('model_meta.baseperson', None),
|
||||
('model_meta.baseperson', None),
|
||||
('model_meta.baseperson', None),
|
||||
('model_meta.baseperson', None),
|
||||
('model_meta.baseperson', None),
|
||||
('model_meta.baseperson', None),
|
||||
('model_meta.person_m2m_inherited', None),
|
||||
('model_meta.person', None),
|
||||
('model_meta.person', None),
|
||||
('model_meta.person', None),
|
||||
('model_meta.person', None),
|
||||
('model_meta.person', None),
|
||||
('model_meta.proxyperson', None),
|
||||
('model_meta.proxyperson', None),
|
||||
('model_meta.proxyperson', None),
|
||||
),
|
||||
},
|
||||
'get_all_related_objects_with_model_proxy': {
|
||||
|
@ -360,67 +360,67 @@ TEST_RESULTS = {
|
|||
},
|
||||
'get_all_related_objects_with_model_proxy_hidden': {
|
||||
BasePerson: (
|
||||
('model_meta:baseperson_friends_base', None),
|
||||
('model_meta:baseperson_friends_base', None),
|
||||
('model_meta:baseperson_m2m_base', None),
|
||||
('model_meta:baseperson_following_base', None),
|
||||
('model_meta:baseperson_following_base', None),
|
||||
('model_meta:baseperson_m2m_abstract', None),
|
||||
('model_meta:baseperson_friends_abstract', None),
|
||||
('model_meta:baseperson_friends_abstract', None),
|
||||
('model_meta:baseperson_following_abstract', None),
|
||||
('model_meta:baseperson_following_abstract', None),
|
||||
('model_meta:person', None),
|
||||
('model_meta:relating_basepeople', None),
|
||||
('model_meta:relating_basepeople_hidden', None),
|
||||
('model_meta:relating', None),
|
||||
('model_meta:relating', None),
|
||||
('model_meta.baseperson_friends_base', None),
|
||||
('model_meta.baseperson_friends_base', None),
|
||||
('model_meta.baseperson_m2m_base', None),
|
||||
('model_meta.baseperson_following_base', None),
|
||||
('model_meta.baseperson_following_base', None),
|
||||
('model_meta.baseperson_m2m_abstract', None),
|
||||
('model_meta.baseperson_friends_abstract', None),
|
||||
('model_meta.baseperson_friends_abstract', None),
|
||||
('model_meta.baseperson_following_abstract', None),
|
||||
('model_meta.baseperson_following_abstract', None),
|
||||
('model_meta.person', None),
|
||||
('model_meta.relating_basepeople', None),
|
||||
('model_meta.relating_basepeople_hidden', None),
|
||||
('model_meta.relating', None),
|
||||
('model_meta.relating', None),
|
||||
),
|
||||
Person: (
|
||||
('model_meta:baseperson_friends_base', BasePerson),
|
||||
('model_meta:baseperson_friends_base', BasePerson),
|
||||
('model_meta:baseperson_m2m_base', BasePerson),
|
||||
('model_meta:baseperson_following_base', BasePerson),
|
||||
('model_meta:baseperson_following_base', BasePerson),
|
||||
('model_meta:baseperson_m2m_abstract', BasePerson),
|
||||
('model_meta:baseperson_friends_abstract', BasePerson),
|
||||
('model_meta:baseperson_friends_abstract', BasePerson),
|
||||
('model_meta:baseperson_following_abstract', BasePerson),
|
||||
('model_meta:baseperson_following_abstract', BasePerson),
|
||||
('model_meta:relating_basepeople', BasePerson),
|
||||
('model_meta:relating_basepeople_hidden', BasePerson),
|
||||
('model_meta:relating', BasePerson),
|
||||
('model_meta:relating', BasePerson),
|
||||
('model_meta:person_m2m_inherited', None),
|
||||
('model_meta:person_friends_inherited', None),
|
||||
('model_meta:person_friends_inherited', None),
|
||||
('model_meta:person_following_inherited', None),
|
||||
('model_meta:person_following_inherited', None),
|
||||
('model_meta:relating_people', None),
|
||||
('model_meta:relating_people_hidden', None),
|
||||
('model_meta:relating', None),
|
||||
('model_meta:relating', None),
|
||||
('model_meta:relating', None),
|
||||
('model_meta:relating', None),
|
||||
('model_meta.baseperson_friends_base', BasePerson),
|
||||
('model_meta.baseperson_friends_base', BasePerson),
|
||||
('model_meta.baseperson_m2m_base', BasePerson),
|
||||
('model_meta.baseperson_following_base', BasePerson),
|
||||
('model_meta.baseperson_following_base', BasePerson),
|
||||
('model_meta.baseperson_m2m_abstract', BasePerson),
|
||||
('model_meta.baseperson_friends_abstract', BasePerson),
|
||||
('model_meta.baseperson_friends_abstract', BasePerson),
|
||||
('model_meta.baseperson_following_abstract', BasePerson),
|
||||
('model_meta.baseperson_following_abstract', BasePerson),
|
||||
('model_meta.relating_basepeople', BasePerson),
|
||||
('model_meta.relating_basepeople_hidden', BasePerson),
|
||||
('model_meta.relating', BasePerson),
|
||||
('model_meta.relating', BasePerson),
|
||||
('model_meta.person_m2m_inherited', None),
|
||||
('model_meta.person_friends_inherited', None),
|
||||
('model_meta.person_friends_inherited', None),
|
||||
('model_meta.person_following_inherited', None),
|
||||
('model_meta.person_following_inherited', None),
|
||||
('model_meta.relating_people', None),
|
||||
('model_meta.relating_people_hidden', None),
|
||||
('model_meta.relating', None),
|
||||
('model_meta.relating', None),
|
||||
('model_meta.relating', None),
|
||||
('model_meta.relating', None),
|
||||
),
|
||||
Relation: (
|
||||
('model_meta:baseperson_m2m_base', None),
|
||||
('model_meta:baseperson_m2m_abstract', None),
|
||||
('model_meta:baseperson', None),
|
||||
('model_meta:baseperson', None),
|
||||
('model_meta:baseperson', None),
|
||||
('model_meta:baseperson', None),
|
||||
('model_meta:baseperson', None),
|
||||
('model_meta:baseperson', None),
|
||||
('model_meta:person_m2m_inherited', None),
|
||||
('model_meta:person', None),
|
||||
('model_meta:person', None),
|
||||
('model_meta:person', None),
|
||||
('model_meta:person', None),
|
||||
('model_meta:person', None),
|
||||
('model_meta:proxyperson', None),
|
||||
('model_meta:proxyperson', None),
|
||||
('model_meta:proxyperson', None),
|
||||
('model_meta.baseperson_m2m_base', None),
|
||||
('model_meta.baseperson_m2m_abstract', None),
|
||||
('model_meta.baseperson', None),
|
||||
('model_meta.baseperson', None),
|
||||
('model_meta.baseperson', None),
|
||||
('model_meta.baseperson', None),
|
||||
('model_meta.baseperson', None),
|
||||
('model_meta.baseperson', None),
|
||||
('model_meta.person_m2m_inherited', None),
|
||||
('model_meta.person', None),
|
||||
('model_meta.person', None),
|
||||
('model_meta.person', None),
|
||||
('model_meta.person', None),
|
||||
('model_meta.person', None),
|
||||
('model_meta.proxyperson', None),
|
||||
('model_meta.proxyperson', None),
|
||||
('model_meta.proxyperson', None),
|
||||
),
|
||||
},
|
||||
'get_all_related_many_to_many_with_model': {
|
||||
|
@ -643,12 +643,12 @@ class GetFieldByNameTests(OptionsBaseTests):
|
|||
def test_get_related_object(self):
|
||||
field_info = Person._meta.get_field_by_name('relating_baseperson')
|
||||
self.assertEqual(field_info[1:], (BasePerson, False, False))
|
||||
self.assertIsInstance(field_info[0], related.RelatedObject)
|
||||
self.assertIsInstance(field_info[0], related.ForeignObjectRel)
|
||||
|
||||
def test_get_related_m2m(self):
|
||||
field_info = Person._meta.get_field_by_name('relating_people')
|
||||
self.assertEqual(field_info[1:], (None, False, True))
|
||||
self.assertIsInstance(field_info[0], related.RelatedObject)
|
||||
self.assertIsInstance(field_info[0], related.ForeignObjectRel)
|
||||
|
||||
def test_get_generic_foreign_key(self):
|
||||
# For historic reasons generic foreign keys aren't available.
|
||||
|
|
|
@ -383,7 +383,7 @@ class OneToOneTests(TestCase):
|
|||
be added to the related model.
|
||||
"""
|
||||
self.assertFalse(
|
||||
hasattr(Target, HiddenPointer._meta.get_field('target').related.get_accessor_name())
|
||||
hasattr(Target, HiddenPointer._meta.get_field('target').rel.get_accessor_name())
|
||||
)
|
||||
|
||||
def test_related_object(self):
|
||||
|
|
Loading…
Reference in New Issue