wagtail / django-modelcluster

Django extension to allow working with 'clusters' of models as a single unit, independently of the database
BSD 3-Clause "New" or "Revised" License
483 stars 66 forks source link

inherit Parentalkey + prefecth_related bug #103

Open MauriceBenink opened 5 years ago

MauriceBenink commented 5 years ago

the following case does not return the results you would expect :

class Parent(ClusterableModel):
    pass

class OneChild(Parent):
    parent = models.OneToOneField(to=Parent, parent_link=True,on_delete=models.CASCADE)

class ManyChild(models.Model):
    parent = ParentalKey('cms.Parent', null=True, related_name='manyChild')
    data = models.CharField(max_length=50)
    data2 = models.CharField(max_length=50)

def populate():
    Parent.objects.all().delete()
    OneChild.objects.all().delete()
    ManyChild.objects.all().delete()
    p1 = Parent.objects.create()
    p2 = Parent.objects.create()
    c1 = OneChild.objects.create(parent=p1)
    c2 = OneChild.objects.create(parent=p2)
    m1 = ManyChild.objects.create(parent=c1,data='1',data2='a')
    m1 = ManyChild.objects.create(parent=c1, data='2', data2='b')
    m1 = ManyChild.objects.create(parent=c2, data='1', data2='c')
    m1 = ManyChild.objects.create(parent=c2, data='2', data2='d')

def testresult():
    queryset = OneChild.objects \
        .prefetch_related(
        Prefetch(
            "manyChild",
            queryset=ManyChild.objects.filter(data='1')
        )
    )

  return [queryset.all()[0].manyChild.values(),queryset.all()[1].manyChild.values()]

populate()

print(testresult())

expected 1 result per queryset, gets all results that mach the filter. relation seems to be lost

in this case you would expect the result to be :

[<QuerySet [{'id': 1, 'parent_id': 10, 'data': '1', 'data2': 'a'}]>,<QuerySet [{'id': 3, 'parent_id': 11, 'data': '1', 'data2': 'c'}]>]

But it returns :

[<QuerySet [{'id': 1, 'parent_id': 10, 'data': '1', 'data2': 'a'},{'id': 3, 'parent_id': 11, 'data': '1', 'data2': 'c'}]>,<QuerySet [{'id': 1, 'parent_id': 10, 'data': '1', 'data2': 'a'},{'id': 3, 'parent_id': 11, 'data': '1', 'data2': 'c'}]>]

This issue doesnt appear if you use models.ForgienKey('cms.Parent', null=True, on_delete=models.CASCADE, related_name='manyChild') instead of ParentalKey('cms.Parent', null=True, related_name='manyChild')

using this lib in combination with wagtail. but this seems to be unrelated to them

Using python 3.6 Django 2.1.2

MauriceBenink commented 5 years ago

for now i seem to be able to fix it using the following

class myRelationManager:

    forceRelation2 = ["add","remove"]

    def __init__(self,manager1,manager2):
        self.manager1 = manager1
        self.manager2 = manager2

    def __getattr__(self, item):
        if item in self.forceRelation2:
            return getattr(self.manager2, item)
        if hasattr(self.manager1,item):
            return getattr(self.manager1,item)
        return getattr(self.manager2, item)

class myRelatedAncestor(ChildObjectsDescriptor):
    def __get__(self, instance, instance_type=None):
        if instance is None:
            return self
        return myRelationManager(self.related_manager_cls(instance),self.child_object_manager_cls(instance))

class myParentalKey(ParentalKey):
    related_accessor_class = myRelatedAncestor

Not very pritty but it works as far as i know atm