장고 휴식 프레임 워크, 동일한 ModelViewSet에서 다른 직렬 변환기 사용


196

두 가지 다른 직렬 변환기를 제공하고 있지만 다음과 같은 모든 기능을 활용할 수 있습니다 ModelViewSet.

  • 객체 목록을 볼 때 각 객체에 세부 정보로 리디렉션되는 URL이 __unicode __있고 대상 모델을 사용하여 다른 모든 관계가 나타납니다 .

예:

{
  "url": "http://127.0.0.1:8000/database/gruppi/2/",
  "nome": "universitari",
  "descrizione": "unitn!",
  "creatore": "emilio",
  "accesso": "CHI",
  "membri": [
    "emilio",
    "michele",
    "luisa",
    "ivan",
    "saverio"
  ]
}
  • 객체의 세부 정보를 볼 때 기본값을 사용하고 싶습니다 HyperlinkedModelSerializer

예:

{
  "url": "http://127.0.0.1:8000/database/gruppi/2/",
  "nome": "universitari",
  "descrizione": "unitn!",
  "creatore": "http://127.0.0.1:8000/database/utenti/3/",
  "accesso": "CHI",
  "membri": [
    "http://127.0.0.1:8000/database/utenti/3/",
    "http://127.0.0.1:8000/database/utenti/4/",
    "http://127.0.0.1:8000/database/utenti/5/",
    "http://127.0.0.1:8000/database/utenti/6/",
    "http://127.0.0.1:8000/database/utenti/7/"
  ]
}

다음과 같은 방법으로 원하는대로이 모든 작업을 수행 할 수있었습니다.

serializers.py

# serializer to use when showing a list
class ListaGruppi(serializers.HyperlinkedModelSerializer):
    membri = serializers.RelatedField(many = True)
    creatore = serializers.RelatedField(many = False)

    class Meta:
        model = models.Gruppi

# serializer to use when showing the details
class DettaglioGruppi(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = models.Gruppi

views.py

class DualSerializerViewSet(viewsets.ModelViewSet):
    """
    ViewSet providing different serializers for list and detail views.

    Use list_serializer and detail_serializer to provide them
    """
    def list(self, *args, **kwargs):
        self.serializer_class = self.list_serializer
        return viewsets.ModelViewSet.list(self, *args, **kwargs)

    def retrieve(self, *args, **kwargs):
        self.serializer_class = self.detail_serializer
        return viewsets.ModelViewSet.retrieve(self, *args, **kwargs)

class GruppiViewSet(DualSerializerViewSet):
    model = models.Gruppi
    list_serializer = serializers.ListaGruppi
    detail_serializer = serializers.DettaglioGruppi

    # etc.

기본적으로 사용자가 목록보기 또는 상세보기를 요청할 때 감지하고 serializer_class내 요구에 맞게 변경 합니다. 나는이 코드에 실제로 만족하지 않습니다. 더티 해킹처럼 보이며 가장 중요한 것은 두 명의 사용자가 동시에 목록과 세부 정보를 요청하면 어떻게됩니까?

이것을 사용하여 더 좋은 방법이 있습니까? ModelViewSets아니면 사용 하지 않아야 GenericAPIView합니까?

편집 :
다음은 사용자 정의 기반을 사용하여 수행하는 방법입니다 ModelViewSet.

class MultiSerializerViewSet(viewsets.ModelViewSet):
    serializers = { 
        'default': None,
    }

    def get_serializer_class(self):
            return self.serializers.get(self.action,
                        self.serializers['default'])

class GruppiViewSet(MultiSerializerViewSet):
    model = models.Gruppi

    serializers = {
        'list':    serializers.ListaGruppi,
        'detail':  serializers.DettaglioGruppi,
        # etc.
    }

최종적으로 어떻게 구현 했습니까? user2734679에서 제안한 방법을 사용하거나 GenericAPIView를 사용합니까?
andilabs

사용자가 제안한대로 2734679; 각 액션에 대한 시리얼 라이저와 지정되지 않은 경우 기본 시리얼 라이저를 지정하는 사전을 추가하는 일반 ViewSet을 만들었습니다.
BlackBear

나는 비슷한 문제가 있고 ( stackoverflow.com/questions/24809737/… ) 지금은 그것으로 끝납니다 ( gist.github.com/andilab/a23a6370bd118bf5e858 ), 나는 그것에 매우 만족하지 않습니다.
andilabs

1
이를 위해이 작은 패키지를 만들었습니다. github.com/Darwesh27/drf-custom-viewsets
Adil Malik

1
재정의 검색 방법은 정상입니다.
gzerone

답변:


288

get_serializer_class방법을 재정의하십시오 . 이 메소드는 모델 믹스 인에서 올바른 Serializer 클래스를 검색하는 데 사용됩니다.

올바른 Serializer get_serializer인스턴스 를 반환하는 메소드 도 있습니다.

class DualSerializerViewSet(viewsets.ModelViewSet):
    def get_serializer_class(self):
        if self.action == 'list':
            return serializers.ListaGruppi
        if self.action == 'retrieve':
            return serializers.DettaglioGruppi
        return serializers.Default # I dont' know what you want for create/destroy/update.                

1
대단합니다, 감사합니다! 그래도 get_serializer_class를 재정의했습니다
BlackBear

15
경고 : django rest swagger는 self.action 매개 변수를 배치하지 않으므로이 함수는 예외를 발생시킵니다. 당신은 gonz의 답변을 사용하거나 사용할 수 있습니다if hasattr(self, 'action') and self.action == 'list'
Tom Leys

이를 위해 작은 pypi 패키지를 작성하십시오. github.com/Darwesh27/drf-custom-viewsets
Adil Malik

pk작업이 요청 된 경우 of of object를 어떻게 요청 retrieve합니까?
Pranjal Mittal

내 self.action은 None입니다. 누군가 이유를 말해 줄 수 있습니까?
카카 지

86

이 믹스 인이 유용하다는 것을 알 수 있으며 get_serializer_class 메소드를 대체하고 조치 및 직렬화 기 클래스를 맵핑하거나 일반적인 동작으로 대체하는 dict를 선언 할 수 있습니다.

class MultiSerializerViewSetMixin(object):
    def get_serializer_class(self):
        """
        Look for serializer class in self.serializer_action_classes, which
        should be a dict mapping action name (key) to serializer class (value),
        i.e.:

        class MyViewSet(MultiSerializerViewSetMixin, ViewSet):
            serializer_class = MyDefaultSerializer
            serializer_action_classes = {
               'list': MyListSerializer,
               'my_action': MyActionSerializer,
            }

            @action
            def my_action:
                ...

        If there's no entry for that action then just fallback to the regular
        get_serializer_class lookup: self.serializer_class, DefaultSerializer.

        """
        try:
            return self.serializer_action_classes[self.action]
        except (KeyError, AttributeError):
            return super(MultiSerializerViewSetMixin, self).get_serializer_class()

이를 위해이 작은 패키지를 만들었습니다. github.com/Darwesh27/drf-custom-viewsets
Adil Malik

15

이 답변은 허용되는 답변과 동일하지만이 방법으로 수행하는 것을 선호합니다.

일반 뷰

get_serializer_class(self):

직렬화기에 사용해야하는 클래스를 반환합니다. 기본적으로 serializer_class속성 을 반환 합니다.

읽기 및 쓰기 작업에 다른 직렬 변환기를 사용하거나 다른 유형의 사용자에게 다른 직렬 변환기를 제공하는 등의 동적 동작을 제공하도록 재정의 될 수 있습니다. serializer_class 속성

class DualSerializerViewSet(viewsets.ModelViewSet):
    # mapping serializer into the action
    serializer_classes = {
        'list': serializers.ListaGruppi,
        'retrieve': serializers.DettaglioGruppi,
        # ... other actions
    }
    default_serializer_class = DefaultSerializer # Your default serializer

    def get_serializer_class(self):
        return self.serializer_classes.get(self.action, self.default_serializer_class)

뷰에 속성 "action"이 없음을 알려주므로 사용할 수 없습니다. ProductIndex (generics.ListCreateAPIView)처럼 보입니다. 뷰 세트를 인수로 절대 전달해야합니까 아니면 제네릭 API 뷰를 사용하여 수행 할 수있는 방법이 있습니까?
Seb

1
@Seb 주석에 늦게 응답은 - 어쩌면 누군가가 그 : 예 용도의 ViewSets하지 조회수 :에서 이익이 될 수
엉덩이

따라서이 게시물 stackoverflow.com/questions/32589087/… 과 결합하여 ViewSets는 다른보기를보다 효과적으로 제어하고 일관된 API를 갖도록 자동으로 URL을 생성하는 방법 인 것 같습니다. 원래 generics.ListeCreateAPIView가 가장 효율적이지만 너무 기본이라고 생각했습니다.

11

다른 시리얼 라이저 제공과 관련하여 왜 HTTP 메소드를 검사하는 접근 방식을 사용하지 않습니까? 더 명확한 IMO이며 추가 검사가 필요하지 않습니다.

def get_serializer_class(self):
    if self.request.method == 'POST':
        return NewRackItemSerializer
    return RackItemSerializer

크레딧 / 소스 : https://github.com/encode/django-rest-framework/issues/1563#issuecomment-42357718


12
에 대해 다른 시리얼 라이저를 사용에 관한 문제의 경우, 들어 listretrieve행동, 당신은 모두가 사용하는 문제가 GET방법을. 이것이 django rest framework ViewSets가 actions 개념을 사용하는 이유 입니다.
Håken Lid

8

@gonz 및 @ user2734679 답변을 기반으로 ModelViewset의 하위 클래스 형태 로이 기능을 제공하는 이 작은 파이썬 패키지 를 만들었습니다 . 작동 방식은 다음과 같습니다.

from drf_custom_viewsets.viewsets.CustomSerializerViewSet
from myapp.serializers import DefaltSerializer, CustomSerializer1, CustomSerializer2

class MyViewSet(CustomSerializerViewSet):
    serializer_class = DefaultSerializer
    custom_serializer_classes = {
        'create':  CustomSerializer1,
        'update': CustomSerializer2,
    }

6
훨씬 일반적인 믹스 인을 사용하는 것이 좋습니다.
iamsk

1

여러 Serializer를 미리 정의하거나 정의하는 것이 가장 명백한 것처럼 보이지만 문서화 된 방법 인 FWIW는 다른 문서화 된 코드를 사용하고 인스턴스화 된대로 인수를 serializer에 전달할 수있는 대체 방법이 있습니다. 사용자 관리 수준, 호출되는 동작, 인스턴스의 속성 등 다양한 요소를 기반으로 논리를 생성해야한다면 더 가치가 있다고 생각합니다.

퍼즐의 첫 번째 조각은 은 인스턴스화 시점에서 직렬 변환기를 동적으로 수정하는 방법 . 이 문서는 뷰셋에서이 코드를 호출하는 방법이나 필드가 초기화 된 후 필드의 읽기 전용 상태를 수정하는 방법을 설명하지는 않지만 그리 어렵지는 않습니다.

두 번째 부분 -get_serializer 메소드 도 문서화되어 있습니다 ( '다른 메소드'에서 get_serializer_class에서 조금 더 아래에 있음). 신뢰할 수 있어야합니다. 수정으로 인한 부작용). GenericAPIView (ModelViewSet 및 다른 모든 내장 뷰 세트 클래스)에서 소스를 확인하여 get_serializer를 정의하는 GenericAPIView에서 상속하십시오.

두 가지를 합치면 다음과 같이 할 수 있습니다.

serializers 파일에서 (base_serializers.py)

class DynamicFieldsModelSerializer(serializers.ModelSerializer):
"""
A ModelSerializer that takes an additional `fields` argument that
controls which fields should be displayed.
"""

def __init__(self, *args, **kwargs):
    # Don't pass the 'fields' arg up to the superclass
    fields = kwargs.pop('fields', None)

    # Adding this next line to the documented example
    read_only_fields = kwargs.pop('read_only_fields', None)

    # Instantiate the superclass normally
    super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)

    if fields is not None:
        # Drop any fields that are not specified in the `fields` argument.
        allowed = set(fields)
        existing = set(self.fields)
        for field_name in existing - allowed:
            self.fields.pop(field_name)

    # another bit we're adding to documented example, to take care of readonly fields 
    if read_only_fields is not None:
        for f in read_only_fields:
            try:
                self.fields[f].read_only = True
            exceptKeyError:
                #not in fields anyway
                pass

그런 다음 뷰 세트에서 다음과 같이 할 수 있습니다.

class MyViewSet(viewsets.ModelViewSet):
    # ...permissions and all that stuff

    def get_serializer(self, *args, **kwargs):

        # the next line is taken from the source
        kwargs['context'] = self.get_serializer_context()

        # ... then whatever logic you want for this class e.g:
        if self.action == "list":
            rofs = ('field_a', 'field_b')
            fs = ('field_a', 'field_c')
        if self.action == retrieve”:
            rofs = ('field_a', 'field_c’, ‘field_d’)
            fs = ('field_a', 'field_b’)
        #  add all your further elses, elifs, drawing on info re the actions, 
        # the user, the instance, anything passed to the method to define your read only fields and fields ...
        #  and finally instantiate the specific class you want (or you could just
        # use get_serializer_class if you've defined it).  
        # Either way the class you're instantiating should inherit from your DynamicFieldsModelSerializer
        kwargs['read_only_fields'] = rofs
        kwargs['fields'] = fs
        return MyDynamicSerializer(*args, **kwargs)

그리고 그게 다야! MyViewSet을 사용하면 원하는 인수를 사용하여 MyDynamicSerializer를 인스턴스화 할 수 있습니다. Serializer가 DynamicFieldsModelSerializer에서 상속한다고 가정하면 수행 할 작업을 알아야합니다.

아마도 다른 방법으로 serializer를 적용하려는 경우 특별한 의미를 가질 수 있다고 언급 할 가치가 있습니다. 예를 들어 read_only_exceptions 목록을 가져 와서 블랙리스트 필드가 아닌 화이트리스트에 사용하는 것이 좋습니다. 전달되지 않은 경우 필드를 빈 튜플로 설정 한 다음 없음 확인을 제거하면 유용합니다. 상속 직렬 변환기의 필드 정의를 ' all '로 설정하십시오. 이것은 직렬화기를 인스턴스화 할 때 전달되지 않은 필드가 우연히 살아남는 것을 의미하며 직렬화 기 호출과 상속 직렬화 기 클래스 정의를 비교하여 포함 된 내용을 알 필요가 없습니다 (예 : DynamicFieldsModelSerializer 의 초기화 내에) .

# ....
fields = kwargs.pop('fields', ())
# ...
allowed = set(fields)
existing = set(self.fields)
for field_name in existing - allowed:
self.fields.pop(field_name)
# ....

NB 별개의 동작으로 매핑 된 2 개 또는 3 개의 클래스를 원하거나 특별히 동적 직렬 변환기 동작을 원하지 않는 경우 다른 사람들이 언급 한 접근법 중 하나를 사용하는 것이 좋을 수도 있지만 대안으로 제시 할 가치가 있다고 생각했습니다. 특히 다른 용도로 사용됩니다.

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