DRF выборка на основе поля другой модели

Необходим метод GET, куда я могу передать id объекта Client и получить все объекты Detection, связанные сним. Не могу понять как добитсья этого в рамках DRF. Detection относится к Stream, а Stream относится к Client связью многие к одному.

views.py

from django.shortcuts import get_object_or_404
from django.shortcuts import render
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from rest_framework_api_key.permissions import HasAPIKey

from fire_smoke import models
from fire_smoke import serializers


class ClientViewSet(viewsets.ModelViewSet):
    """
    Клиент
    """
    queryset = models.Client.objects.all().order_by("-created_at")
    serializer_class = serializers.ClientSerializer
    permission_classes = [HasAPIKey | IsAuthenticated]
    filter_backends = [DjangoFilterBackend]
    filterset_fields = "__all__"


class StreamViewSet(viewsets.ModelViewSet):
    """
    Стрим
    """
    queryset = models.Stream.objects.all().order_by("-created_at")
    serializer_class = serializers.StreamSerializer
    permission_classes = [HasAPIKey | IsAuthenticated]
    filter_backends = [DjangoFilterBackend]
    filterset_fields = "__all__"


class DetectionViewSet(viewsets.ModelViewSet):
    """
    Детекция
    """
    queryset = models.Detection.objects.all().order_by("-created_at")
    serializer_class = serializers.DetectionSerializer
    permission_classes = [HasAPIKey | IsAuthenticated]
    filter_backends = [DjangoFilterBackend]
    filterset_fields = ["stream_id", "count_smoke", "count_fire", "created_at"]

serializers.py

from rest_framework import serializers
from fire_smoke import models


class ClientSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Client
        fields = "__all__"


class StreamSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Stream
        fields = "__all__"


class DetectionSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Detection
        fields = "__all__"

models.py

from django.core.validators import MaxValueValidator
from django.core.validators import MinValueValidator
from django.db import models


class Client(models.Model):
    """
    Клиент(пользователь)
    """
    username = models.CharField(max_length=30, unique=True)
    created_at = models.DateTimeField(
        auto_now=False,
        auto_now_add=True,
        verbose_name="Дата создания",
        blank=True,
    )
    updated_at = models.DateTimeField(
        auto_now=True, auto_now_add=False, verbose_name="Дата изменения"
    )
    def __str__(self):
        return f"{self.username}({self.id})"

    class Meta:
        verbose_name = "Клиент"
        verbose_name_plural = "Клиенты"


class Stream(models.Model):
    """
    Поток
    """

    class ProtocolType(models.TextChoices):
        RTSP = "RTSP", ("RTSP")
        RTMP = "RTMP", ("RTMP")

    class DeviceTypeForPredict(models.TextChoices):
        CUDA = "cuda", ("cuda")
        CPU = "cpu", ("cpu")

    client = models.ForeignKey(
        Client,
        on_delete=models.CASCADE,
        null=False,
        verbose_name="Клиент"
    )

    stream_url = models.CharField(
        verbose_name="Ссылка на поток",
        help_text="Пример ссылки: rtsp://admin:[email protected]:6551/live/main",
        unique=True,
        max_length=256,
    )
    vid_stride = models.IntegerField(
        verbose_name="Частота опорных кадров",
        help_text="Если указать 1, то нейросетью будет анализироваться каждый кадр в потоке, если указать 50, то будет анализироваться каждый 50-ый кадр в потоке.",
        validators=[MaxValueValidator(3000), MinValueValidator(1)],
        default=1,
    )
    skip_frames = models.IntegerField(
        verbose_name="Задержка после детекции",
        help_text="Сколько кадров пропустить после того как произойдет регистрация любой детекции. Полезно для того чтобы бы не спамило дубликатами детекций. Если выставить 50, то при детекции пропустится 50*{Частота опорных кадров} кадров.",
        default=80,
    )
    device_uuid = models.CharField(
        max_length=86,
        unique=False,
        verbose_name="ID устройства",
        help_text="ID устройства с которого получаем поток",
    )
    device_name = models.CharField(
        max_length=100,
        default="",
        verbose_name="Имя устройства",
        help_text="Имя устройства с которого получаем поток",
        blank=True,
    )
    description = models.CharField(
        max_length=200,
        default="",
        verbose_name="Описание",
        help_text="Дополнительное описание",
        blank=True,
    )
    is_run = models.BooleanField(
        verbose_name="Распознование запущено",
        help_text="Работает или нет распознование по данному потоку в данный момент. Можно изменить эту опцию вручную, чтобы отключить/включить анализ потока. Этот флажок снимется самостоятельно, если произайдет какая то ошибка и анализ прервется.",
        default=True,
    )
    must_work = models.BooleanField(
        verbose_name="Должен работать",
        help_text='Должен ли работать поток в данный момент. Если True и "Распознование запущено" установлено в False, то система автоматически время от времени будет пытаться поднять соединение с потоком.',
        default=True,
    )    
    conf = models.FloatField(
        verbose_name="Коэффицент детекций",
        help_text="Если установить 0.4, то кадры будут помечаться как детекции только в случае если нейросеть уверенна минимум на 40% что это действительно дым/огонь",
        validators=[MaxValueValidator(0.99), MinValueValidator(0.01)],
        default=0.4,
    )
    protocol = models.CharField(
        verbose_name="Протокол",
        choices=ProtocolType.choices,
        help_text="Протокол потока",
    )
    device_for_predict = models.CharField(
        verbose_name="Устройство для распознавания",
        choices=DeviceTypeForPredict.choices,
        help_text="Через видеокарту распознаем или через процессор?",
        default=DeviceTypeForPredict.CPU,
    )
    save_fire = models.BooleanField(
        verbose_name="Распознавать огонь",
        help_text="Регистрировать детекцию только в случае если на кадре обнаружен хотя бы 1 истолчник огня.",
        default=True,
    )
    save_smoke = models.BooleanField(
        verbose_name="Распознавать дым",
        help_text="Регистрировать детекцию только в случае если на кадре обнаружен хотя бы 1 истолчник дыма.",
        default=False,
    )
    created_at = models.DateTimeField(
        auto_now=False,
        auto_now_add=True,
        verbose_name="Дата создания",
        blank=True,
    )
    updated_at = models.DateTimeField(
        auto_now=True, auto_now_add=False, verbose_name="Дата изменения"
    )
    process_id = models.CharField(
        null=True,
        blank=True,
        max_length=36,
        verbose_name="ID процесса",
        help_text="ID фонового процесса Celery",
    )
    last_error = models.TextField(
        verbose_name="Ошибка",
        help_text="Если работа потока аварийно завершится, то тут будет ошибка",
        default='',
        max_length=255,
    )

    def __str__(self):
        return f"{self.id} | {self.device_name}"

    class Meta:
        verbose_name = "Поток"
        verbose_name_plural = "Потоки"


class Detection(models.Model):
    """
    Детекции
    """

    stream_id = models.ForeignKey(
        Stream,
        on_delete=models.CASCADE,
        verbose_name="Поток",
    )
    img = models.ImageField(verbose_name="Снимок", null=True, blank=True)
    count_smoke = models.IntegerField(
        verbose_name="Детекций дыма",
        help_text="Счетчик детекций дыма на снимке",
        default=0,
    )
    count_fire = models.IntegerField(
        verbose_name="Детекций огня",
        help_text="Счетчик детекций огня на снимке",
        default=0,
    )
    created_at = models.DateTimeField(
        auto_now=False,
        auto_now_add=True,
        verbose_name="Дата создания",
        blank=True,
    )

    def __str__(self):
        return f"{self.id} | {self.stream_id}"

    class Meta:
        verbose_name = "Детекция"
        verbose_name_plural = "Детекции"

Ответы (0 шт):