Django Rest Framework упорядочивание по количеству отфильтрованных предварительно найденных связанных объектов

Я надеюсь, что кто-нибудь сможет мне помочь. Я работаю с Django Rest Framework и пытаюсь создать API, который позволяет пользователям искать Providers, которые предоставляют конкретные Procedures в определенных Regions, и возвращать только соответствующие детали.

Установить

У меня есть такие модели (сильно упрощенные):

# models.py

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

class Region(models.Model):
    name = models.CharField(max_length=100)

    class Meta:
        ordering = ["name"]

class Procedure(models.Model):
    description = models.CharField(max_length=100)

    class Meta:
        ordering = ["description"]

class Provider(models.Model):
    provider_name = models.CharField(max_length=200)

    class Meta:
        ordering = ["provider_name"]

class Provision(models.Model):
    fk_procedure = models.ForeignKey(
        Procedure,
        related_name="providers",
        on_delete=models.RESTRICT,
    )
    fk_provider = models.ForeignKey(
        Provider,
        related_name="services",
        on_delete=models.CASCADE,
    )
    discount = models.FloatField(
        validators=[MaxValueValidator(100), MinValueValidator(0)],
        default=0,
    )
    
    class Meta:
        ordering = ["-discount"]
        unique_together = ["fk_procedure", "fk_provider"]

class ProvisionLinkRegion(models.Model):
    fk_provision = models.ForeignKey(
        Provision,
        related_name="regions",
        on_delete=models.CASCADE,
    )
    fk_region = models.ForeignKey(
        Region,
        related_name="services",
        on_delete=models.RESTRICT,
    )
    location = models.BooleanField(default=False)

Как вы видите, между Provision и Region существует связь ManyToMany через ProvisionLinkRegion. Однако я не определил это поле как поле ManyToMany, поскольку мне нужно хранить дополнительные детали (location) о сопряжении.

Я определил следующие сериализаторы на этих моделях:

# serializers.py

from rest_framework import serializers
from models import (
    Provider,
    Region,
    Procedure,
    ProvisionLinkRegion,
    Provision,
)

class ProvisionLinkRegionSerializer(serializers.ModelSerializer):
    class Meta:
        model = ProvisionLinkRegion
        fields = ["fk_region", "location"]

class ProvisionDetailsSerializer(serializers.ModelSerializer):
    regions = ProvisionLinkRegionSerializer(many=True)

    class Meta:
        model = Provision
        fields = ["fk_procedure", "discount", "mff_opt_out", "regions"]

class ProviderProvisionSerializer(serializers.ModelSerializer):
    services = ProvisionDetailsSerializer(many=True) 
    number_services = serializers.IntegerField()

    class Meta:
        model = Provider
        fields = [
            "provider_name",
            "services",
            "number_services",
        ]

И определил мой API следующим образом:

Моя проблема в том, что значение number_services не является правильным, поскольку он не выполняет подсчет для всех отфильтрованных результатов. Он делает это только для предварительно отфильтрованных (хотя фильтр региона работает). Я также не хочу, чтобы появлялись провайдеры, у которых нет никаких услуг (отсюда .filter(number_services__gt=0)).

Я думаю, что это связано с тем, что я не фильтрую основной набор запросов Provider, как я это делаю с регионом, т.е. чтобы включить:

queryset = queryset.filter(services__fk_procedure__in=procedure_list)

Но когда я включаю это, на самом деле удаляются не все услуги, а только провайдеры, которые не предоставляют НИ ОДНОЙ из этих услуг, поэтому подсчет все еще не верен.

Пример

Если мои данные без фильтрации или предварительной выборки выглядят следующим образом:

    "results": [
        {
            "provider_name": "Provider 2.0",
            "services": [
                {
                    "fk_procedure": 3,
                    "discount": 0.05,
                    "regions": [
                        {
                            "fk_region": 1,
                            "location": true
                        },
                        {
                            "fk_region": 2,
                            "location": false
                        }
                        {
                            "fk_region": 3,
                            "location": true
                        }
                    ]
                },
                {
                    "fk_procedure": 5,
                    "discount": 0.05,
                    "regions": [
                        {
                            "fk_region": 1,
                            "location": true
                        }
                    ]
                }
            ]
        },
        {
            "provider_name": "Test Provider",
            "services": [
                {
                    "fk_procedure": 2,
                    "discount": 0.00,
                    "regions": [
                        {
                            "fk_region": 1,
                            "location": true
                        }
                    ]
                }
            ]
        }
    ]

Если я затем запустил это на нем:

GET /api/v1/provision?page=1&limit=10&region=1,3&services=3`

Я хочу показать всех провайдеров и соответствующие детали, если они относятся к региону 1 или 3, и процедуре 3.

Актуальный результат

{
    "pagination": {
        "previous": null,
        "next": null,
        "count": 2,
        "current_page": 1,
        "total_pages": 1,
        "items_on_page": 2
    },
    "results": [
        {
            "provider_name": "Provider 2.0",
            "services": [
                {
                    "fk_procedure": 3,
                    "discount": 0.05,
                    "regions": [
                        {
                            "fk_region": 1,
                            "location": true
                        },
                        {
                            "fk_region": 3,
                            "location": true
                        }
                    ]
                }
            ],
            "number_services": 2
        },
        {
            "provider_name": "Test Provider",
            "services": [],
            "number_services": 1
        }
    ]
}

Желаемый результат

{
    "pagination": {
        "previous": null,
        "next": null,
        "count": 2,
        "current_page": 1,
        "total_pages": 1,
        "items_on_page": 2
    },
    "results": [
        {
            "provider_name": "Provider 2.0",
            "services": [
                {
                    "fk_procedure": 3,
                    "discount": 0.05,
                    "regions": [
                        {
                            "fk_region": 1,
                            "location": true
                        },
                        {
                            "fk_region": 3,
                            "location": true
                        }
                    ]
                }
            ],
            "number_services": 1,
        }
    ]
}

То, что я пробовал

SerializerMethodField

Я смог заставить number_services работать с использованием SerializerMethodField, включив:

# serializers.py

class ProviderProvisionSerializer(serializers.ModelSerializer):
    ...
    number_services = serializers.SerializerMethodField()
    ...
    class Meta:
        ...

    def get_number_services(self, obj):
        return obj.services.count()

К сожалению, я не могу использовать это для упорядочивания или фильтрации в API, и я также не могу использовать это с пагинацией, так что это довольно бесполезно для того, для чего мне это нужно.

Подзапрос

В методе get_queryset для API я также пробовал использовать то, что у меня сейчас есть в качестве подзапроса, и использовать основной набор запросов, где ID находится в другом, но тогда я теряю всю предварительную выборку из первого подзапроса и получаю регионы и услуги, которые не относятся к моему фильтру.

## TLDR

Как мне отфильтровать набор запросов в методе get_queryset для ListAPIView по свойствам дочерних элементов основной модели и иметь возможность вернуть подсчет оставшихся дочерних элементов после фильтрации?

Вернуться на верх