Django Rest Framework에서 중첩 된 직렬 변환기를 어떻게 필터링합니까?


84

Django Rest Framework에서 다른 serializer에 중첩되어있을 때 serializer를 어떻게 필터링합니까?

내 필터는 DRF 뷰셋에 적용되지만 다른 시리얼 라이저 내부에서 시리얼 라이저를 호출하면 중첩 된 시리얼 라이저의 뷰셋이 호출되지 않으므로 중첩 된 결과가 필터링되지 않은 것처럼 보입니다.

원래 뷰셋에 필터를 추가하려고 시도했지만 중첩 된 결과가 별도의 미리 분할 된 쿼리로 호출되기 때문에 중첩 된 결과를 필터링하지 않는 것 같습니다. (중첩 된 직렬 변환기는 역방향 조회입니다.)

중첩 된 serializer 자체에 get_queryset () 재정의를 추가하여 (뷰셋 밖으로 이동) 거기에 필터를 추가 할 수 있습니까? 나도 그것을 시도했지만 운이 없었다.

이것은 내가 시도한 것이지만 호출되지 않는 것 같습니다.

class QuestionnaireSerializer(serializers.ModelSerializer):
    edition = EditionSerializer(read_only=True)
    company = serializers.StringRelatedField(read_only=True)

    class Meta:
        model = Questionnaire

    def get_queryset(self):
        query = super(QuestionnaireSerializer, self).get_queryset(instance)
        if not self.request.user.is_staff:
            query = query.filter(user=self.request.user, edition__hide=False)
        return query

6
get_queryset에 클래스 ModelViewSet없는 이유가 호출 점점 아니에요 인 시리얼에,
NotSimon

답변:


99

ListSerializer 를 하위 클래스로 만들고to_representation 메서드를 덮어 쓸 수 있습니다 .

기본적으로 to_representation메서드 data.all()는 중첩 된 쿼리 집합을 호출 합니다. 따라서 data = data.filter(**your_filters)메서드가 호출되기 전에 효과적으로 만들어야 합니다. 그런 다음 하위 클래스 화 된 ListSerializer를 중첩 된 serializer의 메타에 list_serializer_class로 추가해야합니다.

  1. ListSerializer 하위 클래스, 덮어 쓰기 to_representation후 super 호출
  2. list_serializer_class중첩 된 Serializer에 메타로 하위 클래스 ListSerializer 추가

다음은 샘플 관련 코드입니다.

class FilteredListSerializer(serializers.ListSerializer):

    def to_representation(self, data):
        data = data.filter(user=self.context['request'].user, edition__hide=False)
        return super(FilteredListSerializer, self).to_representation(data)


class EditionSerializer(serializers.ModelSerializer):

    class Meta:
        list_serializer_class = FilteredListSerializer
        model = Edition


class QuestionnaireSerializer(serializers.ModelSerializer):
    edition = EditionSerializer(read_only=True)
    company = serializers.StringRelatedField(read_only=True)

    class Meta:
        model = Questionnaire

1
그게 속임수였습니다! 결국 내 직렬 변환기가 너무 복잡해지기로 결정하고 모두 리팩토링하여 클라이언트가 몇 가지 API 호출을 더 실행하도록 강요했지만 내 앱을 크게 단순화했습니다.
John

3
이것을 유사한 문제에 대한 해결책의 기초로 사용하려고합니다. 자체 질문이 정말 좋은지 확실하지 않습니다. QuestionnaireSerializerListSerializer로 var를 어떻게 전달 합니까? 대략적으로 알아 보려면 Edition의 ID와 설문지의 ID로 필터링해야합니다.
Brendan 2015

3
이것은 DRF 문서에 있어야합니다. 매우 유용한 감사합니다!
Daniel van Flymen 2015

7
내 구현에서 'FilteredListSerializer' object has no attribute 'request'다른 사람이 똑같이 받고 있습니까?
Dominooch

11
당신이 사용 self.context [ '요청'] 대신 self.request 필요 @Dominooch 대답하기
rojoca

25

SO 및 기타 장소에서 많은 솔루션을 테스트했습니다.

Django 2.0 + DRF 3.7.7에 대한 작동 솔루션을 하나만 찾았습니다.

중첩 된 클래스가있는 모델에서 메서드를 정의합니다. 필요에 맞는 필터를 만드십시오.

class Channel(models.Model):
    name = models.CharField(max_length=40)
    number = models.IntegerField(unique=True)
    active = models.BooleanField(default=True)

    def current_epg(self):
        return Epg.objects.filter(channel=self, end__gt=datetime.now()).order_by("end")[:6]


class Epg(models.Model):
    start = models.DateTimeField()
    end = models.DateTimeField(db_index=True)
    title = models.CharField(max_length=300)
    description = models.CharField(max_length=800)
    channel = models.ForeignKey(Channel, related_name='onair', on_delete=models.CASCADE)

.

class EpgSerializer(serializers.ModelSerializer):
    class Meta:
        model = Epg
        fields = ('channel', 'start', 'end', 'title', 'description',)


class ChannelSerializer(serializers.ModelSerializer):
    onair = EpgSerializer(many=True, read_only=True, source="current_epg")

    class Meta:
        model = Channel
        fields = ('number', 'name', 'onair',)

주의를 기울 source="current_epg"이면 요점을 알 수 있습니다.


예! 이 주석은 소스의 기능을 활용하여 모델에서 정의한 다음 거기에서 필터링을 활용할 수 있습니다! 멋있는!
possumkeys

클래스 아래의 함수에 문자열을 전달할 수 있습니까?
AlexW

나는 많은 관련 분야의 주문 만 필요했습니다. 또한 많은 다른 솔루션을 시도했습니다 (말장난 의도). 그러나 이것은 나를 위해 일한 유일한 해결책이었습니다! 감사!
gabn88

이것은 받아 들여진 대답보다 django의 코드 철학 측면에서 더 정확한 해결책 인 것 같습니다. Django는 ActiveModel ( "fat models") 접근 방식을 제안하므로 필터링은 모델 수준 (또는 뷰셋 수준)에서 이루어져야하며 직렬화는 비즈니스 로직에 대해 아무것도 알지 못합니다.
oxfn

14

위의 모든 답변이 작동하는 동안 Django의 Prefetch개체를 사용하는 것이 가장 쉬운 방법이라는 것을 알았 습니다.

발언권 Restaurantobj가 많이 가지고 MenuItem있는 일부들, is_remove == True그리고 당신은 단지 제거되지 않은 사람을 원한다.

에서 RestaurantViewSet다음과 같이하십시오.

from django.db.models import Prefetch

queryset = Restaurant.objects.prefetch_related(
    Prefetch('menu_items', queryset=MenuItem.objects.filter(is_removed=False), to_attr='filtered_menu_items')
)

에서 RestaurantSerializer다음과 같이하십시오.

class RestaurantSerializer(serializers.ModelSerializer):
    menu_items = MenuItemSerializer(source='filtered_menu_items', many=True, read_only=True)


2
훌륭한 솔루션, 이것이 해결하는 가장 좋은 방법이라는 데 동의합니다.
요르단

이것은 맨 위에 있어야합니다. 현재 최상위 솔루션은 데이터베이스에서 이미 가져온 후 to_representation으로 데이터를 필터링합니다. 이 솔루션은 쿼리의 데이터를 필터링하고 대량 요청으로 가져옵니다. 대부분의 경우에 훨씬 좋습니다.
Alex

이것은 많은 도움이되었습니다. 감사합니다!
물론

7

serializer가 인스턴스화되고 many = True가 전달되면 ListSerializer 인스턴스가 생성됩니다. 그러면 serializer 클래스가 부모 ListSerializer의 자식이됩니다.

이 메서드는 필드의 대상을 값 인수로 사용하고 대상을 직렬화하는 데 사용해야하는 표현을 반환해야합니다. 일반적으로 값 인수는 모델 인스턴스입니다.

다음은 중첩 된 직렬 변환기의 예입니다.

class UserSerializer(serializers.ModelSerializer):
    """ Here many=True is passed, So a ListSerializer instance will be 
     created"""
    system = SystemSerializer(many=True, read_only=True)

    class Meta:
        model = UserProfile
        fields = ('system', 'name')

class FilteredListSerializer(serializers.ListSerializer):
    
    """Serializer to filter the active system, which is a boolen field in 
       System Model. The value argument to to_representation() method is 
      the model instance"""
    
    def to_representation(self, data):
        data = data.filter(system_active=True)
        return super(FilteredListSerializer, self).to_representation(data)

class SystemSerializer(serializers.ModelSerializer):
    mac_id = serializers.CharField(source='id')
    system_name = serializers.CharField(source='name')
    serial_number = serializers.CharField(source='serial')

    class Meta:
        model = System
        list_serializer_class = FilteredListSerializer
        fields = (
            'mac_id', 'serial_number', 'system_name', 'system_active', 
        )

보기 :

class SystemView(viewsets.GenericViewSet, viewsets.ViewSet):
    def retrieve(self, request, email=None):
        data = get_object_or_404(UserProfile.objects.all(), email=email)
        serializer = UserSerializer(data)
        return Response(serializer.data)

5

SerializerMethodField필터링하려는 serializer 필드에서 를 사용하는 것이 더 쉽고 간단합니다 .

그래서 당신은 이렇게 할 것입니다.

class CarTypesSerializer(serializers.ModelSerializer):

    class Meta:
        model = CarType
        fields = '__all__'


class CarSerializer(serializers.ModelSerializer):

    car_types = serializers.SerializerMethodField()

    class Meta:
        model = Car
        fields = '__all__'

    def get_car_types(self, instance):
        # Filter using the Car model instance and the CarType's related_name
        # (which in this case defaults to car_types_set)
        car_types_instances = instance.car_types_set.filter(brand="Toyota")
        return CarTypesSerializer(car_types_instances, many=True).data

이렇게하면 serializers.ListSerializer서로 다른 serializer에 대해 서로 다른 필터링 기준이 필요한 경우의 재정의를 많이 만들지 않아도됩니다.

또한 하위 클래스 정의로 들어가는 대신 직렬 변환기 내에서 필터가 수행하는 작업을 정확히 확인할 수있는 추가 이점도 있습니다.

물론 어떤 식 으로든 필터링해야하는 중첩 된 개체가 많은 serializer가있는 경우 단점이 있습니다. 직렬 변환기 코드가 크게 증가 할 수 있습니다. 필터링 방법은 귀하에게 달려 있습니다.

도움이 되었기를 바랍니다!

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.