【问题标题】:Django: Prefetch_related on nested attributesDjango:嵌套属性上的 Prefetch_related
【发布时间】:2020-05-07 11:52:33
【问题描述】:

我需要列出我的所有设备。为此,我使用与减少查询量相关的预取。但是其中一个非常耗时。我想知道它是否不能变得更好。

我将从模型构造开始:我想要一个设备列表。

class Device(models.Model):
    name = models.CharField(max_length=250, null=True, blank=True)

class GatewayDevice(models.Model):
    gateway = models.ForeignKey(
        Gateway, on_delete=models.CASCADE, related_name="devices"
    )
    device = models.ForeignKey(
        Device, on_delete=models.CASCADE, related_name="gatewaydevices"
    )

在实际代码中,设备模型更大,但该代码无关紧要。可以看到,一个设备有一些网关设备(网关和设备之间的模型)

所以在我的设备列表中,我想要每个设备,链接网关。

这是我的看法:

class AdminDeviceView(GenericAPIView):
    def get_permissions(self):
        return IsAuthenticated()

    # noinspection PyMethodMayBeStatic
    def get_serializer_class(self):
        return AdminDeviceInfoSerializer

    @swagger_auto_schema(
        responses={
            200: openapi.Response(
                _("Successfully fetched all data from devices."),
                AdminDeviceInfoSerializer,
            )
        }
    )

    def get(self, request):
    """
    GET the data from all the devices.
    """

    devices = (
        Device.objects.filter()
        .all()
        .prefetch_related(
            "site__users",
            "software_update_history",
            "supplier",
            Prefetch(
                "gatewaydevices",
                queryset=GatewayDevice.objects.filter(end_date=None)
                .order_by()
                .distinct()
                .prefetch_related("gateway"),
            ),
        )
    )

    serializer_class = self.get_serializer_class()
    serializer = serializer_class(devices, many=True)
    devices_data = serializer.data

    return Response(
        {"total": devices.count(), "items": devices_data}, status=status.HTTP_200_OK
    )

这是序列化程序中重要的部分:

@staticmethod
def get_gateway(device):
    return (
        GatewaySimpleSerializer(device.gatewaydevices.gateway).data
        if device.gatewaydevices.gateway
        else None
    )

我尝试了不同的方法。这是当前的.. 现在我收到此错误:

AttributeError: 'RelatedManager' object has no attribute 'gateway'

【问题讨论】:

    标签: django prefetch


    【解决方案1】:

    首先,您应该能够使用.select_related("gateway") 而不是.prefetch_related("gateway")。这应该会为您节省一个查询。

    您的问题似乎在这里:

    if device.gatewaydevices.gateway
    

    这里,gatewaydevices 不是单个模型实例,因此它没有网关属性。我不确定您的用例是什么,但可以有多个gatewaydevices。也许你想要第一个(如果gatewaydevices 为零,这将失败):

    if device.gatewaydevices.first().gateway
    

    也许你想知道有没有。这应该有效:

    if device.gatewaydevices.filter(gateway__isnull=False)
    

    虽然这看起来很奇怪,但如果您想排除没有gateways 的对象,则感觉此代码应该与查询的其余部分一起使用。我认为您应该完全删除 if 并始终返回数据。这样做并没有节省任何数据库时间。

    根据进一步的评论:

    if device.gatewaydevices.filter(end_date__isnull=True).first()
    

    如果您想避免额外的工作,编写查询的(可能)更好的方法:

    gateway_devices = (
        GatewayDevice.objects
        .filter(end_date__isnull=True)
        .select_related("gateway", "device")
    )
    

    这将在一个查询中获取所有内容,您只需使用简单的属性访问即可获取相关对象而无需任何额外查询,例如:

    for gateway_device in gateway_devices:
       print(gateway_device.device.name)
    

    查看您的模型(尽管我看不到完美编写此代码所需的所有内容),您似乎还需要来自 Device 关系的一些信息,因此您可能需要对此进行调整以使 device 预取什么你需要,比如:

    gateway_devices = (
        GatewayDevice.objects
        .filter(end_date_isnull=True)
        .select_related("gateway")
        .prefetch_related(
            Prefetch(
                "device",
                queryset=Device.objects.prefetch_related(
                    # Depending how these are related you may be able to
                    # save some queries using `select_related()` instead.
                    "site__users", "software_update_history", "supplier"
                )
            )
        )
    )
    

    这可能并不完美,但它应该为您提供良好开端所需的一切。

    【讨论】:

    • 没有其他方法可以通过.first()获取网关设备吗?
    • 当然还有其他方法,但这取决于你想做什么。问题是可以有任意数量的网关设备,并且不清楚您的逻辑应该是什么。
    • 如您所见,它指的是“GatewayDevice”。这是 SQL 中的一个表,其中一个设备可以链接到许多网关。但是对于每台设备,只有一个没有“end_date”的网关,即当前网关。我只需要为每台设备显示该网关
    • 我为此添加了一些内容。您可以使用.frst().get()。不同之处在于.get() 将在没有匹配行时引发异常。
    • 但是不可能在查询中最后一个 .first() 来减少查询量?
    猜你喜欢
    • 2018-12-15
    • 1970-01-01
    • 1970-01-01
    • 2017-02-10
    • 1970-01-01
    • 1970-01-01
    • 2017-04-24
    • 2018-08-17
    • 2017-10-12
    相关资源
    最近更新 更多