Skip to content

Commit 6372ce1

Browse files
Support Q expressions that contain subquery expressions.
Co-authored-by: CelestialGuru <45701317+CelestialGuru@users.noreply.github.com> Co-authored-by: Brian Kohan <bckohan@gmail.com>
1 parent 0d820d0 commit 6372ce1

File tree

5 files changed

+92
-7
lines changed

5 files changed

+92
-7
lines changed

docs/changelog.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
Changelog
22
=========
33

4+
v4.3.0 (202X-XX-XX)
5+
-------------------
6+
* Fixed `Support Q expressions that contain subquery expressions <https://github.com/jazzband/django-polymorphic/pull/572>`_
7+
48
v4.2.0 (2025-12-04)
59
-------------------
610

src/polymorphic/query_translate.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ def tree_node_correct_field_specs(my_model, node):
7070
)
7171
if new_expr:
7272
node.children[i] = new_expr
73-
else:
74-
# this Q object child is another Q object, recursively process this as well
73+
elif isinstance(child, models.Q):
74+
# this Q object child is another Q object, recursively process
7575
tree_node_correct_field_specs(my_model, child)
7676

7777
if isinstance(potential_q_object, models.Q):

src/polymorphic/tests/migrations/0001_initial.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Generated by Django 4.2.26 on 2025-12-03 23:29
1+
# Generated by Django 4.2.26 on 2025-12-04 12:57
22

33
from django.db import migrations, models
44
import django.db.models.deletion
@@ -12,8 +12,8 @@ class Migration(migrations.Migration):
1212
initial = True
1313

1414
dependencies = [
15-
('contenttypes', '0002_remove_content_type_name'),
1615
('auth', '0012_alter_user_first_name_max_length'),
16+
('contenttypes', '0002_remove_content_type_name'),
1717
]
1818

1919
operations = [
@@ -1097,7 +1097,7 @@ class Migration(migrations.Migration):
10971097
fields=[
10981098
('inlinemodela_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.inlinemodela')),
10991099
('field2', models.CharField(max_length=30)),
1100-
('plain_a', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='tests.plaina')),
1100+
('plain_a', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='inline_bs', to='tests.plaina')),
11011101
],
11021102
options={
11031103
'abstract': False,

src/polymorphic/tests/models.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,12 @@ class InlineModelB(InlineModelA):
452452
field2 = models.CharField(max_length=30)
453453

454454
plain_a = models.ForeignKey(
455-
PlainA, null=True, blank=True, default=None, on_delete=models.SET_NULL
455+
PlainA,
456+
null=True,
457+
blank=True,
458+
default=None,
459+
on_delete=models.SET_NULL,
460+
related_name="inline_bs",
456461
)
457462

458463

src/polymorphic/tests/test_orm.py

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from django.contrib.contenttypes.models import ContentType
66
from django.db import models, connection
7-
from django.db.models import Case, Count, FilteredRelation, Q, Sum, When, F
7+
from django.db.models import Case, Count, FilteredRelation, Q, Sum, When, Exists, OuterRef
88
from django.db.utils import IntegrityError, NotSupportedError
99
from django.test import TransactionTestCase
1010
from django.test.utils import CaptureQueriesContext
@@ -25,6 +25,9 @@
2525
CustomPkInherit,
2626
Enhance_Base,
2727
Enhance_Inherit,
28+
InlineParent,
29+
InlineModelA,
30+
InlineModelB,
2831
InitTestModelSubclass,
2932
Model2A,
3033
Model2B,
@@ -1422,3 +1425,76 @@ def test_transmogrify_with_init(self):
14221425
assert pur.color == "blue"
14231426
# issues/615 fixes following line:
14241427
assert pur.home == "Duckburg"
1428+
1429+
def test_subqueries(self):
1430+
PlainA.objects.all().delete()
1431+
InlineParent.objects.all().delete()
1432+
InlineModelA.objects.all().delete()
1433+
InlineModelB.objects.all().delete()
1434+
1435+
pa1 = PlainA.objects.create(field1="plain1")
1436+
PlainA.objects.create(field1="plain2")
1437+
1438+
ip1 = InlineParent.objects.create(title="parent1")
1439+
ip2 = InlineParent.objects.create(title="parent2")
1440+
1441+
ima1 = InlineModelA.objects.create(parent=ip1, field1="ima1")
1442+
ima2 = InlineModelA.objects.create(parent=ip2, field1="ima2")
1443+
imb1 = InlineModelB.objects.create(parent=ip1, field1="imab1", field2="imb1", plain_a=pa1)
1444+
imb2 = InlineModelB.objects.create(parent=ip2, field1="imab2", field2="imb2")
1445+
1446+
results = InlineModelA.objects.filter(
1447+
Exists(PlainA.objects.filter(inline_bs=OuterRef("pk")))
1448+
| Exists(InlineParent.objects.filter(inline_children=OuterRef("pk")))
1449+
)
1450+
1451+
assert ima1 in results
1452+
assert ima2 in results
1453+
assert imb1 in results
1454+
assert imb2 in results
1455+
1456+
results = InlineModelA.objects.filter(
1457+
Exists(PlainA.objects.filter(inline_bs=OuterRef("pk"), field1="plain1"))
1458+
| Exists(InlineParent.objects.filter(inline_children=OuterRef("pk"), title="parent2"))
1459+
)
1460+
1461+
assert ima1 not in results
1462+
assert ima2 in results
1463+
assert imb1 in results
1464+
assert imb2 in results
1465+
1466+
results = InlineModelA.objects.filter(
1467+
Exists(PlainA.objects.filter(inline_bs=OuterRef("pk")))
1468+
)
1469+
1470+
assert ima1 not in results
1471+
assert ima2 not in results
1472+
assert imb1 in results
1473+
assert imb2 not in results
1474+
1475+
results = InlineModelA.objects.filter(
1476+
Exists(PlainA.objects.filter(inline_bs=OuterRef("pk"))), field1="imab1"
1477+
)
1478+
1479+
assert ima1 not in results
1480+
assert ima2 not in results
1481+
assert imb1 in results
1482+
assert imb2 not in results
1483+
1484+
results = InlineModelA.objects.filter(
1485+
Exists(PlainA.objects.filter(inline_bs=OuterRef("pk"))), InlineModelB___field2="imb2"
1486+
)
1487+
1488+
assert not results
1489+
1490+
results = InlineModelA.objects.filter(
1491+
~Exists(PlainA.objects.filter(inline_bs=OuterRef("pk"))), InlineModelB___field2="imb2"
1492+
)
1493+
1494+
assert len(results) == 1
1495+
assert imb2 in results
1496+
1497+
PlainA.objects.all().delete()
1498+
InlineParent.objects.all().delete()
1499+
InlineModelA.objects.all().delete()
1500+
InlineModelB.objects.all().delete()

0 commit comments

Comments
 (0)