When I want to define my business logic, I'm struggling finding the right way to do this, because I often both need a property AND a custom queryset to get the same info. In the end, the logic is duplicated.
Let me explain...
First, after defining my class, I naturally start writing a simple property for data I need:
class PickupTimeSlot(models.Model):
@property
def nb_bookings(self) -> int:
""" How many times this time slot is booked? """
return self.order_set.validated().count()
Then, I quickly realise that calling this property while dealing with many objects in a queryset will lead to duplicated queries and will kill performance (even if I use prefetching, because filtering is called again). So I solve the problem writing a custom queryset with annotation:
class PickupTimeSlotQuerySet(query.QuerySet):
def add_nb_bookings_data(self):
return self.annotate(db_nb_bookings=Count('order', filter=Q(order__status=Order.VALIDATED)))
The issue
And then, I end up with 2 problems:
- I have the same business logic ("how to find the number of bookings") written twice, that could lead to functional errors.
- I need to find two different attribute names to avoid conflicts, because obviously, setting
nb_bookingsfor both the property and the annotation don't work. This forces me, when using my object, to think about how the data is generated, to call the right attribute name (let's saypickup_slot.nb_bookings(property) orpickup_slot.db_nb_bookings(annotation) )
This seems poorly designed to me, and I'm pretty sure there is a way to do better. I'd need a way to always write pickup_slot.nb_bookings and having a performant answer, always using the same business logic.
I have an idea, but I'm not sure...
I was thinking of completely removing the property and keeking custom queryset only. Then, for single objects, wrapping them in querysets just to be able to call add annotation data on it. Something like:
pickup_slot = PickupTimeSlot.objects.add_nb_bookings_data().get(pk=pickup_slot.pk)
Seems pretty hacky and unnatural to me. What do you think?