Django Rest Framework로 여러 모델 인스턴스를 생성하려면 어떻게해야합니까?


80

한 번의 API 호출로 Django Rest Framework를 사용하여 여러 인스턴스를 저장하고 업데이트하고 싶습니다. 예를 들어 여러 "교사"를 가질 수있는 "교실"모델이 있다고 가정 해 보겠습니다. 여러 명의 선생님을 만들고 나중에 모든 교실 번호를 업데이트하려면 어떻게해야합니까? 각 교사에 대해 API 호출을해야합니까?

현재 중첩 된 모델을 저장할 수 없다는 것을 알고 있지만 교사 수준에서 저장할 수 있는지 알고 싶습니다. 감사!


나를 위해 일한 솔루션과 비슷한 질문이 있습니다. stackoverflow.com/questions/21439672/…
Marcin Rapacz

답변:


80

나는 이것이 얼마 전에 요청되었다는 것을 알고 있지만 이것을 스스로 알아 내려고 노력하는 동안 발견했습니다.

many=True모델에 대한 serializer 클래스를 인스턴스화 할 때 통과 하면 여러 개체를 받아 들일 수 있습니다.

이것은 django rest 프레임 워크 문서에 언급되어 있습니다 .

제 경우에는 다음과 같이 보입니다.

class ThingViewSet(viewsets.ModelViewSet):
    """This view provides list, detail, create, retrieve, update
    and destroy actions for Things."""
    model = Thing
    serializer_class = ThingSerializer

나는 직렬화 기의 인스턴스화를 직접 제어하고 전달하기 위해 많은 상용구를 작성하고 싶지 않았으므로 many=True직렬화 기 클래스에서 __init__대신 다음을 재정의합니다 .

class ThingSerializer(serializers.ModelSerializer):
    def __init__(self, *args, **kwargs):
        many = kwargs.pop('many', True)
        super(ThingSerializer, self).__init__(many=many, *args, **kwargs)

    class Meta:
        model = Thing
        fields = ('loads', 'of', 'fields', )

이보기의 목록 URL에 다음 형식으로 데이터를 게시합니다.

[
    {'loads':'foo','of':'bar','fields':'buzz'},
    {'loads':'fizz','of':'bazz','fields':'errrrm'}
]

이러한 세부 정보로 두 개의 리소스를 만들었습니다. 좋았어.


하, 좋은 캐치입니다. 현재 기본값 인 많은 값으로 실제로 무언가를 수행하도록 코드를 업데이트했습니다. 내 입력 오류였습니다. 표시된 형식으로 데이터를 보내는 것만으로도 사용되지 않는 방법을 통해 작업을 수행합니다. 경고, 변경 사항은 테스트되지 않았습니다.
Tom Manterfield

1
이 경우 request.DATA는 어떤 모습입니까? 사전이 될 수 없습니다. 아니면 어떻게 든 사전에 붙일까요?
akaphenom 2014 년

@akaphenom 대답을 찾았는지 모르겠지만 request.DATA는 직렬화 방법에 따라 dict를 포함하는 목록 또는 dict를 포함하는 목록을 포함하는 dict 일 수 있습니다. 적어도 그것은 내 경험이었습니다.
whoisearth 2014 년

알아두면 좋습니다. 저는 장고 작업에서 옮겨 왔기 때문에 집중하지 않았습니다. 하지만이 답변이 좀 더 완전 해져서 기쁩니다.
akaphenom 2014 년

20
나를 위해 작동하지 않습니다 { "non_field_errors": [ "잘못된 데이터입니다. 사전이 필요하지만 목록이 있습니다." ]}
rluts

58

Daniel Albarral과 비슷한 결론에 도달했지만 여기에 더 간결한 해결책이 있습니다.

class CreateListModelMixin(object):

    def get_serializer(self, *args, **kwargs):
        """ if an array is passed, set serializer to many """
        if isinstance(kwargs.get('data', {}), list):
            kwargs['many'] = True
        return super(CreateListModelMixin, self).get_serializer(*args, **kwargs)

3
이것은 나의 하루를 만들었다! 나는 이것이 잘 작동하는지 확인하고 목록과 사전을 모두 허용합니다.
alekwisnia

1
request.data가 dict 또는 목록이 아닌 QueryDict 인 경우 이것이 어떻게 작동합니까? 단위 테스트에서는 작동하지만 그 사실로 인해 실제 런타임에서는 작동하지 않습니다 (적어도 나에게는).
alexdlaird

kwargs.get ( 'data', {})는 QueryDict를 반환하므로 인스턴스가 실패하므로 많은 수가 True로 설정되지 않습니다.
Roger Collins

1
@RogerCollins 목록 항목 중 하나에서 유효성 검사 오류가 발생하면 전체 요청이 실패합니다. 유효하지 않은 항목을 건너 뛰고 나머지 인스턴스를 만드는 방법이 있습니까?
pnhegde

@pnhegde 직렬 변환기에 해당 논리를 포함해야합니다. 또한 관계가 동기화되지 않았기 때문에 프런트 엔드가 결과로 모델을 업데이트했는지 확인하는 데 많은 작업이 필요합니다.
Roger Collins

39

여기에 또 다른 해결책이 있습니다 __init__. serializers 메서드 를 재정의 할 필요가 없습니다 . 뷰의 (ModelViewSet) 'create'메서드를 재정의하십시오 . 통지 many=isinstance(request.data,list). 여기 many=True에서 생성 False할 객체 배열 을 보내고 하나만 보낼 때. 이렇게하면 항목과 목록을 모두 저장할 수 있습니다!

from rest_framework import status, viewsets
from rest_framework.response import Response

class ThingViewSet(viewsets.ModelViewSet):

"""This view snippet provides both list and item create functionality."""

    #I took the liberty to change the model to queryset
    queryset = Thing.objects.all()
    serializer_class = ThingSerializer

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data, many=isinstance(request.data,list))
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

4
이 답변은 더 간단 해 보이며이 기능이 구현 될 것으로 기대합니다.
Pitt

1
이것은 작동하고 가장 많이 투표 한 답변은 나를 위해 작동하지 않습니다.
darcyq

당신은 추가로 추가 할 수 transaction.atomic()있는지 모든 요소가 추가 될 수 있도록 블록
펠리페 Buccioni

이것은 나를 위해 일한 유일한 사람이었고 매우 간단하기 때문에 더 많은 투표를 할 가치가 있습니다.
Steven

나는 Expected a dictionary, but got list.받아 들인 대답에 오류가 생겼고 이것은 나를 위해 수정했습니다. 감사.
Lewis Menelaws

13

딕셔너리에서 배열로 변환하기 위해 request.DATA를 얻는 방법을 알아낼 수 없었습니다. 이로 인해 Tom Manterfield의 솔루션이 작동하는 데 한계가있었습니다. 내 해결책은 다음과 같습니다.

class ThingSerializer(serializers.ModelSerializer):
    def __init__(self, *args, **kwargs):
        many = kwargs.pop('many', True)
        super(ThingSerializer, self).__init__(many=many, *args, **kwargs)

    class Meta:
        model = Thing
        fields = ('loads', 'of', 'fields', )

class ThingViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet ):
    queryset = myModels\
        .Thing\
        .objects\
        .all()
    serializer_class = ThingSerializer

    def create(self, request, *args, **kwargs):
        self.user = request.user
        listOfThings = request.DATA['things']

        serializer = self.get_serializer(data=listOfThings, files=request.FILES, many=True)
        if serializer.is_valid():
            serializer.save()
            headers = self.get_success_headers(serializer.data)
            return Response(serializer.data, status=status.HTTP_201_CREATED,
                            headers=headers)

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

그런 다음 클라이언트에서 이와 동등한 것을 실행합니다.

var things = {    
    "things":[
        {'loads':'foo','of':'bar','fields':'buzz'},
        {'loads':'fizz','of':'bazz','fields':'errrrm'}]
}
thingClientResource.post(things)

1
+1 예를 들어 주셔서 감사합니다. Serializer에서 init 를 재정의 할 필요가 없었 습니다. 뷰 클래스의 create 메서드 만 사용하면됩니다
Fiver

init없이 시도해볼 생각은 없었고, 이전 예제에서 작업하고있었습니다. 나는 확실히 당신의 수정을 시도하고 그 실험을 기다리는 동안 내 대답을 업데이트 할 것입니다. "주의"감사합니다.
akaphenom 2014

3
나는 키가의 포함 생각 many=True에서 get_serializer호출
오파 운드 지폐

내가 답을 쓴 지 1 년이 넘었고 아침 식사로 먹은 것을 기억하기가 힘들어, 그만한 가치를 위해 이것을 취하십시오. 어떤 이유로 직렬화 클래스를 직접 인스턴스화하고 싶지 않았습니다. 그래서 네, many = True의 전달이 여기서 핵심입니다. 재정의 된 init를 삭제할 수 있습니다.
Tom Manterfield 2014 년

이 예제는 django 1.9에서 작동하는 유일한 예제입니다
Georg Zimmer

8

프레임 워크의 제안 된 아키텍처를 존중하는 가장 좋은 방법은 다음과 같은 믹스 인을 만드는 것입니다.

class CreateListModelMixin(object):

    def create(self, request, *args, **kwargs):
        """
            Create a list of model instances if a list is provides or a
            single model instance otherwise.
        """
        data = request.data
        if isinstance(data, list):
            serializer = self.get_serializer(data=request.data, many=True)
        else:
            serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED,
                    headers=headers)

그런 다음 다음과 같이 ModelViewSet의 CreateModelMixin을 재정의 할 수 있습니다.

class <MyModel>ViewSet(CreateListModelMixin, viewsets.ModelViewSet):
    ...
    ...

이제 클라이언트에서 다음과 같이 작업 할 수 있습니다.

var things = [    
    {'loads':'foo','of':'bar','fields':'buzz'},
    {'loads':'fizz','of':'bazz','fields':'errrrm'}
]
thingClientResource.post(things)

또는

var thing = {
    'loads':'foo','of':'bar','fields':'buzz'
}

thingClientResource.post(thing)

편집하다:

Roger Collins가 그녀의 응답 에서 제안했듯이 'create'보다 get_serializer 메서드를 덮어 쓰는 것이 더 영리합니다.


1
Daniel Albarral과 비슷한 결론에 도달했지만 더 간결한 해결책이 있습니다. class CreateListModelMixin (object) : def get_serializer (self, * args, ** kwargs) : "" "배열이 전달되면 serializer를 여러" "로 설정합니다. "if isinstance (kwargs.get ( 'data', {}), list) : kwargs [ 'many'] = True return super (CreateListModelMixin, self) .get_serializer (* args, ** kwargs)
Daniel Albarral

로저는 사람입니다 느낌이
조쉬

8

get_serializerAPIView 에서 메소드 를 덮어 쓰고 다음 과 같이 기본보기 many=True로 전달할 get_serializer수 있습니다.

class SomeAPIView(CreateAPIView):
    queryset = SomeModel.objects.all()
    serializer_class = SomeSerializer

    def get_serializer(self, instance=None, data=None, many=False, partial=False):
        return super(SomeAPIView, self).get_serializer(instance=instance, data=data, many=True, partial=partial)

1
이 메서드를 구현할 때 "AssertionError"가 발생할 수 있습니다. serializer에 data키워드 인수 가 전달 .is_valid()되면 직렬화 된 .data표현 에 액세스하기 전에 호출해야합니다 . .is_valid()먼저 전화를 걸 거나 .initial_data대신 액세스 해야합니다 .
Philip Mutua

다음 시도 :from rest_framework.fields import empty def get_serializer(self, instance=None, data=empty, many=False, partial=False): return super(SomeAPIView, self).get_serializer(instance=instance, data=data, many=True, partial=partial)
Guillermo Hernandez 19

3

Django REST Framework의 문서 에 있는 Generic Views 페이지 에는 ListCreateAPIView 일반보기가 "모델 인스턴스 컬렉션을 나타내는 읽기-쓰기 끝점에 사용됨"이 명시되어 있습니다.

여기서부터 살펴볼 것입니다 (실제로 프로젝트에도이 기능이 필요할 것이기 때문입니다).

Generic Views 페이지 의 예제ListCreateAPIView.


나는 것을보고; 그러나 튜토리얼에는 여러 항목의 생성 / 업데이트를 허용하는 방법을 보여주는 예제가 없습니다. json 객체 내에 리소스를 중첩합니까, 평평해야하는지, 항목의 하위 집합 만 유효성을 검사하지 않으면 어떻게되는지 등의 질문이 문서화되지 않았습니다. 지금은 교사 json 객체를 반복하고 교사 직렬 변환기를 사용하여 유효성을 검사하고 저장하는 다소 비정상적인 해결 방법을 수행했습니다. 더 나은 해결책을 찾으면 알려주십시오. 감사합니다
Chaz 2013

예, 개별 생성 및 목록 기능처럼 보입니다. 여러 레코드를 업데이트 / 생성하는 솔루션이 거기에 있다고 생각하지 않습니다.
akaphenom 2014 년

링크 4 개 중 3 개가 끊어졌습니다
e4c5

3

나는 간단한 예를 생각해 냈다. post

Serializers.py

from rest_framework import serializers
from movie.models import Movie

class MovieSerializer(serializers.ModelSerializer):

    class Meta:
        model = Movie
        fields = [
            'popularity',
            'director',
            'genre',
            'imdb_score',
            'name',
        ]  

Views.py

from rest_framework.response import Response
from rest_framework import generics
from .serializers import MovieSerializer
from movie.models import Movie
from rest_framework import status
from rest_framework.permissions import IsAuthenticated

class MovieList(generics.ListCreateAPIView):
    queryset = Movie.objects.all().order_by('-id')[:10]
    serializer_class = MovieSerializer
    permission_classes = (IsAuthenticated,)

    def list(self, request):
        queryset = self.get_queryset()
        serializer = MovieSerializer(queryset, many=True)
        return Response(serializer.data)

    def post(self, request, format=None):
        data = request.data
        if isinstance(data, list):  # <- is the main logic
            serializer = self.get_serializer(data=request.data, many=True)
        else:
            serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

이 라인은 다중 인스턴스의 실제 논리입니다.

data = request.data
if isinstance(data, list):  # <- is the main logic
      serializer = self.get_serializer(data=request.data, many=True)
else:
      serializer = self.get_serializer(data=request.data)

many = True와 혼동되는 경우 다음을 참조하십시오.

우리가 데이터를 보낼 때 그것은 다음 list과 같은 내부에있을 것입니다.

[
    {
        "popularity": 84.0,
        "director": "Stanley Kubrick",
        "genre": [
            1,
            6,
            10
        ],
        "imdb_score": 8.4,
        "name": "2001 : A Space Odyssey"
    },
    {
        "popularity": 84.0,
        "director": "Stanley Kubrick",
        "genre": [
            1,
            6,
            10
        ],
        "imdb_score": 8.4,
        "name": "2001 : A Space Odyssey"
    }
]

1

내가 본 가장 간단한 방법 :

    def post(self, request, *args, **kwargs):
        serializer = ThatSerializer(data=request.data, many=isinstance(request.data, list))
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.