Consider a room booking system. You might have a Building, Floor, Room models as well as a Booking. We give the room a name based on its building and floor:
class Room(models.Model):
number = models.PositiveIntegerField()
name = models.CharField(..)
floor = models.ForeignKey('Floor')
def __str__(self):
return '%s, #%d Floor %d, %s' % (
self.name,
self.number,
self.floor.number,
self.floor.building.name
)
This is woefully inefficient when you're doing hundreds of them (eg admin list, big reports, etc.) so I've taken to writing managers like this:
class RoomManager(models.Manager):
def get_queryset(self):
return super().get_queryset().annotate(
roomname=Concat(
'name',
V(', #'),
'number'
V(' Floor '),
'floor__number'
V(', '),
'floor__building__name',
output_field=models.CharField()
),
)
And that works. It does everything I wanted it to. It's fast and I've reworked the __str__
to do a if hasattr(self, 'roomname'): return self.roomname
before it does the horrendous multi-query string builder.
But now on top of this, I have Booking. Each Booking instance is linked to a single room. There are many cases where to list Bookings, I actually also list room names.
What I've done is write a BookingManager:
class RoomManager(models.Manager):
def get_queryset(self):
return super().get_queryset().annotate(
roomname=Concat(
'room__name',
V(', #'),
'room__number'
V(' Floor '),
'room__floor__number'
V(', '),
'room__floor__building__name',
output_field=models.CharField()
),
)
But what the hell? I'm repeating myself. Django is all about DRY and here I am copy and pasting a huge messy annotation around. It's disgusting.
My question is... Is there another way?