Кверисет с сопоставленными/слитыми значениями

Допустим, у меня есть следующие две модели:

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]
        }
    ]
}

Могу ли я сделать это с помощью встроенных инструментов, без дорогостоящего и многократного нанесения ударов по базе данных?

Если вы работаете с , вы можете работать с выражением 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']
Вернуться на верх