11

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.

3
  • is this similar to stackoverflow.com/questions/47536903/… and stackoverflow.com/questions/54973908/… ?
    – sdementen
    Commented Dec 11, 2019 at 14:52
  • @sdementen those are also nested prefetches but my question is about bypassing the first level of a nested prefetch and adding the second level as an attribute to each object in the main queryset. From what I've looked into so far I think at minimum it will involve a custom Prefetch function and possibly a custom queryset method in place of prefetch_related.
    – bdoubleu
    Commented Dec 11, 2019 at 15:05
  • sorry @bdoubleu, and indeed you describe the same behaviour as in the other two posts.
    – sdementen
    Commented Dec 11, 2019 at 16:20

1 Answer 1

3

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',
        ))
    )
)

Not the answer you're looking for? Browse other questions tagged or ask your own question.