Django Rest Framework упорядочивание по количеству отфильтрованных предварительно найденных связанных объектов
Я надеюсь, что кто-нибудь сможет мне помочь. Я работаю с Django Rest Framework и пытаюсь создать API, который позволяет пользователям искать Provider
s, которые предоставляют конкретные Procedure
s в определенных Region
s, и возвращать только соответствующие детали.
Установить
У меня есть такие модели (сильно упрощенные):
# 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®ion=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
по свойствам дочерних элементов основной модели и иметь возможность вернуть подсчет оставшихся дочерних элементов после фильтрации?