Skip to content

Commit 74d3396

Browse files
authored
Merge pull request jazzband#739 from bckohan/recursion-bug
Fix infinite recursion bug when using only()
2 parents 253abe7 + a3c93f5 commit 74d3396

File tree

5 files changed

+45
-3
lines changed

5 files changed

+45
-3
lines changed

codecov.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
fixes:
2+
- "src/::"

src/polymorphic/query.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,8 @@ def only(self, *fields):
219219
when we're retrieving the real instance (since we'll need to translate
220220
them again, as the model will have changed).
221221
"""
222-
new_fields = [translate_polymorphic_field_path(self.model, a) for a in fields]
222+
new_fields = {translate_polymorphic_field_path(self.model, a) for a in fields}
223+
new_fields.add("polymorphic_ctype_id")
223224
clone = super().only(*new_fields)
224225
clone._polymorphic_add_immediate_loading(fields)
225226
return clone

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Generated by Django 4.2 on 2025-12-14 01:14
1+
# Generated by Django 4.2 on 2025-12-16 21:55
22

33
from django.conf import settings
44
from django.db import migrations, models
@@ -13,8 +13,8 @@ class Migration(migrations.Migration):
1313
initial = True
1414

1515
dependencies = [
16-
('contenttypes', '0002_remove_content_type_name'),
1716
('auth', '0012_alter_user_first_name_max_length'),
17+
('contenttypes', '0002_remove_content_type_name'),
1818
]
1919

2020
operations = [
@@ -893,6 +893,18 @@ class Migration(migrations.Migration):
893893
},
894894
bases=(polymorphic.showfields.ShowFieldType, models.Model),
895895
),
896+
migrations.CreateModel(
897+
name='RecursionBug',
898+
fields=[
899+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
900+
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),
901+
('status', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='recursions', to='tests.plaina')),
902+
],
903+
options={
904+
'abstract': False,
905+
'base_manager_name': 'objects',
906+
},
907+
),
896908
migrations.CreateModel(
897909
name='RankedAthlete',
898910
fields=[

src/polymorphic/tests/models.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -770,3 +770,14 @@ class RankedAthlete(models.Model):
770770
choiceAthlete = models.ForeignKey("ChoiceBlank", on_delete=models.CASCADE)
771771
bet = models.ForeignKey("BetMultiple", on_delete=models.CASCADE)
772772
rank = models.IntegerField()
773+
774+
775+
class RecursionBug(PolymorphicModel):
776+
status = models.ForeignKey(PlainA, on_delete=models.CASCADE, related_name="recursions")
777+
778+
def __init__(self, *args, **kwargs):
779+
"""
780+
https://github.com/jazzband/django-polymorphic/issues/334
781+
"""
782+
super().__init__(*args, **kwargs)
783+
self.old_status_id = self.status_id

src/polymorphic/tests/test_orm.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1929,3 +1929,19 @@ def test_through_model_updates(self):
19291929
assert list(bet.answer.order_by("rankedathlete__rank")) == [a2, a1]
19301930
assert RankedAthlete.objects.get(pk=ra.pk).rank == 99
19311931
assert RankedAthlete.objects.get(pk=ra2.pk).rank == 0
1932+
1933+
def test_infinite_recursion_with_only(self):
1934+
"""
1935+
https://github.com/jazzband/django-polymorphic/issues/334
1936+
"""
1937+
from polymorphic.tests.models import RecursionBug
1938+
1939+
draft = PlainA.objects.create(field1="draft")
1940+
closed = PlainA.objects.create(field1="closed")
1941+
1942+
assert isinstance(closed.recursions, PolymorphicManager)
1943+
1944+
item = RecursionBug.objects.create(status=draft)
1945+
RecursionBug.objects.filter(id=item.id).update(status=closed)
1946+
item.refresh_from_db(fields=("status",))
1947+
assert item.status == closed

0 commit comments

Comments
 (0)