Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

How to make a model field equal to a queryset result

I am new to django and coding in general. I have this project that I am trying to code thats basically an auctions website.

I am facing some difficulties structuring the models though.

Here are 2 models of all models

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

class Listing(models.Model):
    title = models.CharField(max_length=64)
    image = models.URLField(null=True, blank=True)
    description = models.CharField(max_length=64)
    starting_price = models.DecimalField(max_digits=7, decimal_places=2)    

    current_price = #highest bid price
    bids_count = #count of bids

    lister = models.ForeignKey(User, on_delete=models.CASCADE, related_name="listings")

    def __str__(self):
        return f"Title: {self.title} ({self.id}), Lister: {self.lister.username}"


class Bid(models.Model):
    listing = models.ForeignKey(Listing, on_delete=models.CASCADE, related_name="bids")
    bidder = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name="bids")
    amount = models.DecimalField(max_digits=7, decimal_places=2)
    time = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return f"Bid by {self.bidder.username} on {self.listing.title} ({self.listing.id}) for an amount of {self.amount}"

I am trying to let the current price equal to the highest bid of that listing

current_price = Listing.bid.objects.all().aggregate(Max("amount"))

And count the number of bids

bids_count = Listing.bid.count()

I know we cant place querysets inside models fields like I have done but i did it to demonstrate my issue.

Surely there is some way around this but I just cant figure it out.

>Solution :

As you said, you cannot put those fields "as-is" in your model. The easiest/quickest way to solve it would be to use a property:

class Listing(models.Model):
    # ... rest of the class

    @property
    def bids_count(self):
        return self.bids.count()

    @property
    def current_price(self):
        return self.bids.all().aggregate(Max("amount"))

    # ... rest of the class

now, be aware that this will do fine when you work with a single instance. This will not be performant if you are looping over a list of Listing instances and display those properties, because it will trigger a new db query each time you access those properties (so these values are fetched in a lazy way)

The best workaround in my opinion would be to use a custom manager, as follow:

class ListingQuerySet(models.QuerySet):

    def with_bids_count(self):
        return self.annotate(bids_count=Count('bids'))

    def with_current_price(self):
        return self.annotate(current_price=Subquery(Bid.objects.filter(listing=OuterRef('pk')).annotate(max=Max('amount')).values('max')[:1]))

class Listing(models.Model):
    
    objects = ListingQuerySet.as_manager()

    # ... rest of the class

# Use it like this in your code

for listing in Listing.objects.with_bids_count().with_current_price():
    print(listing.current_price)

This previous method is more advanced for someone new to Django/coding (especially with the subquery). You’ll be able to read more about all this in the documentation:

Note that I didn’t try the code

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading