如何使用Django Rest Framework创建多个模型实例?


78

我想使用Django Rest Framework通过一个API调用来保存和更新多个实例。例如,假设我有一个“教室”模型,可以有多个“老师”。如果我想创建多位老师,然后更新他们的所有教室编号,我该怎么做?我需要为每个老师进行API调用吗?

我知道目前我们无法保存嵌套模型,但是我想知道是否可以在教师级别保存它。谢谢!


这里是解决方案,为我工作类似的问题: stackoverflow.com/questions/21439672/...
马辛Rapacz

Answers:


80

我知道这是在不久前被问到的,但是我在试图自己解决这个问题时就发现了。

事实证明,如果你通过了 many=True在实例化模型的序列化器类时,则它可以接受多个对象。

这是提到这里django rest框架文档中

就我而言,我的观点如下所示:

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

我并不是真的想写一个样板代码只是为了直接控制序列化器和pass的实例化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'}
]

使用这些详细信息创建了两个资源。很好


哈,这是一个好收获。我已经更新了代码,以实际使用现在默认的多值进行操作。我的输入错误。事实证明,仅通过显示的格式发送数据即可通过不建议使用的方法完成这项工作。警告,更改未经测试。
汤姆·曼特菲尔德

1
在这种情况下,request.DATA是什么样的?它不能是字典-还是以某种方式将其粘贴在字典中?
akaphenom 2014年

@akaphenom我不知道您是否找到答案,但看来request.DATA可以是包含字典的列表,也可以是包含包含字典的列表的字典,具体取决于您对序列化的方式。至少那是我的经验。
whoisearth 2014年

很高兴知道。我已经从django的工作转移了,所以我没有集中精力。但我很乐意让这个答案更加完整。
akaphenom

17
对我不起作用{{“ non_field_errors”:[“无效的数据。需要一个字典,但有列表。]}
rluts

53

我得出的结论与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是QueryDict而不是dict或list的情况下,这应该如何工作?由于这个事实,它在单元测试中有效,但在实际运行时无效(至少对我而言)。
alexdlaird

kwargs.get('data',{})将返回QueryDict,因此实例化失败,因此许多实例都不会设置为True。
罗杰·柯林斯

1
@RogerCollins如果列表项之一引发验证错误,则整个请求将失败。有没有办法跳过无效项目并创建其余实例?
pnhegde

@pnhegde您必须在序列化器中包括该逻辑。您还需要做很多工作,以确保前端用结果更新模型,因为关系将不同步。
罗杰·柯林斯

34

这是另一种解决方案,您不需要重写序列化器__init__方法。只需重写视图的(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)

2
这个答案似乎更直接,以及我希望如何实现此功能。
皮特

1
这一项有效,对我来说,票数最高的答案不起作用。
darcyq

您还可以添加一个transaction.atomic()块以确保添加了所有元素
Felipe Buccioni

这应该得到更多的投票,因为这是为我解决的唯一问题,而且也很简单。
史蒂文

我遇到了: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感谢您的示例。注意,我不必在序列化程序中重写init,而只需在视图类中覆盖create方法
Fiver 2014年

我不打算在没有init的情况下尝试它,因为我正在处理前面的示例。我一定会尝试您的修改,并在实验之前更新我的答案。感谢您的“注意”。
akaphenom 2014年

3
我认为关键是要包含many=Trueget_serializer通话中
Fiver

自从我写出答案以来已有一年多了,我一直努力地想起自己早餐吃的东西,所以以它的价值为准:我似乎记得我唯一必须重写自己的init来添加很多标志的原因是因为我出于某种原因不想直接实例化序列化器类(希望这是一个很好的类,但是现在它使我逃脱了)。所以,是的,关键是要通过= True。可以删除覆盖的init。
Tom Manterfield 2014年

我也很痛苦
akaphenom,2014年

8

我认为尊重该框架的最佳架构的最佳方法是创建一个像这样的mixin:

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)在回应中所建议的那样,重写get_serializer方法比“创建”方法更聪明。


1
得出了与Daniel Albarral类似的结论,但是这里有一个更为简洁的解决方案:类CreateListModelMixin(object):def get_serializer(self,* args,** kwargs):“”“如果传递了数组,请将序列化器设置为许多”“ “如果isinstance(kwargs.get('data',{}),list):kwargs ['many'] =真返回super(CreateListModelMixin,self).get_serializer(* args,** kwargs)
Daniel Albarral

感觉罗杰是个男人
乔什

8

你可以简单地覆盖get_serializer法在APIView并传递many=Trueget_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”。传递序列化程序时data,必须.is_valid()在尝试访问序列化.data表示形式之前调用关键字参数。您应该.is_valid()先呼叫,或者先访问.initial_data
菲利普·穆图阿18'Mar

试试下一个: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

3

Django REST Framework文档中的通用视图”页面指出ListCreateAPIView通用视图“用于读写端点来表示模型实例的集合”。

那就是我开始寻找的地方(实际上,因为我们很快也会在我们的项目中需要此功能,所以我要去找它)。

另请注意,“通用视图”页面上的示例恰好使用ListCreateAPIView


我看到了; 但是,在本教程中,没有示例显示如何允许创建/更新多个项目。我是否将资源嵌套在json对象中,是否应该是扁平的,如果仅一部分项目不通过验证会发生什么等问题,等等,都没有记录。现在,我做了一个不太巧妙的解决方法,我遍历了教师的json对象,并使用教师的序列化程序进行验证和保存。如果您找到更好的解决方案,请告诉我。谢谢
Chaz

是的,它看起来像是单独的Create和List函数。我认为没有更新/创建多个记录的解决方案。
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)

如果您对“很多=真”感到困惑,请参阅此

当我们发送数据时,它会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)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.