Django - заполнить связанное с экземпляром модели поле из кэшированного запроса - PullRequest
2 голосов
/ 17 марта 2019

Та же ситуация, что и Django prefetch_related детей детей , но другой вопрос:

У меня есть модель Node, которая выглядит примерно так:

class Node(models.Model):
    parent = models.ForeignKey('self', related_name='children', on_delete=models.CASCADE, null=True)

Узел может иметь несколько детей, и каждый из этих детей может иметь своих собственных детей.

Я бы хотел сделать что-то подобное:

def cache_children(node):
    for child in node.children.all():
        cache_children(child)

root_node = Node.objects.prefetch_related('children').get(pk=my_node_id) 

all_nodes = Node.objects.all()  # get all the nodes in a single query

# Currently: hit database for every loop
# Would like: to somehow use the already loaded data from all_nodes
cache_children(root_node)  

Поскольку я уже собрал все узлы в запросе all_nodes, я хотел бы повторно использовать кэшированные данные из этого запроса вместо того, чтобы каждый раз выполнять новые.

Есть ли способ достичь этого?

Ответы [ 2 ]

1 голос
/ 17 марта 2019

Данные в древовидной структуре не очень хорошо подходят для реляционной базы данных, однако есть несколько стратегий для решения этой проблемы - см. Главу о реализациях дерева в документах django-treebeard .

Если ваше дерево не слишком большое, вы можете полностью сохранить дерево в Python dict и кэшировать результаты.

Пример (не проверено - адаптируйте структуру данных по своему вкусу ...) :

from django.core.cache import cache

# ...

def get_children(nodes, node):
    node['children'] = [n for n in nodes if n['parent']==node['id']]
    for child_node in node['children']:
        child_node = get_children(nodes, child_node)
    return node


def get_tree(timeout_in_seconds=3600)
    tree = cache.get('your_cache_key')
    if not tree:
        # this creates a list of dicts with the instances values - one DB hit!
        all_nodes = list(Node.objects.all().values())
        root_node = [n for n in nodes if n['parent']==None][0]
        tree = get_children(all_nodes, root_node)

        cache.set('your_cache_key', tree, timeout_in_seconds)
    return tree
  • Конечно, вы должны включить кэш
  • вы можете аннулировать кеш в вашем методе Node.save
0 голосов
/ 18 марта 2019

Мне удалось заставить его работать таким образом и заполнить все дерево вызовами по 2 дБ:

def populate_prefetch_cache(node, all_nodes):
    children = [child for child in all_nodes if child.parent_id==node.id]

    # will not have the attribute if no prefetch has been done
    if not hasattr(node, '_prefetched_objects_cache'):
        node._prefetched_objects_cache = {}

    # Key to using local data to populate a prefetch!
    node._prefetched_objects_cache['children'] = children
    node._prefetch_done = True

    for child in node.children.all():
        populate_prefetch_cache(child , all_nodes )


all_nodes = list(Node.objects.all())  # Hit database once
root_node = Node.objects.get(pk=my_node_id)  # Hit database once

# Does not hit the database and properly populates the children field
populate_prefetch_cache(root_node, all_nodes)

Я обнаружил атрибут _prefetched_objects_cache благодаря этому ответу: Django: Добавление объектов всвязанный набор без сохранения в БД

...