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. I
I don't think there is a silver bullet here. But I use this pattern in my projects for such cases.
class PickupTimeSlotAnnotatedManager(models.Manager):
def with_nb_bookings(self):
return self.annotate(
_nb_bookings=Count(
'order', filter=Q(order__status=Order.VALIDATED)
)
)
class PickupTimeSlot(models.Model):
...
annotated = PickupTimeSlotAnnotatedManager()
@property
def nb_bookings(self) -> int:
""" How many times this time slot is booked? """
if hasattr(self, '_nb_bookings'):
return self._nb_bookings
return self.order_set.validated().count()
In code
qs = PickupTimeSlot.annotated.with_nb_bookings()
for item in qs:
print(item.nb_bookings)
This way I can always use property, if it is part of annotated queryset it will use annotated value if not it will calculate it. This approach guaranties that I will have full control of when to make queryset "heavier" by annotating it with required values. If I don't need this I just use regular PickupTimeSlot.objects. ...
Also if there are many such properties you could write decorator that will wrap property and simplify code. It will work as cached_property decorator, but instead it will use annotated value if it is present.