2

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.

2
  • Are you using postgres? Commented Dec 22, 2019 at 22:42
  • Yes, postgres 11.6 Commented Dec 22, 2019 at 22:45

1 Answer 1

3

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)
Sign up to request clarification or add additional context in comments.

1 Comment

Yeah, this was the way to go! You should replace product with product_id in the queryset: queryset=ProductRevision.objects.order_by('product_id', '-version').distinct('product') otherwise you get the SELECT DISTINCT ON expressions must match initial ORDER BY expressions error (see stackoverflow.com/questions/20582966/… for more information)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.