Refs #34634 -- Fixed creating diamond-shaped MTI objects with ancestors inherited from different paths.

Co-authored-by: Simon Charette <charette.s@gmail.com>
This commit is contained in:
Akash Kumar Sen 2023-06-15 18:08:30 +05:30 committed by Mariusz Felisiak
parent 82a588a6bc
commit 1754c2c802
3 changed files with 38 additions and 15 deletions

View File

@ -864,7 +864,7 @@ class Options:
reverse=True,
include_parents=True,
include_hidden=False,
seen_models=None,
topmost_call=True,
):
"""
Internal helper function to return fields of the model.
@ -885,13 +885,6 @@ class Options:
# implementation and to provide a fast way for Django's internals to
# access specific subsets of fields.
# We must keep track of which models we have already seen. Otherwise we
# could include the same field multiple times from different models.
topmost_call = seen_models is None
if topmost_call:
seen_models = set()
seen_models.add(self.model)
# Creates a cache key composed of all arguments
cache_key = (forward, reverse, include_parents, include_hidden, topmost_call)
@ -906,12 +899,11 @@ class Options:
# Recursively call _get_fields() on each parent, with the same
# options provided in this call.
if include_parents is not False:
# In diamond inheritance it is possible that we see the same model
# from two different routes. In that case, avoid adding fields from
# the same parent again.
parent_fields = set()
for parent in self.parents:
# In diamond inheritance it is possible that we see the same
# model from two different routes. In that case, avoid adding
# fields from the same parent again.
if parent in seen_models:
continue
if (
parent._meta.concrete_model != self.concrete_model
and include_parents == PROXY_PARENTS
@ -922,13 +914,15 @@ class Options:
reverse=reverse,
include_parents=include_parents,
include_hidden=include_hidden,
seen_models=seen_models,
topmost_call=False,
):
if (
not getattr(obj, "parent_link", False)
or obj.model == self.concrete_model
):
) and obj not in parent_fields:
fields.append(obj)
parent_fields.add(obj)
if reverse and not self.proxy:
# Tree is computed once and cached until the app cache is expired.
# It is composed of a list of fields pointing to the current model

View File

@ -106,6 +106,12 @@ class ItalianRestaurant(Restaurant):
serves_gnocchi = models.BooleanField(default=False)
class ItalianRestaurantCommonParent(ItalianRestaurant, Place):
place_ptr_two = models.OneToOneField(
Place, on_delete=models.CASCADE, parent_link=True
)
class Supplier(Place):
customers = models.ManyToManyField(Restaurant, related_name="provider")

View File

@ -15,6 +15,7 @@ from .models import (
GrandChild,
GrandParent,
ItalianRestaurant,
ItalianRestaurantCommonParent,
MixinModel,
Parent,
ParkingLot,
@ -158,6 +159,28 @@ class ModelInheritanceTests(TestCase):
with self.assertNumQueries(4):
common_child.save()
def test_create_diamond_mti_common_parent(self):
with self.assertNumQueries(4):
italian_restaurant_child = ItalianRestaurantCommonParent.objects.create(
name="Ristorante Miron",
address="1234 W. Ash",
)
self.assertEqual(
italian_restaurant_child.italianrestaurant_ptr.place_ptr,
italian_restaurant_child.place_ptr_two,
)
self.assertEqual(
italian_restaurant_child.italianrestaurant_ptr.restaurant_ptr,
italian_restaurant_child.restaurant_ptr,
)
self.assertEqual(
italian_restaurant_child.restaurant_ptr.place_ptr,
italian_restaurant_child.place_ptr_two,
)
self.assertEqual(italian_restaurant_child.name, "Ristorante Miron")
self.assertEqual(italian_restaurant_child.address, "1234 W. Ash")
def test_update_parent_filtering(self):
"""
Updating a field of a model subclass doesn't issue an UPDATE