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:
parent
82a588a6bc
commit
1754c2c802
@ -864,7 +864,7 @@ class Options:
|
|||||||
reverse=True,
|
reverse=True,
|
||||||
include_parents=True,
|
include_parents=True,
|
||||||
include_hidden=False,
|
include_hidden=False,
|
||||||
seen_models=None,
|
topmost_call=True,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Internal helper function to return fields of the model.
|
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
|
# implementation and to provide a fast way for Django's internals to
|
||||||
# access specific subsets of fields.
|
# 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
|
# Creates a cache key composed of all arguments
|
||||||
cache_key = (forward, reverse, include_parents, include_hidden, topmost_call)
|
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
|
# Recursively call _get_fields() on each parent, with the same
|
||||||
# options provided in this call.
|
# options provided in this call.
|
||||||
if include_parents is not False:
|
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:
|
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 (
|
if (
|
||||||
parent._meta.concrete_model != self.concrete_model
|
parent._meta.concrete_model != self.concrete_model
|
||||||
and include_parents == PROXY_PARENTS
|
and include_parents == PROXY_PARENTS
|
||||||
@ -922,13 +914,15 @@ class Options:
|
|||||||
reverse=reverse,
|
reverse=reverse,
|
||||||
include_parents=include_parents,
|
include_parents=include_parents,
|
||||||
include_hidden=include_hidden,
|
include_hidden=include_hidden,
|
||||||
seen_models=seen_models,
|
topmost_call=False,
|
||||||
):
|
):
|
||||||
if (
|
if (
|
||||||
not getattr(obj, "parent_link", False)
|
not getattr(obj, "parent_link", False)
|
||||||
or obj.model == self.concrete_model
|
or obj.model == self.concrete_model
|
||||||
):
|
) and obj not in parent_fields:
|
||||||
fields.append(obj)
|
fields.append(obj)
|
||||||
|
parent_fields.add(obj)
|
||||||
|
|
||||||
if reverse and not self.proxy:
|
if reverse and not self.proxy:
|
||||||
# Tree is computed once and cached until the app cache is expired.
|
# 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
|
# It is composed of a list of fields pointing to the current model
|
||||||
|
@ -106,6 +106,12 @@ class ItalianRestaurant(Restaurant):
|
|||||||
serves_gnocchi = models.BooleanField(default=False)
|
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):
|
class Supplier(Place):
|
||||||
customers = models.ManyToManyField(Restaurant, related_name="provider")
|
customers = models.ManyToManyField(Restaurant, related_name="provider")
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ from .models import (
|
|||||||
GrandChild,
|
GrandChild,
|
||||||
GrandParent,
|
GrandParent,
|
||||||
ItalianRestaurant,
|
ItalianRestaurant,
|
||||||
|
ItalianRestaurantCommonParent,
|
||||||
MixinModel,
|
MixinModel,
|
||||||
Parent,
|
Parent,
|
||||||
ParkingLot,
|
ParkingLot,
|
||||||
@ -158,6 +159,28 @@ class ModelInheritanceTests(TestCase):
|
|||||||
with self.assertNumQueries(4):
|
with self.assertNumQueries(4):
|
||||||
common_child.save()
|
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):
|
def test_update_parent_filtering(self):
|
||||||
"""
|
"""
|
||||||
Updating a field of a model subclass doesn't issue an UPDATE
|
Updating a field of a model subclass doesn't issue an UPDATE
|
||||||
|
Loading…
x
Reference in New Issue
Block a user