Django: Prefetch_related для вложенных атрибутов - PullRequest
1 голос
/ 07 мая 2020

Мне нужно перечислить все мои устройства. Для этого я использую предварительную выборку, связанную с уменьшением количества запросов. Но один из них отнимает много времени .. Интересно, не может ли он go лучше.

Начну с построения моделей: мне нужен список устройств.

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'

1 Ответ

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

Прежде всего, вы должны иметь возможность использовать .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)

Это кажется странным, но кажется, что этот код должен быть с остальной частью запроса, если вы хотите исключить объекты без gateway s. Я думаю, вам следует полностью удалить 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"
            )
        )
    )
)

Это может быть не идеально, но должно дать вам то, что вам нужно для хорошего старта.

...