django - 预取只有最新的记录?

时间:2018-05-07 17:54:50

标签: python django django-queryset

我正在尝试仅针对父记录预取最新记录。

我的模特就是这样

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并做一个相关的选择,但我不知道如何只为每个link_target_id获取1条记录

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

编辑:

使用rtindru的解决方案,预取的似乎是空的。目前有6条记录,3条LinkTargets中每条记录都有1条记录

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

4 个答案:

答案 0 :(得分:4)

原因是Queryset期望Django 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.objects.filter(link_target__dashboard=True).values('link_target').annotate(max_id=Max('id'))条记录。

要为每个LinkTarget预取最大LinkData,您应该从LinkData开始:您可以按如下方式实现:

link_target

这将返回{max_id:12,max_id的字典:3223}

然后您可以使用它来返回正确的对象集;也许根据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) ) )

的值过滤LinkData

看起来像这样:

{{1}}

答案 1 :(得分:1)

以下内容适用于PostgreSQL。我了解它不会帮助OP,但是对其他人可能有用。

from django.db.models import Count, Prefetch
from .models import LinkTargets, LinkData

link_data_qs = LinkData.objects.order_by(
    'link_target__id',
    '-id',
).distinct(
    'link_target__id',
)

qs = LinkTargets.objects.prefetch_related(
    Prefetch(
        'linkdata_set',
        queryset=link_data_qs,
    )
).all()

答案 2 :(得分:0)

LinkData.objects.all().order_by('-id')[0]不是查询集,它是模型对象,因此是您的错误。

您可以尝试LinkData.objects.all().order_by('-id')[0:1]这确实是一个QuerySet,但它无法正常工作。鉴于prefetch_related如何工作,queryset参数必须返回一个包含所需的所有LinkData记录的查询集(然后进一步过滤,并使用LinkTarget对象将其中的项连接起来)。此查询集仅包含一个项目,因此不合适。 (并且Django会抱怨&#34;一旦切片被采取后就无法过滤查询&#34;并且应该提出异常)。

让我们回来吧。基本上你要问一个聚合/注释问题 - 对于每个LinkTarget,你想要知道最新的LinkData对象,或者&#39; max&#39;一个&#39; id&#39;柱。最简单的方法是使用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属性现在丢失了结果。你真的想被那个地方的某个地方所困扰吗?最好制作一个只有您想要的新属性。

答案 3 :(得分:0)

棘手,但似乎有效:

class ForeignKeyAsOneToOneField(models.OneToOneField):
    def __init__(self, to, on_delete, to_field=None, **kwargs):
        super().__init__(to, on_delete, to_field=to_field, **kwargs)
        self._unique = False

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


link_data = LinkTargets.objects.filter(dashboard=True) \
                               .prefetch_related(
                                    Prefetch(
                                        'linkdata_helper',
                                        queryset=LinkData.objects.all().order_by('-id'),
                                        'linkdata'
                                    )
                                )

# Now you can access linkdata:
link_data[0].linkdata

当然,使用这种方法您不能使用 linkdata_helper 来获取相关对象。