django - предварительная загрузка только самой новой записи? - PullRequest
0 голосов
/ 07 мая 2018

Я пытаюсь предварительно выбрать только последнюю запись для родительской записи.

мои модели как таковые

class LinkTargets(models.Model):
    device_circuit_subnet = models.ForeignKey(DeviceCircuitSubnets, verbose_name="Device", on_delete=models.PROTECT)
    interface_index = models.CharField(max_length=100, verbose_name='Interface index (SNMP)', blank=True, null=True)
    get_bgp = models.BooleanField(default=False, verbose_name="get BGP Data?")
    dashboard = models.BooleanField(default=False, verbose_name="Display on monitoring dashboard?")


class LinkData(models.Model):
    link_target = models.ForeignKey(LinkTargets, verbose_name="Link Target", on_delete=models.PROTECT)
    interface_description = models.CharField(max_length=200, verbose_name='Interface Description', blank=True, null=True)
...

Приведенный ниже запрос завершается с ошибкой

AttributeError: 'LinkData' object has no attribute '_iterable_class'

Запрос:

link_data = LinkTargets.objects.filter(dashboard=True) \
                            .prefetch_related(
                                Prefetch(
                                    'linkdata_set',
                                    queryset=LinkData.objects.all().order_by('-id')[0]
                                    )
                                )

Я думал о том, чтобы вместо этого получить LinkData и сделать связанный элемент select, но я не представляю, как получить только 1 запись для каждого link_target_id

link_data = LinkData.objects.filter(link_target__dashboard=True) \
                            .select_related('link_target')..?   

EDIT:

с использованием решения rtindru, предварительно извлеченные данные кажутся пустыми. в настоящее время там 6 записей, по 1 записи для каждой из 3 целей LinkText

>>> link_data[0]
<LinkTargets: LinkTargets object>
>>> link_data[0].linkdata_set.all()
<QuerySet []>
>>>

Ответы [ 2 ]

0 голосов
/ 07 мая 2018

LinkData.objects.all().order_by('-id')[0] - это не набор запросов, это объект модели, поэтому ваша ошибка.

Вы можете попробовать LinkData.objects.all().order_by('-id')[0:1], который действительно является QuerySet, но он не будет работать. Учитывая, как работает prefetch_related, аргумент queryset должен возвращать набор запросов, который содержит все необходимые записи LinkData (затем происходит дополнительная фильтрация, и элементы в нем объединяются с объектами LinkTarget). Этот набор запросов содержит только один элемент, так что это бесполезно. (И Django будет жаловаться «Не удается отфильтровать запрос после того, как будет взят фрагмент» и вызовет исключение, как и должно быть).

Давайте вернемся. По сути, вы задаете вопрос об агрегации / аннотации - для каждого LinkTarget вы хотите знать самый последний объект LinkData или «max» столбца «id». Самый простой способ - просто аннотировать идентификатор, а затем выполнить отдельный запрос, чтобы получить все объекты.

Итак, это будет выглядеть так (я проверил аналогичную модель в моем проекте, поэтому она должна работать, но код ниже может иметь некоторые опечатки):

linktargets = (LinkTargets.objects
               .filter(dashboard=True)
               .annotate(most_recent_linkdata_id=Max('linkdata_set__id'))

# Now, if we need them, lets collect and get the actual objects
linkdata_ids = [t.most_recent_linkdata_id for t in linktargets]
linkdata_objects = LinkData.objects.filter(id__in=linkdata_ids)

# And we can decorate the LinkTarget objects as well if we want:

linkdata_d = {l.id: l for l in linkdata_objects}
for t in linktargets:
    if t.most_recent_linkdata_id is not None:
        t.most_recent_linkdata = linkdata_d[t.most_recent_linkdata_id]

Я намеренно не превратил это в предварительную выборку, маскирующую linkdata_set, потому что в результате у вас есть объекты, которые вам лгут - атрибут linkdata_set теперь пропускает результаты. Вы действительно хотите быть укушенным этим где-нибудь в будущем? Лучше всего сделать новый атрибут, который имеет именно то, что вы хотите.

0 голосов
/ 07 мая 2018

Причина в том, что Prefetch ожидает Django Queryset в качестве параметра queryset, а вы даете экземпляр объекта.

Измените ваш запрос следующим образом:

link_data = LinkTargets.objects.filter(dashboard=True) \
                            .prefetch_related(
                                Prefetch(
                                    'linkdata_set',
                                    queryset=LinkData.objects.filter(pk=LinkData.objects.latest('id').pk)
                                    )
                                )

К сожалению, это в значительной степени отменяет цель Prefetch.

Обновление Это предварительно выбирает ровно одну запись в глобальном масштабе; не самая последняя запись LinkData за LinkTarget.

Чтобы предварительно выбрать максимальное значение LinkData для каждой LinkTarget, вы должны начать с LinkData: вы можете добиться этого следующим образом:

LinkData.objects.filter(link_target__dashboard=True).values('link_target').annotate(max_id=Max('id'))

Возвращает словарь {link_target: 12, max_id: 3223}

Затем вы можете использовать это, чтобы вернуть правильный набор объектов; возможно отфильтровать LinkData на основе значений max_id.

Это будет выглядеть примерно так:

latest_link_data_pks = LinkData.objects.filter(link_target__dashboard=True).values('link_target').annotate(max_id=Max('id')).values_list('max_id', flat=True)
link_data = LinkTargets.objects.filter(dashboard=True) \
                            .prefetch_related(
                                Prefetch(
                                    'linkdata_set',
                                    queryset=LinkData.objects.filter(pk__in=latest_link_data_pks)
                                    )
                                )   
...