簡體   English   中英

注釋 Django 中現有的模型對象

[英]Annotate existing model objects in Django

有沒有辦法使用 Django 的annotate方法之類的東西,但是對於現有模型實例的集合而不是查詢集?

假設我有一個這樣的模型(刪除了所有不相關的細節):

class Node(Model):
    parent = ForeignKey('self', related_name='children')

如果我正在獲取一些節點並希望每個節點的子節點計數,我可以這樣做:

nodes = Node.objects.filter(some_filter=True).annotate(child_count=Count('children'))
for node in nodes:
    print(node.child_count)

但是如果我已經有一個 Node 對象的集合,而不是一個查詢集呢? 這樣做的天真的方法會遇到N+1 查詢問題,這對於性能來說是不可接受的:

for node in nodes:
    print(node.children.count()) # executes a separate query for each instance

我本質上想要與prefetch_related_objects等效的注釋。 我正在想象這樣的事情:

nodes = list(Node.objects.filter(some_filter=True))
annotate_objects(nodes, child_count=Count('children'))
for node in nodes:
    print(node.child_count)

Django 中是否有類似的東西? 翻閱文檔對我來說並沒有什么收獲。

我最終編寫了一個輔助函數來實現我想象中的 API:

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)

使用:

annotate_objects(nodes, child_count=Count('children'))
for node in nodes:
    print(node.child_count)

不幸的是,沒有任何東西真正寫入 Django。 注釋是查詢集的特定功能。

你可以考慮在你的 Node 模型上添加一個 @property

class Node(Model):
    parent = ForeignKey('self', related_name='children')

    @property
    def child_count(self)
        return ... #Some logic to return the desired count

或者我之前使用的解決方法是從我擁有的列表中獲取一個查詢集,例如:

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'))

我想出了與作者相同的解決方法,但采用了通用形式。

只需通過進行單獨的查詢來收集數據以避免N+1 ,然后為模型實例分配值:

instances = Model.objects.filter(...).all()
for instance in instances:
    value = 'custom value'
    setattr(instance, 'new_attribute', value)

那么你可以簡單地調用新屬性:

instance = instances[0]
print(instance.new_attribute)

輸出custom value

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM