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>
This commit is contained in:
CelestialGuru 2024-01-30 20:30:26 -07:00 committed by Brian Kohan
parent 0d820d0641
commit 6372ce1a5b
No known key found for this signature in database
GPG Key ID: 5C6CE8BA38C43FC1
5 changed files with 92 additions and 7 deletions

View File

@ -1,6 +1,10 @@
Changelog
=========
v4.3.0 (202X-XX-XX)
-------------------
* Fixed `Support Q expressions that contain subquery expressions <https://github.com/jazzband/django-polymorphic/pull/572>`_
v4.2.0 (2025-12-04)
-------------------

View File

@ -70,8 +70,8 @@ def translate_polymorphic_Q_object(queryset_model, potential_q_object, using=DEF
)
if new_expr:
node.children[i] = new_expr
else:
# this Q object child is another Q object, recursively process this as well
elif isinstance(child, models.Q):
# this Q object child is another Q object, recursively process
tree_node_correct_field_specs(my_model, child)
if isinstance(potential_q_object, models.Q):

View File

@ -1,4 +1,4 @@
# Generated by Django 4.2.26 on 2025-12-03 23:29
# Generated by Django 4.2.26 on 2025-12-04 12:57
from django.db import migrations, models
import django.db.models.deletion
@ -12,8 +12,8 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('auth', '0012_alter_user_first_name_max_length'),
('contenttypes', '0002_remove_content_type_name'),
]
operations = [
@ -1097,7 +1097,7 @@ class Migration(migrations.Migration):
fields=[
('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')),
('field2', models.CharField(max_length=30)),
('plain_a', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='tests.plaina')),
('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')),
],
options={
'abstract': False,

View File

@ -452,7 +452,12 @@ class InlineModelB(InlineModelA):
field2 = models.CharField(max_length=30)
plain_a = models.ForeignKey(
PlainA, null=True, blank=True, default=None, on_delete=models.SET_NULL
PlainA,
null=True,
blank=True,
default=None,
on_delete=models.SET_NULL,
related_name="inline_bs",
)

View File

@ -4,7 +4,7 @@ import uuid
from django.contrib.contenttypes.models import ContentType
from django.db import models, connection
from django.db.models import Case, Count, FilteredRelation, Q, Sum, When, F
from django.db.models import Case, Count, FilteredRelation, Q, Sum, When, Exists, OuterRef
from django.db.utils import IntegrityError, NotSupportedError
from django.test import TransactionTestCase
from django.test.utils import CaptureQueriesContext
@ -25,6 +25,9 @@ from polymorphic.tests.models import (
CustomPkInherit,
Enhance_Base,
Enhance_Inherit,
InlineParent,
InlineModelA,
InlineModelB,
InitTestModelSubclass,
Model2A,
Model2B,
@ -1422,3 +1425,76 @@ class PolymorphicTests(TransactionTestCase):
assert pur.color == "blue"
# issues/615 fixes following line:
assert pur.home == "Duckburg"
def test_subqueries(self):
PlainA.objects.all().delete()
InlineParent.objects.all().delete()
InlineModelA.objects.all().delete()
InlineModelB.objects.all().delete()
pa1 = PlainA.objects.create(field1="plain1")
PlainA.objects.create(field1="plain2")
ip1 = InlineParent.objects.create(title="parent1")
ip2 = InlineParent.objects.create(title="parent2")
ima1 = InlineModelA.objects.create(parent=ip1, field1="ima1")
ima2 = InlineModelA.objects.create(parent=ip2, field1="ima2")
imb1 = InlineModelB.objects.create(parent=ip1, field1="imab1", field2="imb1", plain_a=pa1)
imb2 = InlineModelB.objects.create(parent=ip2, field1="imab2", field2="imb2")
results = InlineModelA.objects.filter(
Exists(PlainA.objects.filter(inline_bs=OuterRef("pk")))
| Exists(InlineParent.objects.filter(inline_children=OuterRef("pk")))
)
assert ima1 in results
assert ima2 in results
assert imb1 in results
assert imb2 in results
results = InlineModelA.objects.filter(
Exists(PlainA.objects.filter(inline_bs=OuterRef("pk"), field1="plain1"))
| Exists(InlineParent.objects.filter(inline_children=OuterRef("pk"), title="parent2"))
)
assert ima1 not in results
assert ima2 in results
assert imb1 in results
assert imb2 in results
results = InlineModelA.objects.filter(
Exists(PlainA.objects.filter(inline_bs=OuterRef("pk")))
)
assert ima1 not in results
assert ima2 not in results
assert imb1 in results
assert imb2 not in results
results = InlineModelA.objects.filter(
Exists(PlainA.objects.filter(inline_bs=OuterRef("pk"))), field1="imab1"
)
assert ima1 not in results
assert ima2 not in results
assert imb1 in results
assert imb2 not in results
results = InlineModelA.objects.filter(
Exists(PlainA.objects.filter(inline_bs=OuterRef("pk"))), InlineModelB___field2="imb2"
)
assert not results
results = InlineModelA.objects.filter(
~Exists(PlainA.objects.filter(inline_bs=OuterRef("pk"))), InlineModelB___field2="imb2"
)
assert len(results) == 1
assert imb2 in results
PlainA.objects.all().delete()
InlineParent.objects.all().delete()
InlineModelA.objects.all().delete()
InlineModelB.objects.all().delete()