programing

Django Rest Framework 일련 번호의 집계(및 주석이 달린 기타) 필드

subpage 2023. 5. 9. 22:45
반응형

Django Rest Framework 일련 번호의 집계(및 주석이 달린 기타) 필드

집계된(계산된) 필드와 같은 주석이 달린 필드를 DRF(모델)에 추가하는 가장 좋은 방법을 찾고 있습니다.시리얼라이저.사용 사례는 데이터베이스에 저장되지 않고 데이터베이스에서 계산된 필드를 엔드포인트가 반환하는 상황입니다.

다음 예를 살펴보겠습니다.

models.py

class IceCreamCompany(models.Model):
    name = models.CharField(primary_key = True, max_length = 255)

class IceCreamTruck(models.Model):
    company = models.ForeignKey('IceCreamCompany', related_name='trucks')
    capacity = models.IntegerField()

serializers.py

class IceCreamCompanySerializer(serializers.ModelSerializer):
    class Meta:
        model = IceCreamCompany

원하는 JSON 출력:

[

    {
        "name": "Pete's Ice Cream",
        "total_trucks": 20,
        "total_capacity": 4000
    },
    ...
]

작동하는 솔루션이 몇 가지 있지만 각각 몇 가지 문제가 있습니다.

옵션 1: 모델에 게터 추가 및 SerializerMethodFields 사용

models.py

class IceCreamCompany(models.Model):
    name = models.CharField(primary_key=True, max_length=255)

    def get_total_trucks(self):
        return self.trucks.count()

    def get_total_capacity(self):
        return self.trucks.aggregate(Sum('capacity'))['capacity__sum']

serializers.py

class IceCreamCompanySerializer(serializers.ModelSerializer):

    def get_total_trucks(self, obj):
        return obj.get_total_trucks

    def get_total_capacity(self, obj):
        return obj.get_total_capacity

    total_trucks = SerializerMethodField()
    total_capacity = SerializerMethodField()

    class Meta:
        model = IceCreamCompany
        fields = ('name', 'total_trucks', 'total_capacity')

위의 코드는 약간 리팩터링될 수 있지만, 이 옵션이 IceCream Company당 2개의 추가 SQL 쿼리를 수행한다는 사실은 그다지 효율적이지 않습니다.

옵션 2: ViewSet.get_queryset에서 주석 달기

원래 설명한 대로 models.py .

views.py

class IceCreamCompanyViewSet(viewsets.ModelViewSet):
    queryset = IceCreamCompany.objects.all()
    serializer_class = IceCreamCompanySerializer

    def get_queryset(self):
        return IceCreamCompany.objects.annotate(
            total_trucks = Count('trucks'),
            total_capacity = Sum('trucks__capacity')
        )

이렇게 하면 단일 SQL 쿼리에서 집계된 필드를 얻을 수 있지만 DRF가 쿼리 집합에서 이러한 필드에 주석을 달았다는 것을 마법처럼 알지 못하기 때문에 이 필드를 직렬화기에 어떻게 추가해야 할지 잘 모르겠습니다.serializer에 total_trucks 및 total_capacity를 추가하면 모델에 이러한 필드가 없다는 오류가 발생합니다.

옵션 2는 View를 사용하여 serializer 없이도 작동할 수 있지만 모델에 필드가 많고 일부만 JSON에 있어야 하는 경우 serializer 없이 엔드포인트를 구축하는 것은 다소 추악한 해킹입니다.

가능한 해결책:

views.py

class IceCreamCompanyViewSet(viewsets.ModelViewSet):
    queryset = IceCreamCompany.objects.all()
    serializer_class = IceCreamCompanySerializer

    def get_queryset(self):
        return IceCreamCompany.objects.annotate(
            total_trucks=Count('trucks'),
            total_capacity=Sum('trucks__capacity')
        )

serializers.py

class IceCreamCompanySerializer(serializers.ModelSerializer):
    total_trucks = serializers.IntegerField()
    total_capacity = serializers.IntegerField()

    class Meta:
        model = IceCreamCompany
        fields = ('name', 'total_trucks', 'total_capacity')

Serializer 필드를 사용하여 작업할 수 있는 작은 예제를 얻었습니다.DRF가 IceCream Company 모델에 존재하지 않는 필드에 대한 오류를 발생시키지 않도록 필드를 직렬화 프로그램의 클래스 속성으로 선언해야 합니다.

쿼리 집합을 정의할 때 주석을 달아 엘니그린의 답변을 약간 단순화했습니다.그럼 제가 오버라이드 할 필요는 없습니다.get_queryset().

# views.py
class IceCreamCompanyViewSet(viewsets.ModelViewSet):
    queryset = IceCreamCompany.objects.annotate(
            total_trucks=Count('trucks'),
            total_capacity=Sum('trucks__capacity'))
    serializer_class = IceCreamCompanySerializer

# serializers.py
class IceCreamCompanySerializer(serializers.ModelSerializer):
    total_trucks = serializers.IntegerField()
    total_capacity = serializers.IntegerField()

    class Meta:
        model = IceCreamCompany
        fields = ('name', 'total_trucks', 'total_capacity')

Elnygreen이 말했듯이, 필드는 아이스크림 회사 모델에 존재하지 않는 오류를 방지하기 위해 시리얼라이저의 클래스 속성으로 선언되어야 합니다.

ModelSerializer 생성자를 해킹하여 뷰 또는 뷰 세트가 전달하는 쿼리 세트를 수정할 수 있습니다.

class IceCreamCompanySerializer(serializers.ModelSerializer):
    total_trucks = serializers.IntegerField(readonly=True)
    total_capacity = serializers.IntegerField(readonly=True)

    class Meta:
        model = IceCreamCompany
        fields = ('name', 'total_trucks', 'total_capacity')

    def __new__(cls, *args, **kwargs):
        if args and isinstance(args[0], QuerySet):
              queryset = cls._build_queryset(args[0])
              args = (queryset, ) + args[1:]
        return super().__new__(cls, *args, **kwargs)

    @classmethod
    def _build_queryset(cls, queryset):
         # modify the queryset here
         return queryset.annotate(
             total_trucks=...,
             total_capacity=...,
         )

이름에 의미가 없습니다._build_queryset(아무것도 무시하지 않습니다), 그것은 우리가 생성자로부터 부풀음을 방지할 수 있게 해줍니다.

언급URL : https://stackoverflow.com/questions/31920853/aggregate-and-other-annotated-fields-in-django-rest-framework-serializers

반응형