Search code examples
djangodjango-modelsdjango-select-relateddjango-related-manager

Django fetch related child with parent object


I am using Django 2.2 and I have a model with two classes Product and ProductRevision. When I retrieve a Product, or a list of Products, I always fetch the corresponding ProductRevision. ProductRevision objects are incremented and only the last revision should be fetched with the Product.

class Product(models.Model):
    name = models.CharField(max_length=50, null=False, blank=False,
                            verbose_name=_("name"))
    product_code = models.CharField(max_length=10, null=False, blank=False,
                                    verbose_name=_("product code"))
    slug = models.SlugField(null=False, unique=True)

    @property
    def current_item(self):
        return ProductRevision.objects.filter(product=self, active=True).order_by('-version').first()


class ProductRevision(models.Model):
    product = models.ForeignKey(Product, null=True, on_delete=models.PROTECT)
    version = models.IntegerField(default=0,
                                  verbose_name=_("version"))
    active = models.BooleanField(default=False, null=False, blank=True,
                                 verbose_name=_("is active"))
    qty_close = models.IntegerField(default=0,
                                    verbose_name=_("qty of accounts to be closed"))
    price_eur = models.DecimalField(max_digits=6, decimal_places=2, default=0,
                                    verbose_name=_("price in EUR"))

I tried to add a property current_item to get the last revision of a given product. While this is working, it is very inefficient because when I use it in a template, it hits the database every time I display a field from the related ProductRevision.

I found this answer based on an old version of Django (Fetch latest related objects in django) and I'm wondering if there is no other ways to achieve the same result with current versions of Django? I'm particularly interrested in achieving this in my models.


Solution

  • I have managed to achieve something similar to this by using a custom prefetch related queryset

    products = Product.objects.prefetch_related(Prefetch(
        'productrevision_set',
        queryset=ProductRevision.objects.order_by('product', '-version').distinct('product'),
        to_attr='latest_revision'
    ))
    

    The queryset above will only return one ProductRevision per Product which in effect gives us the latest ProductRevision for all Products in only 2 queries

    for product in products:
        for latest_revision in product.latest_revision:
            print(product, latest_revision)