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

Posted

技术标签:

【中文标题】如何使用 Django Rest Framework 创建多个模型实例?【英文标题】:How do I create multiple model instances with Django Rest Framework? 【发布时间】:2013-01-17 22:33:43 【问题描述】:

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

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

【问题讨论】:

这里有类似的问题,解决方案对我有用:***.com/questions/21439672/… 【参考方案1】:

Django REST Framework's documentation 中的Generic Views 页面声明ListCreateAPIView 通用视图“用于读写端点以表示模型实例的集合”。

这就是我开始寻找的地方(实际上我会寻找,因为我们很快就会在我们的项目中需要这个功能)。

另请注意,通用视图页面上的examples 恰好使用ListCreateAPIView

【讨论】:

我看到了;但是,在本教程中没有显示如何允许创建/更新多个项目的示例。诸如我是否将资源嵌套在 json 对象中、它是否是扁平的、如果只有项目的一个子集没有验证会发生什么等问题没有记录在案。现在,我做了一个有点不优雅的解决方法,我循环了一个教师 json 对象并使用一个教师序列化程序来验证和保存。如果您找到更好的解决方案,请告诉我。谢谢 是的,它看起来像是单独的 Create 和 List 函数。我不认为更新/创建多条记录的解决方案在那里。 4 个链接中的 3 个现已损坏【参考方案2】:

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

事实证明,如果您在为模型实例化序列化程序类时传递many=True,那么它可以接受多个对象。

这在 django rest 框架文档中提到了 here

就我而言,我的观点是这样的:

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'
]

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

【讨论】:

哈,这很好。我已经更新了我的代码,以实际使用现在默认的 many 值来做一些事情。这是我的打字错误。事实证明,只是通过不推荐使用的方法以显示的格式发送数据就可以完成这项工作。警告,更改未经测试。 在这种情况下 request.DATA 是什么样的?它不可能是字典——或者他们会以某种方式把它放在字典里? @akaphenom 我不知道您是否找到答案,但似乎 request.DATA 可以是包含字典的列表或包含包含字典的列表的字典,具体取决于您如何序列化它.至少这是我的经验。 很高兴知道。我已经从 django 工作继续前进,所以我没有专注。但我很高兴这个答案更完整。 对我不起作用 "non_field_errors": [ "无效数据。需要字典,但得到了列表。" ] 【参考方案3】:

我无法完全弄清楚如何让 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 感谢您的示例。请注意,我不必在我的序列化程序中重写 init,只需在我的视图类中重写 create 方法 我没想过在没有 init 的情况下尝试它,我正在处理前面的示例。我一定会尝试您的修改,并在该实验之前更新我的答案。感谢您的“提醒”。 我认为关键是在get_serializer调用中包含many=True 自从我写下我的答案已经一年多了,我很难记住我早餐吃了什么,所以把它当作它的价值吧:我似乎记得我必须覆盖我的 init 的唯一原因添加 many 标志是因为出于某种原因我不想直接实例化序列化程序类(希望它是一个好的,但现在它逃脱了我)。所以,是的,传递 many=True 是这里的关键。可以删除覆盖的 init。 这个例子是唯一适用于 django 1.9 的例子【参考方案4】:

我认为尊重所提议的框架架构的最佳方法是创建这样的 mixin:

class CreateListModelMixin(object):

    def create(self, request, *args, **kwargs):
        """
            Create a list of model instances if a list is provided 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 在 his response 中所建议的那样,覆盖 get_serializer 方法比覆盖“create”更聪明。

【讨论】:

感觉罗杰是个男人【参考方案5】:

我得出了与 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)

【讨论】:

这让我很开心!我确认这可以正常工作并接受列表和字典。 鉴于 request.data 是 QueryDict 而不是 dict 或列表,这应该如何工作?由于这个事实,它在单元测试中有效,但在实际运行时无效(至少对我而言)。 kwargs.get('data', ) 将返回一个 QueryDict,因此实例失败,因此许多不会设置为 True。 @RogerCollins 如果列表项之一引发验证错误,则整个请求将失败。有没有办法跳过无效项目并创建其余实例? @pnhegde 你必须在你的序列化程序中包含这个逻辑。你还需要做很多工作来确保你的前端用结果更新你的模型,因为关系会消失同步。【参考方案6】:

这是另一种解决方案,您不需要覆盖您的序列化程序__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)

【讨论】:

这个答案似乎更直接,以及我希望如何实现此功能。 这个有效,而投票最多的答案对我不起作用。 您可以另外添加一个transaction.atomic() 块以确保添加所有元素 这值得更多投票,因为这是唯一对我有用的,而且也很简单。 我在接受的答案中遇到了:Expected a dictionary, but got list. 错误,这个为我修复了它。谢谢。【参考方案7】:

您可以简单地覆盖 APIView 中的 get_serializer 方法,并将 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)

【讨论】:

当你实现这个方法时,你可能会得到和“AssertionError”当一个序列化器被传递一个data关键字参数时,你必须在尝试访问序列化的.data表示之前调用.is_valid()。您应该先致电.is_valid(),或者改为访问.initial_data 尝试下一个: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) 是否可以用它覆盖 perform_create 或 perform_update 方法,并将其他生成的值添加到各个对象?【参考方案8】:

我遇到的最直接的方法:

    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)

【讨论】:

【参考方案9】:

我在post提出了一个简单的例子

序列化器.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)

这几行是Multiple Instance的实际逻辑——

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 you are confused with many=True, see this

当我们发送数据时,它会在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"
    
]

【讨论】:

以上是关于如何使用 Django Rest Framework 创建多个模型实例?的主要内容,如果未能解决你的问题,请参考以下文章

django.test.client 上的 Django rest 框架导入错误

无法使用 Django Rest 框架发送压缩的 gzip 数据

Django 和 Django 休息框架

Django前后端分离——drf

Django REST to React - 无需密码即可获取社交身份验证令牌

尽管有 AllowAny 权限,django-rest-framework 在 POST、PUT、DELETE 上返回 403 响应