diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 7a498611648..2c0527d2b74 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -1,5 +1,6 @@ import functools import inspect +import warnings from functools import partial from django import forms @@ -13,6 +14,7 @@ from django.db.models.constants import LOOKUP_SEP from django.db.models.deletion import CASCADE, SET_DEFAULT, SET_NULL from django.db.models.query_utils import PathInfo from django.db.models.utils import make_model_tuple +from django.utils.deprecation import RemovedInDjango60Warning from django.utils.functional import cached_property from django.utils.translation import gettext_lazy as _ @@ -777,12 +779,22 @@ class ForeignObject(RelatedField): return attname, None def get_joining_columns(self, reverse_join=False): + warnings.warn( + "ForeignObject.get_joining_columns() is deprecated. Use " + "get_joining_fields() instead.", + RemovedInDjango60Warning, + ) source = self.reverse_related_fields if reverse_join else self.related_fields return tuple( (lhs_field.column, rhs_field.column) for lhs_field, rhs_field in source ) def get_reverse_joining_columns(self): + warnings.warn( + "ForeignObject.get_reverse_joining_columns() is deprecated. Use " + "get_reverse_joining_fields() instead.", + RemovedInDjango60Warning, + ) return self.get_joining_columns(reverse_join=True) def get_joining_fields(self, reverse_join=False): diff --git a/django/db/models/fields/reverse_related.py b/django/db/models/fields/reverse_related.py index f3da8f8bf29..c74e92ba89f 100644 --- a/django/db/models/fields/reverse_related.py +++ b/django/db/models/fields/reverse_related.py @@ -8,8 +8,10 @@ in the ``remote_field`` attribute of the field. They also act as reverse fields for the purposes of the Meta API because they're the closest concept currently available. """ +import warnings from django.core import exceptions +from django.utils.deprecation import RemovedInDjango60Warning from django.utils.functional import cached_property from django.utils.hashable import make_hashable @@ -193,6 +195,11 @@ class ForeignObjectRel(FieldCacheMixin): return bool(self.related_name) and self.related_name[-1] == "+" def get_joining_columns(self): + warnings.warn( + "ForeignObjectRel.get_joining_columns() is deprecated. Use " + "get_joining_fields() instead.", + RemovedInDjango60Warning, + ) return self.field.get_reverse_joining_columns() def get_joining_fields(self): diff --git a/django/db/models/sql/datastructures.py b/django/db/models/sql/datastructures.py index 46a977188ac..dadd7c063d7 100644 --- a/django/db/models/sql/datastructures.py +++ b/django/db/models/sql/datastructures.py @@ -2,8 +2,11 @@ Useful auxiliary data structures for query construction. Not useful outside the SQL domain. """ +import warnings + from django.core.exceptions import FullResultSet from django.db.models.sql.constants import INNER, LOUTER +from django.utils.deprecation import RemovedInDjango60Warning class MultiJoin(Exception): @@ -68,6 +71,11 @@ class Join: for lhs_field, rhs_field in self.join_fields ) else: + warnings.warn( + "The usage of get_joining_columns() in Join is deprecated. Implement " + "get_joining_fields() instead.", + RemovedInDjango60Warning, + ) self.join_fields = None self.join_cols = join_field.get_joining_columns() # Along which field (or ForeignObjectRel in the reverse join case) @@ -87,9 +95,13 @@ class Join: qn = compiler.quote_name_unless_alias qn2 = connection.ops.quote_name # Add a join condition for each pair of joining columns. + # RemovedInDjango60Warning: when the depraction ends, replace with: + # for lhs, rhs in self.join_field: join_fields = self.join_fields or self.join_cols for lhs, rhs in join_fields: if isinstance(lhs, str): + # RemovedInDjango60Warning: when the depraction ends, remove + # the branch for strings. lhs_full_name = "%s.%s" % (qn(self.parent_alias), qn2(lhs)) rhs_full_name = "%s.%s" % (qn(self.table_alias), qn2(rhs)) else: diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 168a5572c61..bb35bd4805d 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -24,6 +24,14 @@ details on these changes. * ``request`` will be required in the signature of ``ModelAdmin.lookup_allowed()`` subclasses. +* The ``django.db.models.sql.datastructures.Join`` will no longer fallback to + ``get_joining_columns()``. + +* The ``get_joining_columns()`` method of ``ForeignObject`` and + ``ForeignObjectRel`` will be removed. + +* The ``ForeignObject.get_reverse_joining_columns()`` method will be removed. + .. _deprecation-removed-in-5.1: 5.1 diff --git a/docs/releases/5.0.txt b/docs/releases/5.0.txt index 934da1bba91..93f1e72b538 100644 --- a/docs/releases/5.0.txt +++ b/docs/releases/5.0.txt @@ -391,6 +391,14 @@ Miscellaneous Support for ``ModelAdmin`` subclasses that do not accept this argument is deprecated. +* The ``get_joining_columns()`` method of ``ForeignObject`` and + ``ForeignObjectRel`` is deprecated. Starting with Django 6.0, + ``django.db.models.sql.datastructures.Join`` will no longer fallback to + ``get_joining_columns()``. Subclasses should implement + ``get_joining_fields()`` instead. + +* The ``ForeignObject.get_reverse_joining_columns()`` method is deprecated. + Features removed in 5.0 ======================= diff --git a/tests/foreign_object/tests.py b/tests/foreign_object/tests.py index 3c06f07e067..c9e8da57923 100644 --- a/tests/foreign_object/tests.py +++ b/tests/foreign_object/tests.py @@ -8,6 +8,7 @@ from django.db import models from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature from django.test.utils import isolate_apps from django.utils import translation +from django.utils.deprecation import RemovedInDjango60Warning from .models import ( Article, @@ -687,3 +688,49 @@ class TestCachedPathInfo(TestCase): foreign_object_restored = pickle.loads(pickle.dumps(foreign_object)) self.assertIn("path_infos", foreign_object_restored.__dict__) self.assertIn("reverse_path_infos", foreign_object_restored.__dict__) + + +class GetJoiningDeprecationTests(TestCase): + def test_foreign_object_get_joining_columns_warning(self): + msg = ( + "ForeignObject.get_joining_columns() is deprecated. Use " + "get_joining_fields() instead." + ) + with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + Membership.person.field.get_joining_columns() + + def test_foreign_object_get_reverse_joining_columns_warning(self): + msg = ( + "ForeignObject.get_reverse_joining_columns() is deprecated. Use " + "get_reverse_joining_fields() instead." + ) + with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + Membership.person.field.get_reverse_joining_columns() + + def test_foreign_object_rel_get_joining_columns_warning(self): + msg = ( + "ForeignObjectRel.get_joining_columns() is deprecated. Use " + "get_joining_fields() instead." + ) + with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + Membership.person.field.remote_field.get_joining_columns() + + def test_join_get_joining_columns_warning(self): + class CustomForeignKey(models.ForeignKey): + def __getattribute__(self, attr): + if attr == "get_joining_fields": + raise AttributeError + return super().__getattribute__(attr) + + class CustomParent(models.Model): + value = models.CharField(max_length=255) + + class CustomChild(models.Model): + links = CustomForeignKey(CustomParent, models.CASCADE) + + msg = ( + "The usage of get_joining_columns() in Join is deprecated. Implement " + "get_joining_fields() instead." + ) + with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + CustomChild.objects.filter(links__value="value")