Кверисет с сопоставленными/слитыми значениями
Допустим, у меня есть следующие две модели:
class Parent(models.Model):
name = models.CharField(max_length=48)
class Child(models.Model):
name = models.CharField(max_length=48)
movement = models.ForeignKey(Parent, related_name='children')
У меня есть следующий DRF generics.ListAPIView
, в котором я хочу иметь возможность искать/фильтровать по Child
объектам, но на самом деле возвращать связанные Parent
объекты:
class ParentSearchByChildNameView(generics.ListAPIView):
"""
Return a list of parents who have a child with the given name
"""
serializer_class = ParentSerializer
def get_queryset(self):
child_name = self.request.query_params.get('name')
queryset = Child.objects.all()
if child_name is not None:
queryset = queryset.filter(name__contains=child_name)
matched_parents = Parent.objects.filter(children__in=queryset).distinct()
return matched_parents
Теперь, это хорошо работает для меня. Если объект Parent
имеет 3 объекта Child
, которые все соответствуют заданному "имени" query_param
, то я получаю обратно только один объект Parent
, что мне и нужно:
{
"next": null,
"previous": null,
"results": [
{
"url": "URL to parent",
"name": "Parent #1",
}
]
}
Однако я также хочу, чтобы в результате были указаны идентификаторы совпавших объектов Child
. Если я могу проиллюстрировать в JSON то, что я хочу:
{
"next": null,
"previous": null,
"results": [
{
"url": "URL to parent",
"name": "Parent #1",
"matched_child": [1, 3, 7]
}
]
}
Могу ли я сделать это с помощью встроенных инструментов, без дорогостоящего и многократного нанесения ударов по базе данных?
Если вы работаете с postgresql, вы можете работать с выражением ArrayAgg
[Django-doc]:
from django.contrib.postgres.aggregates import ArrayAgg
class ParentSearchByChildNameView(generics.ListAPIView):
"""
Return a list of parents who have a child with the given name
"""
serializer_class = ParentSerializer
def get_queryset(self):
child_name = self.request.query_params.get('name')
return = Parent.objects.filter(
children__name__contains=child_name
).annotate(matched_children=ArrayAgg('children__pk'))
В сериализаторе вы добавляете ListField
, который будет перечислять дочерние элементы с:
class ParentSerializer(serializers.ModelSerializer)
matched_children = serializers.ListField(
child=serializers.IntegerField(),
read_only=True
)
class Meta:
model = Parent
fields = ['url', 'name', 'matched_children']
Не используя специфическую для базы данных функцию, вы можете работать с объектом Prefetch
[Django-doc]:
from django.db.models import Prefetch
class ParentSearchByChildNameView(generics.ListAPIView):
"""
Return a list of parents who have a child with the given name
"""
serializer_class = ParentSerializer
def get_queryset(self):
child_name = self.request.query_params.get('name')
return = Parent.objects.filter(
children__name__contains=child_name
).prefetch_related(
Prefetch('children', Child.objects.filter(name=child_name), to_attr='matched_children')
).distinct()
Для сериализатора мы можем работать с PrimaryKeyRelatedField
[drf-doc]:
class ParentSerializer(serializers.ModelSerializer)
matched_children = serializers.PrimaryKeyRelatedField(
many=True,
read_only=True
)
class Meta:
model = Parent
fields = ['url', 'name', 'matched_children']