diff --git a/django/db/models/options.py b/django/db/models/options.py index bc5a95e750..28c2088630 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -1,6 +1,5 @@ from __future__ import unicode_literals -import warnings from bisect import bisect from collections import OrderedDict, defaultdict from itertools import chain @@ -11,15 +10,12 @@ from django.core.exceptions import FieldDoesNotExist from django.db import connections from django.db.models.fields import AutoField from django.db.models.fields.proxy import OrderWrt -from django.db.models.fields.related import ManyToManyField from django.utils import six from django.utils.datastructures import ImmutableList, OrderedSet -from django.utils.deprecation import RemovedInDjango110Warning from django.utils.encoding import ( force_text, python_2_unicode_compatible, smart_text, ) 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.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') -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): """ option_together can be either a tuple of tuples, or a single @@ -151,31 +129,6 @@ class Options(object): 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 def label(self): 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) ) - @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 def many_to_many(self): """ @@ -496,10 +441,6 @@ class Options(object): 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 def _forward_fields_map(self): res = {} @@ -530,36 +471,14 @@ class Options(object): pass 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 - 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. + Return a field instance given the name of a forward or reverse field. """ - 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: # In order to avoid premature loading of the relation tree # (expensive) we prefer checking if the field is a forward field. - field = 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 + return self._forward_fields_map[field_name] except KeyError: # If the app registry is not ready, reverse fields are # unavailable, therefore we throw a FieldDoesNotExist exception. @@ -571,84 +490,12 @@ class Options(object): ) 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 # field map. return self.fields_map[field_name] except KeyError: 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): """ Return a list of parent classes leading to `model` (ordered from diff --git a/docs/ref/models/meta.txt b/docs/ref/models/meta.txt index d8f711b810..d1ae35ff3e 100644 --- a/docs/ref/models/meta.txt +++ b/docs/ref/models/meta.txt @@ -70,18 +70,6 @@ Retrieving a single field instance of a model by name ... 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 ` - attribute after calling ``get_field()``. - Retrieving all field instances of a model ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/model_meta/test_legacy.py b/tests/model_meta/test_legacy.py deleted file mode 100644 index d93b5fc020..0000000000 --- a/tests/model_meta/test_legacy.py +++ /dev/null @@ -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))