'Django prefetch table that is not directly related

With the following models is it possible to get Category objects and prefetch OrderLine so that it can be accessed by category.orderlines.all()?

class Category(models.Model):
    name = models.CharField(...)

class Product(models.Model):
    category = models.ForeignKey(Category, related_name='products', ...)
    name = models.CharField(...)
    price = models.DecimalField(...)

class OrderLine(models.Model):
    product = models.ForeignKey(Product, related_name='orderlines', ...)

I know you can do a nested prefetch through products but I'd like to access OrderLine objects directly from a Category rather than go through Product

from django.db.models import Prefetch

categories = Category.objects.prefetch_related(Prefetch(
    'products__orderlines',
    queryset=OrderLine.objects.filter(...)
))

for category in categories:
    for product in category.products.all():
        for line in product.orderlines.all():
            ...

Desired usage:

for category in categories:
    for line in category.orderlines.all():
        ...

Update

Adding to_attr='orderlines' results in:

ValueError: to_attr=orderlines conflicts with a field on the Product model.

Changing the attribute name to_attr='lines' doesn't cause an error but the attribute isn't added to the Category objects. It prefetches Product then adds a lines attribute to each product.



Solution 1:[1]

The closest I've come to this is using an ArraySubquery. The only downside being that lines is a list of dictionaries rather than model instances.

Category.objects.annotate(
    lines=ArraySubquery(
        OrderLine.objects
        .filter(category=OuterRef('id'))
        .values(json=JSONObject(
            id='id',
            product_name='product__name',
            quantity='quantity',
        ))
    )
)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 bdoubleu