Следование отношениям подкласса с select_related

У меня есть схема наследования с несколькими таблицами, похожая на следующую:

class NodeData(models.Model):
    node = models.ForeignKey(Node, db_index = True)
    value = models.FloatField(default = 0)
    name = models.TextField(unique=True, blank=True)

class Node(models.Model):
    name = models.CharField(max_length=50, blank=True)
    description = models.TextField(blank=True)
    node_tree = models.ForeignKey(NodeTree, db_index = True)
    unique_name = models.TextField(unique=True, blank=True)
    last_updated_timestamp = models.DateTimeField('date last updated', blank=True)

class ConceptNode(Node):
    node_parent = models.ForeignKey(Node, related_name="nodeParent", null=True, blank=True)

class DerivedNode(Node):
    node_source = models.ForeignKey(Node, related_name="nodeSource")
    node_target = models.ForeignKey(Node, related_name="nodeTarget")

Из соображений производительности я использую select_related(depth = 2) всякий раз, когда я извлекаю большой выбор элементов NodeData. Однако select_related не отслеживает отношения вплоть до подклассов, поэтому следующий код* приводит к вызову реализации myFunction ConceptNode без предварительно выбранных данных для объекта ConceptNode, используемого в этой функции:

nd = NodeData.objects.get(id = 1)
nd.node.conceptnode.myFunction()

Это делает select_related не таким уж полезным, так как многие функции вызываются таким образом, и они не пользуются преимуществами данных, кэшированных select_related.

Мой вопрос: могу ли я заставить select_related получить эту информацию для меня, чтобы каждый объект NodeData, который я получаю, имел кэшированные экземпляры Node и ConceptNode/DerivedNode?*

ПРИМЕЧАНИЕ. На самом деле это делается с использованием модели функций доступа, как я спрашивал о здесь

ПРИМЕЧАНИЕ. То, что я пытаюсь сделать, похоже на this, но немного отличается, потому что у меня есть несколько подклассов.

EDIT: благодаря подсказке от chris-platt я обнаружил, что следующее делает то, что мне нужно:

nd = NodeData.objects.select_related('conceptnode','derivednode').get(id = 1)
nd.node.conceptnode.myFunction()

Первая строка предварительно загружает экземпляр DerivedNode или ConceptNode в соответствующий объект кэша.


person turtlemonvh    schedule 27.07.2012    source источник


Ответы (2)


Если вы сделаете что-то вроде ConceptNode.objects.get(...), экземпляр Node будет загружен одновременно, так что у вас будут полные данные для вашего экземпляра. Однако, когда вы делаете что-то вроде Node.objects.get(...), данные из ConceptNode и т. д. подклассов не включаются. Чтобы выбрать их, вам нужно использовать select_related(<related_name>), а чтобы получить несколько подклассов, вы просто продолжаете добавлять related_name в виде списка, разделенного запятыми, например:

Node.objects.select_related('nodeParent', 'nodeSource', 'nodeTarget')
person Chris Pratt    schedule 27.07.2012
comment
Спасибо, Крис - ваш ответ заставил меня двигаться в правильном направлении. Предоставление неявно определенных полей related_name, определенных для многотабличного наследования, работает. - person turtlemonvh; 29.07.2012

Я думаю, что здесь есть небольшое недопонимание того, как работает select_related. Это не работает для всех отношений, это работает для внешнего ключа или отношений один-к-одному, когда связанные данные представляют собой одну запись и где исходный запрос может быть изменен с помощью соединения, чтобы получить его. Так что этот пример не работает:

nd = NodeData.objects.get(id = 1)
nd.node.conceptnode.myFunction()

... потому что Node является родителем для ConceptNode, поэтому многие из них могут быть связаны. На самом деле я думаю, что вы получите ошибку атрибута с nd.node.conceptnode. Я думаю, вы бы получили доступ к этим дочерним узлам ConceptNodes через что-то вроде:

nd = NodeData.objects.get(id = 1)
nd.node.nodeParent.all() # Loop through this and do stuff with the children...

Я использую «nodeParent» здесь, так как вы установили related_name (вы можете изменить его на что-то более подходящее, например «nodeChildren» или что-то в этом роде), но по умолчанию это будет «conceptnode_set», например: nd.node.conceptnode_set.all()

Если вы используете Django 1.4, есть новый метод QuerySet под названием [prefetch_related][1], который я не использовал, но он предположительно предназначен именно для этой ситуации (сокращение запросов для отношений типа «многие»), и теоретически вы должны быть в состоянии сделать что-то вроде:

nd = NodeData.objects.get(id = 1).prefetch_related('conceptnode')

... хотя вам придется поиграть с аргументом «conceptnode» и с prefetch_related в целом, чтобы увидеть, даст ли он вам то, что вы хотите.

person Chris Forrette    schedule 27.07.2012
comment
Спасибо, Крис. Я думаю, что мой вопрос, возможно, сбил с толку, потому что и вы, и @chris-platt интерпретировали его одинаково. Мне не нужно извлекать все дочерние элементы определенного узла, я просто хочу предварительно получить экземпляр DerivedNode или ConceptNode, если Node также является ConceptNode или DerivedNode. - person turtlemonvh; 29.07.2012
comment
Проблема в том, что невозможно узнать, какой это подкласс Node, пока после он уже не будет извлечен из базы данных и не будут созданы экземпляры всех классов. Вот почему вы select_related используете все возможности, и тогда подходящая будет под рукой, когда она вам понадобится. - person Chris Pratt; 30.07.2012
comment
@chris-platt, ты совершенно прав. Вот почему в решении я ссылаюсь как на производный узел, так и на концепт-узел. - person turtlemonvh; 31.07.2012