Refs #12663 -- Removed deprecated Model._meta methods.
This commit is contained in:
parent
08ab262649
commit
c64dd646f5
|
@ -1,6 +1,5 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import warnings
|
|
||||||
from bisect import bisect
|
from bisect import bisect
|
||||||
from collections import OrderedDict, defaultdict
|
from collections import OrderedDict, defaultdict
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
@ -11,15 +10,12 @@ from django.core.exceptions import FieldDoesNotExist
|
||||||
from django.db import connections
|
from django.db import connections
|
||||||
from django.db.models.fields import AutoField
|
from django.db.models.fields import AutoField
|
||||||
from django.db.models.fields.proxy import OrderWrt
|
from django.db.models.fields.proxy import OrderWrt
|
||||||
from django.db.models.fields.related import ManyToManyField
|
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.utils.datastructures import ImmutableList, OrderedSet
|
from django.utils.datastructures import ImmutableList, OrderedSet
|
||||||
from django.utils.deprecation import RemovedInDjango110Warning
|
|
||||||
from django.utils.encoding import (
|
from django.utils.encoding import (
|
||||||
force_text, python_2_unicode_compatible, smart_text,
|
force_text, python_2_unicode_compatible, smart_text,
|
||||||
)
|
)
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.lru_cache import lru_cache
|
|
||||||
from django.utils.text import camel_case_to_spaces
|
from django.utils.text import camel_case_to_spaces
|
||||||
from django.utils.translation import override, string_concat
|
from django.utils.translation import override, string_concat
|
||||||
|
|
||||||
|
@ -41,24 +37,6 @@ DEFAULT_NAMES = ('verbose_name', 'verbose_name_plural', 'db_table', 'ordering',
|
||||||
'required_db_features', 'required_db_vendor')
|
'required_db_features', 'required_db_vendor')
|
||||||
|
|
||||||
|
|
||||||
class raise_deprecation(object):
|
|
||||||
def __init__(self, suggested_alternative):
|
|
||||||
self.suggested_alternative = suggested_alternative
|
|
||||||
|
|
||||||
def __call__(self, fn):
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
warnings.warn(
|
|
||||||
"'%s is an unofficial API that has been deprecated. "
|
|
||||||
"You may be able to replace it with '%s'" % (
|
|
||||||
fn.__name__,
|
|
||||||
self.suggested_alternative,
|
|
||||||
),
|
|
||||||
RemovedInDjango110Warning, stacklevel=2
|
|
||||||
)
|
|
||||||
return fn(*args, **kwargs)
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
def normalize_together(option_together):
|
def normalize_together(option_together):
|
||||||
"""
|
"""
|
||||||
option_together can be either a tuple of tuples, or a single
|
option_together can be either a tuple of tuples, or a single
|
||||||
|
@ -151,31 +129,6 @@ class Options(object):
|
||||||
|
|
||||||
self.default_related_name = None
|
self.default_related_name = None
|
||||||
|
|
||||||
@lru_cache(maxsize=None)
|
|
||||||
def _map_model(self, link):
|
|
||||||
# This helper function is used to allow backwards compatibility with
|
|
||||||
# the previous API. No future methods should use this function.
|
|
||||||
# It maps a field to (field, model or related_model,) depending on the
|
|
||||||
# field type.
|
|
||||||
model = link.model._meta.concrete_model
|
|
||||||
if model is self.model:
|
|
||||||
model = None
|
|
||||||
return link, model
|
|
||||||
|
|
||||||
@lru_cache(maxsize=None)
|
|
||||||
def _map_model_details(self, link):
|
|
||||||
# This helper function is used to allow backwards compatibility with
|
|
||||||
# the previous API. No future methods should use this function.
|
|
||||||
# This function maps a field to a tuple of:
|
|
||||||
# (field, model or related_model, direct, is_m2m) depending on the
|
|
||||||
# field type.
|
|
||||||
direct = not link.auto_created or link.concrete
|
|
||||||
model = link.model._meta.concrete_model
|
|
||||||
if model is self.model:
|
|
||||||
model = None
|
|
||||||
m2m = link.is_relation and link.many_to_many
|
|
||||||
return link, model, direct, m2m
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def label(self):
|
def label(self):
|
||||||
return '%s.%s' % (self.app_label, self.object_name)
|
return '%s.%s' % (self.app_label, self.object_name)
|
||||||
|
@ -455,14 +408,6 @@ class Options(object):
|
||||||
"local_concrete_fields", (f for f in self.local_fields if f.concrete)
|
"local_concrete_fields", (f for f in self.local_fields if f.concrete)
|
||||||
)
|
)
|
||||||
|
|
||||||
@raise_deprecation(suggested_alternative="get_fields()")
|
|
||||||
def get_fields_with_model(self):
|
|
||||||
return [self._map_model(f) for f in self.get_fields()]
|
|
||||||
|
|
||||||
@raise_deprecation(suggested_alternative="get_fields()")
|
|
||||||
def get_concrete_fields_with_model(self):
|
|
||||||
return [self._map_model(f) for f in self.concrete_fields]
|
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def many_to_many(self):
|
def many_to_many(self):
|
||||||
"""
|
"""
|
||||||
|
@ -496,10 +441,6 @@ class Options(object):
|
||||||
if not obj.hidden or obj.field.many_to_many)
|
if not obj.hidden or obj.field.many_to_many)
|
||||||
)
|
)
|
||||||
|
|
||||||
@raise_deprecation(suggested_alternative="get_fields()")
|
|
||||||
def get_m2m_with_model(self):
|
|
||||||
return [self._map_model(f) for f in self.many_to_many]
|
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def _forward_fields_map(self):
|
def _forward_fields_map(self):
|
||||||
res = {}
|
res = {}
|
||||||
|
@ -530,36 +471,14 @@ class Options(object):
|
||||||
pass
|
pass
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def get_field(self, field_name, many_to_many=None):
|
def get_field(self, field_name):
|
||||||
"""
|
"""
|
||||||
Returns a field instance given a field name. The field can be either a
|
Return a field instance given the name of a forward or reverse field.
|
||||||
forward or reverse field, unless many_to_many is specified; if it is,
|
|
||||||
only forward fields will be returned.
|
|
||||||
|
|
||||||
The many_to_many argument exists for backwards compatibility reasons;
|
|
||||||
it has been deprecated and will be removed in Django 1.10.
|
|
||||||
"""
|
"""
|
||||||
m2m_in_kwargs = many_to_many is not None
|
|
||||||
if m2m_in_kwargs:
|
|
||||||
# Always throw a warning if many_to_many is used regardless of
|
|
||||||
# whether it alters the return type or not.
|
|
||||||
warnings.warn(
|
|
||||||
"The 'many_to_many' argument on get_field() is deprecated; "
|
|
||||||
"use a filter on field.many_to_many instead.",
|
|
||||||
RemovedInDjango110Warning
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# In order to avoid premature loading of the relation tree
|
# In order to avoid premature loading of the relation tree
|
||||||
# (expensive) we prefer checking if the field is a forward field.
|
# (expensive) we prefer checking if the field is a forward field.
|
||||||
field = self._forward_fields_map[field_name]
|
return self._forward_fields_map[field_name]
|
||||||
|
|
||||||
if many_to_many is False and field.many_to_many:
|
|
||||||
raise FieldDoesNotExist(
|
|
||||||
'%s has no field named %r' % (self.object_name, field_name)
|
|
||||||
)
|
|
||||||
|
|
||||||
return field
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# If the app registry is not ready, reverse fields are
|
# If the app registry is not ready, reverse fields are
|
||||||
# unavailable, therefore we throw a FieldDoesNotExist exception.
|
# unavailable, therefore we throw a FieldDoesNotExist exception.
|
||||||
|
@ -571,84 +490,12 @@ class Options(object):
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if m2m_in_kwargs:
|
|
||||||
# Previous API does not allow searching reverse fields.
|
|
||||||
raise FieldDoesNotExist('%s has no field named %r' % (self.object_name, field_name))
|
|
||||||
|
|
||||||
# Retrieve field instance by name from cached or just-computed
|
# Retrieve field instance by name from cached or just-computed
|
||||||
# field map.
|
# field map.
|
||||||
return self.fields_map[field_name]
|
return self.fields_map[field_name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise FieldDoesNotExist('%s has no field named %r' % (self.object_name, field_name))
|
raise FieldDoesNotExist('%s has no field named %r' % (self.object_name, field_name))
|
||||||
|
|
||||||
@raise_deprecation(suggested_alternative="get_field()")
|
|
||||||
def get_field_by_name(self, name):
|
|
||||||
return self._map_model_details(self.get_field(name))
|
|
||||||
|
|
||||||
@raise_deprecation(suggested_alternative="get_fields()")
|
|
||||||
def get_all_field_names(self):
|
|
||||||
names = set()
|
|
||||||
fields = self.get_fields()
|
|
||||||
for field in fields:
|
|
||||||
# For backwards compatibility GenericForeignKey should not be
|
|
||||||
# included in the results.
|
|
||||||
if field.is_relation and field.many_to_one and field.related_model is None:
|
|
||||||
continue
|
|
||||||
# Relations to child proxy models should not be included.
|
|
||||||
if (field.model != self.model and
|
|
||||||
field.model._meta.concrete_model == self.concrete_model):
|
|
||||||
continue
|
|
||||||
|
|
||||||
names.add(field.name)
|
|
||||||
if hasattr(field, 'attname'):
|
|
||||||
names.add(field.attname)
|
|
||||||
return list(names)
|
|
||||||
|
|
||||||
@raise_deprecation(suggested_alternative="get_fields()")
|
|
||||||
def get_all_related_objects(self, local_only=False, include_hidden=False,
|
|
||||||
include_proxy_eq=False):
|
|
||||||
|
|
||||||
include_parents = True if local_only is False else PROXY_PARENTS
|
|
||||||
fields = self._get_fields(
|
|
||||||
forward=False, reverse=True,
|
|
||||||
include_parents=include_parents,
|
|
||||||
include_hidden=include_hidden,
|
|
||||||
)
|
|
||||||
fields = (obj for obj in fields if not isinstance(obj.field, ManyToManyField))
|
|
||||||
if include_proxy_eq:
|
|
||||||
children = chain.from_iterable(c._relation_tree
|
|
||||||
for c in self.concrete_model._meta.proxied_children
|
|
||||||
if c is not self)
|
|
||||||
relations = (f.remote_field for f in children
|
|
||||||
if include_hidden or not f.remote_field.field.remote_field.is_hidden())
|
|
||||||
fields = chain(fields, relations)
|
|
||||||
return list(fields)
|
|
||||||
|
|
||||||
@raise_deprecation(suggested_alternative="get_fields()")
|
|
||||||
def get_all_related_objects_with_model(self, local_only=False, include_hidden=False,
|
|
||||||
include_proxy_eq=False):
|
|
||||||
return [
|
|
||||||
self._map_model(f) for f in self.get_all_related_objects(
|
|
||||||
local_only=local_only,
|
|
||||||
include_hidden=include_hidden,
|
|
||||||
include_proxy_eq=include_proxy_eq,
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
@raise_deprecation(suggested_alternative="get_fields()")
|
|
||||||
def get_all_related_many_to_many_objects(self, local_only=False):
|
|
||||||
include_parents = True if local_only is not True else PROXY_PARENTS
|
|
||||||
fields = self._get_fields(
|
|
||||||
forward=False, reverse=True,
|
|
||||||
include_parents=include_parents, include_hidden=True
|
|
||||||
)
|
|
||||||
return [obj for obj in fields if isinstance(obj.field, ManyToManyField)]
|
|
||||||
|
|
||||||
@raise_deprecation(suggested_alternative="get_fields()")
|
|
||||||
def get_all_related_m2m_objects_with_model(self):
|
|
||||||
fields = self._get_fields(forward=False, reverse=True, include_hidden=True)
|
|
||||||
return [self._map_model(obj) for obj in fields if isinstance(obj.field, ManyToManyField)]
|
|
||||||
|
|
||||||
def get_base_chain(self, model):
|
def get_base_chain(self, model):
|
||||||
"""
|
"""
|
||||||
Return a list of parent classes leading to `model` (ordered from
|
Return a list of parent classes leading to `model` (ordered from
|
||||||
|
|
|
@ -70,18 +70,6 @@ Retrieving a single field instance of a model by name
|
||||||
...
|
...
|
||||||
FieldDoesNotExist: User has no field named 'does_not_exist'
|
FieldDoesNotExist: User has no field named 'does_not_exist'
|
||||||
|
|
||||||
.. deprecated:: 1.8
|
|
||||||
|
|
||||||
:meth:`Options.get_field()` previously accepted a ``many_to_many``
|
|
||||||
parameter which could be set to ``False`` to avoid searching
|
|
||||||
``ManyToManyField``\s. The old behavior has been preserved for
|
|
||||||
backwards compatibility; however, the parameter and this behavior
|
|
||||||
has been deprecated.
|
|
||||||
|
|
||||||
If you wish to filter out ``ManyToManyField``\s, you can inspect the
|
|
||||||
:attr:`Field.many_to_many <django.db.models.Field.many_to_many>`
|
|
||||||
attribute after calling ``get_field()``.
|
|
||||||
|
|
||||||
Retrieving all field instances of a model
|
Retrieving all field instances of a model
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -1,166 +0,0 @@
|
||||||
import warnings
|
|
||||||
|
|
||||||
from django import test
|
|
||||||
from django.contrib.contenttypes.fields import GenericRelation
|
|
||||||
from django.core.exceptions import FieldDoesNotExist
|
|
||||||
from django.db.models.fields import CharField, related
|
|
||||||
from django.utils.deprecation import RemovedInDjango110Warning
|
|
||||||
|
|
||||||
from .models import BasePerson, Person
|
|
||||||
from .results import TEST_RESULTS
|
|
||||||
|
|
||||||
|
|
||||||
class OptionsBaseTests(test.SimpleTestCase):
|
|
||||||
|
|
||||||
def _map_related_query_names(self, res):
|
|
||||||
return tuple((o.field.related_query_name(), m) for o, m in res)
|
|
||||||
|
|
||||||
def _map_names(self, res):
|
|
||||||
return tuple((f.name, m) for f, m in res)
|
|
||||||
|
|
||||||
|
|
||||||
class M2MTests(OptionsBaseTests):
|
|
||||||
|
|
||||||
def test_many_to_many_with_model(self):
|
|
||||||
for model, expected_result in TEST_RESULTS['many_to_many_with_model'].items():
|
|
||||||
with warnings.catch_warnings(record=True) as warning:
|
|
||||||
warnings.simplefilter("always")
|
|
||||||
models = [model for field, model in model._meta.get_m2m_with_model()]
|
|
||||||
self.assertEqual([RemovedInDjango110Warning], [w.message.__class__ for w in warning])
|
|
||||||
self.assertEqual(models, expected_result)
|
|
||||||
|
|
||||||
|
|
||||||
@test.ignore_warnings(category=RemovedInDjango110Warning)
|
|
||||||
class RelatedObjectsTests(OptionsBaseTests):
|
|
||||||
key_name = lambda self, r: r[0]
|
|
||||||
|
|
||||||
def test_related_objects(self):
|
|
||||||
result_key = 'get_all_related_objects_with_model_legacy'
|
|
||||||
for model, expected in TEST_RESULTS[result_key].items():
|
|
||||||
objects = model._meta.get_all_related_objects_with_model()
|
|
||||||
self.assertEqual(self._map_related_query_names(objects), expected)
|
|
||||||
|
|
||||||
def test_related_objects_local(self):
|
|
||||||
result_key = 'get_all_related_objects_with_model_local_legacy'
|
|
||||||
for model, expected in TEST_RESULTS[result_key].items():
|
|
||||||
objects = model._meta.get_all_related_objects_with_model(local_only=True)
|
|
||||||
self.assertEqual(self._map_related_query_names(objects), expected)
|
|
||||||
|
|
||||||
def test_related_objects_include_hidden(self):
|
|
||||||
result_key = 'get_all_related_objects_with_model_hidden_legacy'
|
|
||||||
for model, expected in TEST_RESULTS[result_key].items():
|
|
||||||
objects = model._meta.get_all_related_objects_with_model(include_hidden=True)
|
|
||||||
self.assertEqual(
|
|
||||||
sorted(self._map_names(objects), key=self.key_name),
|
|
||||||
sorted(expected, key=self.key_name)
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_related_objects_include_hidden_local_only(self):
|
|
||||||
result_key = 'get_all_related_objects_with_model_hidden_local_legacy'
|
|
||||||
for model, expected in TEST_RESULTS[result_key].items():
|
|
||||||
objects = model._meta.get_all_related_objects_with_model(
|
|
||||||
include_hidden=True, local_only=True)
|
|
||||||
self.assertEqual(
|
|
||||||
sorted(self._map_names(objects), key=self.key_name),
|
|
||||||
sorted(expected, key=self.key_name)
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_related_objects_proxy(self):
|
|
||||||
result_key = 'get_all_related_objects_with_model_proxy_legacy'
|
|
||||||
for model, expected in TEST_RESULTS[result_key].items():
|
|
||||||
objects = model._meta.get_all_related_objects_with_model(
|
|
||||||
include_proxy_eq=True)
|
|
||||||
self.assertEqual(self._map_related_query_names(objects), expected)
|
|
||||||
|
|
||||||
def test_related_objects_proxy_hidden(self):
|
|
||||||
result_key = 'get_all_related_objects_with_model_proxy_hidden_legacy'
|
|
||||||
for model, expected in TEST_RESULTS[result_key].items():
|
|
||||||
objects = model._meta.get_all_related_objects_with_model(
|
|
||||||
include_proxy_eq=True, include_hidden=True)
|
|
||||||
self.assertEqual(
|
|
||||||
sorted(self._map_names(objects), key=self.key_name),
|
|
||||||
sorted(expected, key=self.key_name)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@test.ignore_warnings(category=RemovedInDjango110Warning)
|
|
||||||
class RelatedM2MTests(OptionsBaseTests):
|
|
||||||
|
|
||||||
def test_related_m2m_with_model(self):
|
|
||||||
result_key = 'get_all_related_many_to_many_with_model_legacy'
|
|
||||||
for model, expected in TEST_RESULTS[result_key].items():
|
|
||||||
objects = model._meta.get_all_related_m2m_objects_with_model()
|
|
||||||
self.assertEqual(self._map_related_query_names(objects), expected)
|
|
||||||
|
|
||||||
def test_related_m2m_local_only(self):
|
|
||||||
result_key = 'get_all_related_many_to_many_local_legacy'
|
|
||||||
for model, expected in TEST_RESULTS[result_key].items():
|
|
||||||
objects = model._meta.get_all_related_many_to_many_objects(local_only=True)
|
|
||||||
self.assertEqual([o.field.related_query_name() for o in objects], expected)
|
|
||||||
|
|
||||||
def test_related_m2m_asymmetrical(self):
|
|
||||||
m2m = Person._meta.many_to_many
|
|
||||||
self.assertTrue('following_base' in [f.attname for f in m2m])
|
|
||||||
related_m2m = Person._meta.get_all_related_many_to_many_objects()
|
|
||||||
self.assertTrue('followers_base' in [o.field.related_query_name() for o in related_m2m])
|
|
||||||
|
|
||||||
def test_related_m2m_symmetrical(self):
|
|
||||||
m2m = Person._meta.many_to_many
|
|
||||||
self.assertTrue('friends_base' in [f.attname for f in m2m])
|
|
||||||
related_m2m = Person._meta.get_all_related_many_to_many_objects()
|
|
||||||
self.assertIn('friends_inherited_rel_+', [o.field.related_query_name() for o in related_m2m])
|
|
||||||
|
|
||||||
|
|
||||||
@test.ignore_warnings(category=RemovedInDjango110Warning)
|
|
||||||
class GetFieldByNameTests(OptionsBaseTests):
|
|
||||||
|
|
||||||
def test_get_data_field(self):
|
|
||||||
field_info = Person._meta.get_field_by_name('data_abstract')
|
|
||||||
self.assertEqual(field_info[1:], (BasePerson, True, False))
|
|
||||||
self.assertIsInstance(field_info[0], CharField)
|
|
||||||
|
|
||||||
def test_get_m2m_field(self):
|
|
||||||
field_info = Person._meta.get_field_by_name('m2m_base')
|
|
||||||
self.assertEqual(field_info[1:], (BasePerson, True, True))
|
|
||||||
self.assertIsInstance(field_info[0], related.ManyToManyField)
|
|
||||||
|
|
||||||
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.assertTrue(field_info[0].auto_created)
|
|
||||||
|
|
||||||
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.assertTrue(field_info[0].auto_created)
|
|
||||||
|
|
||||||
def test_get_generic_relation(self):
|
|
||||||
field_info = Person._meta.get_field_by_name('generic_relation_base')
|
|
||||||
self.assertEqual(field_info[1:], (None, True, False))
|
|
||||||
self.assertIsInstance(field_info[0], GenericRelation)
|
|
||||||
|
|
||||||
def test_get_m2m_field_invalid(self):
|
|
||||||
with warnings.catch_warnings(record=True) as warning:
|
|
||||||
warnings.simplefilter("always")
|
|
||||||
self.assertRaises(
|
|
||||||
FieldDoesNotExist,
|
|
||||||
Person._meta.get_field,
|
|
||||||
**{'field_name': 'm2m_base', 'many_to_many': False}
|
|
||||||
)
|
|
||||||
self.assertEqual(Person._meta.get_field('m2m_base', many_to_many=True).name, 'm2m_base')
|
|
||||||
|
|
||||||
# 2 RemovedInDjango110Warning messages should be raised, one for each call of get_field()
|
|
||||||
# with the 'many_to_many' argument.
|
|
||||||
self.assertEqual(
|
|
||||||
[RemovedInDjango110Warning, RemovedInDjango110Warning],
|
|
||||||
[w.message.__class__ for w in warning]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@test.ignore_warnings(category=RemovedInDjango110Warning)
|
|
||||||
class GetAllFieldNamesTestCase(OptionsBaseTests):
|
|
||||||
|
|
||||||
def test_get_all_field_names(self):
|
|
||||||
for model, expected_names in TEST_RESULTS['get_all_field_names'].items():
|
|
||||||
objects = model._meta.get_all_field_names()
|
|
||||||
self.assertEqual(sorted(map(str, objects)), sorted(expected_names))
|
|
Loading…
Reference in New Issue