You're a tad late if you want to get the counts in an instance method. The easiest way to optimize this is by using annotations in the initial query:
obj = MyModel.objects.annotate(item_one_count=Count('items_one')) \
.annotate(item_two_count=Count('items_two')) \
.annotate(item_three_count=Count('items_three')) \
.get(...)
Another good optimization is to cache the results, e.g.:
MyModel(models.Model):
def get_item_one_count(self):
if not hasattr(self, '_item_one_count'):
self._item_one_count = self.items_one.count()
return self._item_one_count
...
def get_counts(self):
return {
'item_one_counts' : self.get_item_one_count(),
'item_two_counts' : self.get_item_two_count(),
'item_three_count' : self.get_item_three_count(),
}
Combine these methods (i.e. .annotate(_item_one_count=Count('items_one'))), and you can optimize the counts into a single query when you have control over the query, while having fallback method in case you can't annotate the results.
Another option is to perform the annotation in your model manager, but you will no longer have fine-grained control over the queries.