Django-Tastypie 中的 Multipart/Form-Data POST、PUT、PATCH

5 投票
1 回答
3589 浏览
提问于 2025-04-18 16:23

我需要在Django中使用tastypie来更新用户头像。我用的是Django 1.6、AngularJs,并且使用了Django的普通认证和DjangoAuthorization类。

当我尝试用'CONTENT-TYPE:Multipart/form-data'上传图片时,出现了以下错误。

error_message: "You cannot access body after reading from request's data stream"

首先,我知道tastypie并不正式支持多部分表单数据。我希望能找到一个临时解决办法,直到tastypie支持多部分表单数据。

我对这个问题做了一些研究,也查看了一些相关的问题:

参考资料:

  1. Django-Tastypie 反序列化多部分表单数据
  2. 在Tastypie中访问POST数据

虽然上面的方法看起来像是个小技巧,但我还是想在我的资源中试一试。

我的资源如下:


class UserProfileResource(ModelResource):
    user = fields.ToOneField(UserResource, 'user', full=True)
    profile_img = fields.FileField(attribute="img", null=True, blank=True)

    """docstring for ProfileResource"""
    def alter_list_data_to_serialize(self, request, data):
        if request.GET.get('meta_only'):
            return {'meta': data['meta']}
        return data

    def authorized_read_list(self, object_list, bundle):
        return object_list.filter(user=bundle.request.user).select_related()

    def obj_create(self, bundle, **kwargs):
        return super(UserProfileResource, self).obj_create(bundle, user=bundle.request.user)

    def dehydrate(self, bundle):
        bundle.data['groups'] = [g.name for g in bundle.request.user.groups.all()]
        return bundle

    """Deserialize for multipart Data"""
    def deserialize(self, request, data, format=None):
        if format is None:
            format = request.META.get('CONTENT_TYPE','application/json')
        if format == 'application/x-www-form-urlencoded':
            return request.POST
        elif format.startswith('multipart'):
            data = request.POST.copy()
            data.update(request.FILES)
            return data
        return super(UserProfileResource, self).deserialize(request, data, format)

    """PATCH For Making the request._body = FALSE"""
    def put_detail(self, request, **kwargs):
        if request.META.get('CONTENT_TYPE').startswith('multipart') and \
                not hasattr(request, '_body'):
            request._body = ''

        return super(UserProfileResource, self).put_detail(request, **kwargs)

    """PATCH for MAKING the request._body = FALSE"""
    def convert_post_to_VERB(request, verb):
        """
        Force Django to process the VERB. Monkey Patch for Multipart Data
        """
        if request.method == verb:
            if hasattr(request, '_post'):
                del (request._post)
                del (request._files)

            request._body  # now request._body is set
            request._read_started = False  # so it won't cause side effects
            try:
                request.method = "POST"
                request._load_post_and_files()
                request.method = verb
            except AttributeError:
                request.META['REQUEST_METHOD'] = 'POST'
                request._load_post_and_files()
                request.META['REQUEST_METHOD'] = verb
            setattr(request, verb, request.POST)
        return request

    class Meta:
        queryset = UserProfile.objects.filter()
        resource_name = 'user_profile'
        list_allowed_methods = ['get', 'post']
        detail_allowed_methods = ['get', 'post', 'put', 'delete', 'patch']
        serializer = Serializer()
        authentication = Authentication()
        authorization = DjangoAuthorization()
        always_return_data = True

上面提到的参考资料说,方法convert_post_to_VERB()并不打算处理多部分数据,通过修改request._read_started = False,我们就能用tastypie上传文件。但出于某种原因,即使我做了上述修改,我仍然收到相同的错误:“错误:在读取请求的数据流后无法访问主体”。

请帮我解决这个问题,我缺少了什么?或者有没有人有一个有效的逻辑可以让我参考一下。

更新:

  • 在我修改了put_detail()方法后,PUT请求可以正常工作。
  • 我对update_detail()做了同样的修改,想让PATCH请求也能工作,但无论如何都没有成功。

1 个回答

4

你需要定义的是 patch_detail 而不是 update_detail

这是我的 MultipartResource 的样子:

class MultipartResource(object):

    def deserialize(self, request, data, format=None):

        if not format:
            format = request.META.get('CONTENT_TYPE', 'application/json')

        if format == 'application/x-www-form-urlencoded':
            return request.POST

        if format.startswith('multipart/form-data'):
            multipart_data = request.POST.copy()
            multipart_data.update(request.FILES)
            return multipart_data

        return super(MultipartResource, self).deserialize(request, data, format)

    def put_detail(self, request, **kwargs):
        if request.META.get('CONTENT_TYPE', '').startswith('multipart/form-data') and not hasattr(request, '_body'):
            request._body = ''
        return super(MultipartResource, self).put_detail(request, **kwargs)

    def patch_detail(self, request, **kwargs):
        if request.META.get('CONTENT_TYPE', '').startswith('multipart/form-data') and not hasattr(request, '_body'):
            request._body = ''
        return super(MultipartResource, self).patch_detail(request, **kwargs)

我在我的资源中使用它和 ModelResource 一起进行多重继承:

class MyResource(MultipartResource, ModelResource):
    pass

顺便说一下,给你的资源设置一个 convert_post_to_VERB 方法是没有意义的。不幸的是,在 tastypie 的 Resource 类中,这个方法是不能被重写的。他们在 resource.py 模块中把它定义成了一个函数。

撰写回答