1
0
mirror of https://github.com/django/django.git synced 2025-01-03 15:06:09 +00:00

Refs #34007, Refs #35359 -- Added Q.referenced_based_fields property.

Thank you to Mariusz Felisiak and Natalia Bidart for the reviews.
This commit is contained in:
David Sanders 2024-04-21 16:46:26 +10:00 committed by Sarah Boyce
parent 39828fa778
commit 97d48cd3c6
2 changed files with 41 additions and 0 deletions

View File

@ -175,6 +175,19 @@ class Q(tree.Node):
def __hash__(self): def __hash__(self):
return hash(self.identity) return hash(self.identity)
@cached_property
def referenced_base_fields(self):
"""
Retrieve all base fields referenced directly or through F expressions
excluding any fields referenced through joins.
"""
# Avoid circular imports.
from django.db.models.sql import query
return {
child.split(LOOKUP_SEP, 1)[0] for child in query.get_children_from_q(self)
}
class DeferredAttribute: class DeferredAttribute:
""" """

View File

@ -10,6 +10,7 @@ from django.db.models import (
) )
from django.db.models.expressions import NegatedExpression, RawSQL from django.db.models.expressions import NegatedExpression, RawSQL
from django.db.models.functions import Lower from django.db.models.functions import Lower
from django.db.models.lookups import Exact, IsNull
from django.db.models.sql.where import NothingNode from django.db.models.sql.where import NothingNode
from django.test import SimpleTestCase, TestCase from django.test import SimpleTestCase, TestCase
@ -263,6 +264,33 @@ class QTests(SimpleTestCase):
Q(*items, _connector=connector), Q(*items, _connector=connector),
) )
def test_referenced_base_fields(self):
# Make sure Q.referenced_base_fields retrieves all base fields from
# both filters and F expressions.
tests = [
(Q(field_1=1) & Q(field_2=1), {"field_1", "field_2"}),
(
Q(Exact(F("field_3"), IsNull(F("field_4"), True))),
{"field_3", "field_4"},
),
(Q(Exact(Q(field_5=F("field_6")), True)), {"field_5", "field_6"}),
(Q(field_2=1), {"field_2"}),
(Q(field_7__lookup=True), {"field_7"}),
(Q(field_7__joined_field__lookup=True), {"field_7"}),
]
combined_q = Q(1)
combined_q_base_fields = set()
for q, expected_base_fields in tests:
combined_q &= q
combined_q_base_fields |= expected_base_fields
tests.append((combined_q, combined_q_base_fields))
for q, expected_base_fields in tests:
with self.subTest(q=q):
self.assertEqual(
q.referenced_base_fields,
expected_base_fields,
)
class QCheckTests(TestCase): class QCheckTests(TestCase):
def test_basic(self): def test_basic(self):