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

ParentalKey relation .get() issue #92

Closed einsfr closed 6 years ago

einsfr commented 6 years ago

I'm using modelcluster with wagtail CMS and faced a regress after packages upgrade (wagtail 1.13.1 > 2.0, django-modelcluster 3.1 > 4.1). For example:

from django.db import models

from wagtail.core.models import Page
from wagtail.admin.edit_handlers import InlinePanel

from modelcluster.models import ParentalKey

class Product(Page):

    content_panels = Page.content_panels + [
        InlinePanel('prices')
    ]

    def get_price_by_type(self, price_type):
        return self.prices.get(price_type=price_type)

class PriceType(models.Model):

    name = models.CharField(
        max_length=16,
        unique=True,
    )

class ProductPrice(models.Model):

    class Meta:
        unique_together = (('price_type', 'product'), )

    value = models.DecimalField()

    price_type = models.ForeignKey(
        PriceType,
        on_delete=models.CASCADE,
        related_name='+',
    )

    product = ParentalKey(
        Product,
        on_delete=models.CASCADE,
        related_name='prices',
    )

Calling _get_price_bytype with modelcluster 3.1 results in:

SELECT "appname_productprice"."id", "appname_productprice"."value", "appname_productprice"."price_type_id", "appname_productprice"."product_id" FROM "appname_productprice" WHERE ("appname_productprice"."product_id" = 40 AND "appname_productprice"."price_type_id" = 1);

And with modelcluster 4.1:

SELECT "appname_productprice"."id", "appname_productprice"."value", "appname_productprice"."price_type_id", "appname_productprice"."product_id" FROM "appname_productprice" WHERE ("appname_productprice"."price_type_id" = 1 AND "appname_productprice"."price_type_id" = 1);
einsfr commented 6 years ago

My fault - example is incomplete - real application fails when there is a 'prefetch_related'. Something about this:

products = return Product.objects.live().filter(pk__in=pks).prefetch_related(
    models.Prefetch(
        'prices',
            queryset=ProductPrice.objects.filter(price_type=price_type)
        )
)
prices = [p.get_price_by_type(price_type) for p in products]