3
There isn’t anything really written into Django for this, unfortunately. The annotation is a specific feature of a queryset.
You may consider adding a @property on your Node model
class Node(Model):
parent = ForeignKey('self', related_name='children')
@property
def child_count(self)
return ... #Some logic to return the desired count
Or a workaround I have used before is to just get a queryset from the list I have for example:
nodes = list(Node.objects.filter(some_filter=True)) # This would be your list from somewhere else
node_ids = [n.id for n in nodes]
node_qs = Node.objects.filter(id__in=node_ids).annotate(child_count=Count('children'))
3
I ended up writing a helper function that implements the API I had imagined:
from collections import defaultdict
def annotate_objects(model_instances, *args, **kwargs):
"""
The annotation equivalent of `prefetch_related_objects`: works just like the
queryset `annotate` method, but operates on a sequence of model instances
instead of a queryset.
"""
if len(model_instances) == 0:
return
# Group instances by model class (since you can hypothetically pass
# instances of different models).
instances_by_model_class = defaultdict(list)
for instance in model_instances:
instances_by_model_class[type(instance)].append(instance)
for Model, instances in instances_by_model_class.items():
pks = set(instance.pk for instance in instances)
queryset = Model.objects.filter(pk__in=pks).annotate(*args, **kwargs)
annotation_keys = list(queryset.query.annotations.keys())
all_annotations = queryset.values(*annotation_keys)
for instance, annotations in zip(instances, all_annotations):
for key, value in annotations.items():
setattr(instance, key, value)
To use:
annotate_objects(nodes, child_count=Count('children'))
for node in nodes:
print(node.child_count)
- [Django]-Django Fulltext search on JSON field
- [Django]-User permissions for Django module
- [Django]-“select_for_update” called inside an atomic block still TransactionManagementError
- [Django]-Django Model API reverse lookup of many to many relationship through intermediary table
2
I came up with same workaround as the author, but generalized form.
Simply collect data by making separate queries to avoid N+1
, and then assign values for model instances:
instances = Model.objects.filter(...).all()
for instance in instances:
value = 'custom value'
setattr(instance, 'new_attribute', value)
then you could simply call new attribute:
instance = instances[0]
print(instance.new_attribute)
outputs custom value
.
Source:stackexchange.com