Django REST框架和文件上传(数据URI)

时间:2013-09-16 13:34:22

标签: python django django-rest-framework

我想做的是

1)允许用户选择图像 2)获取图像并将其添加到画布 3)允许在canavs内部进行操作(重新调整大小) 4)按“上传” 5)获取画布并从中生成数据URI

这一切都在JS中花花公子,给我留下了三个隐藏的领域:

<input type="hidden" id="imageData" name="imageData" />
<input type="hidden" id="imageName" name="imageName" />
<input type="hidden" id="imageCaption" name="imageCaption" />

这是Python代码

class Image(models.Model):
    filePath = models.CharField(max_length=200)
    imageCaption = models.CharField(max_length=200)
    imageName = models.CharField(max_length=200)

class ImageSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Image
        fields = ('filePath', 'imageCaption','imageName')

class ImageViewSet(viewsets.ModelViewSet):
    queryset = Image.objects.all()
    serializer_class = ImageSerializer

我在这里缺少一些关键点。

1)在哪里/如何拦截其余请求来解析/处理传入的REST帖子,以便能够拆分数据URI并将图像存储在磁盘上?

2)我可能不明白这一点 - 所以如果还有其他我想念的东西,请告诉我


我认为诀窍在于重写序列化程序恢复字段方法。当它查找由model.FileField构建的“file”字段时,我需要重定向框架以查找dataUri字段,该字段是传递的字段,但我需要实例化一个新字段,没有限制最大长度。拉出dataUri,存储文件,并将文件字段添加到已解析资源的字典中,并让框架按计划继续。在这种情况下,不需要覆盖pre_save,因为此代码需要在验证之前执行。

JS:

// angularJs controller submit method, using RESTAngular
$scope.submit = function() { //function(event) {

        var someImg = {
            file: ''
            , dataUri: $scope.fileUrl
            , caption: $scope.caption
        }

        ImagesResource.post(someImg )

    }

的Python:

class ImageSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Image
        fields = ('file', 'caption','id')

    def saveImage(self, imgFileUri):
        #parse dataUri and save locally, return local path
        return 'somewhereOverTheBlah'

    def restore_fields(self, data, files):
        """
        Core of deserialization, together with `restore_object`.
        Converts a dictionary of data into a dictionary of deserialized fields.
        """
        reverted_data = {}

        if data is not None and not isinstance(data, dict):
            self._errors['non_field_errors'] = ['Invalid data']
            return None

        for field_name, field in self.fields.items():
            print('a: ' + field_name)
            if(field_name == 'file'):
                field_name = 'dataUri'
                field = fields.CharField()
                try:
                    # restore using the built in mechanism
                    field.field_from_native(data, files, field_name, reverted_data)
                    # take the dataUri, save it to disk and return the Path
                    value = reverted_data[field_name]
                    path = self.saveImage(value)
                    # set the file <Path> property on the model, remove the old dataUri
                    reverted_data['file'] = path
                    del reverted_data[field_name]

                except ValidationError as err:
                    self._errors[field_name] = list(err.messages)
            else:
                field.initialize(parent=self, field_name=field_name)
                try:
                    field.field_from_native(data, files, field_name, reverted_data)
                except ValidationError as err:
                    self._errors[field_name] = list(err.messages)

        return reverted_data

3 个答案:

答案 0 :(得分:4)

如果您打算继续使用viewsets.ModelViewSet,那么您可以使用GenericAPIView可用的任何方法覆盖 - 在this link中简要提及,并在GenericAPIView 方法中详细记录 section here

最有用的可能是框架提供的pre_savepost_save挂钩,您可以使用自己的方法覆盖这些挂钩,从而将自己的自定义代码添加到视图中。正如您所料,pre_save在视图保存发布的数据之前调用。其中一个参数是即将保存的对象:

def pre_save(self, obj):

所以你可以在此时进行数据丰富等。

如果这对您来说不够灵活,那么构建自己的自定义视图非常简单,可以让您完全控制 - 请查看chapter 3 of the tutorial以获得比我提供的更好的解释!

更新 - 解决下面评论中提到的验证问题

DRF会在pre_save被调用之前执行验证,因此,如果您的帖子数据中缺少file字段,则在您的自定义pre_save代码有可能被执行之前,您的更新将被拒绝。有几种方法可以解决这个问题:

  1. 在序列化程序中添加validate_<field_name>(self, attrs, source)方法(在您的案例中为validate_file())。无论您的帖子数据中是否已填充该字段,都会调用此字段,您可以在此处进行处理,并在返回之前将file添加到attrs。请注意,如果file将基于模型中的其他字段,那么实现模型级验证器(validate(self, attrs))可能更合适。有关详细信息,请参阅here

  2. 按照上面的建议构建自定义视图。

答案 1 :(得分:2)

如果发布的JSON / XML与django中的模型不对齐,则需要重新设置序列化程序。我使用了源代码并复制了restore_fields方法,并重新编码以处理所需的额外代码。

class ImageSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Image
        fields = ('file', 'caption','id')

    def saveImage(self, imgFileUri):
        #parse dataUri and save locally, return local path
        return 'somewhereOverTheBlah'

    def restore_fields(self, data, files):
        """
        Core of deserialization, together with `restore_object`.
        Converts a dictionary of data into a dictionary of deserialized fields.
        """
        reverted_data = {}

        if data is not None and not isinstance(data, dict):
            self._errors['non_field_errors'] = ['Invalid data']
            return None

        for field_name, field in self.fields.items():
            """
            So it  is iterating over the fields to serialize, when we find the file field
            do something different (in this case look for the fileUri field, handle it and replace
            it inside of the reverted_data dictionary with the intended file field
            """

            if(field_name == 'file'):
                field_name = 'dataUri'
                field = fields.CharField()
                try:
                    # restore using the built in mechanism
                    field.field_from_native(data, files, field_name, reverted_data)
                    # take the dataUri, save it to disk and return the Path
                    value = reverted_data[field_name]
                    path = self.saveImage(value)
                    # set the file <Path> property on the model, remove the old dataUri
                    reverted_data['file'] = path
                    del reverted_data[field_name]

                except ValidationError as err:
                    self._errors[field_name] = list(err.messages)
            else:
                field.initialize(parent=self, field_name=field_name)
                try:
                    field.field_from_native(data, files, field_name, reverted_data)
                except ValidationError as err:
                    self._errors[field_name] = list(err.messages)

        return reverted_data

答案 2 :(得分:0)

  

1)在哪里/如何拦截其余请求来解析/处理   传入的REST帖子能够拆分数据URI并存储   磁盘上的图像?

我不清楚REST框架如何适应这一点,但表单提交的数据是[request.POST][request.FILES]docs)。您可以在表单发布的Django视图中使用这些对象。这是一个简单的示例from the documentation来说明:

from django.shortcuts import render
from django.http import HttpResponseRedirect

def contact(request):
    if request.method == 'POST': # If the form has been submitted...
        form = ContactForm(request.POST) # A form bound to the POST data
        if form.is_valid(): # All validation rules pass
            # Process the data in form.cleaned_data
            # ...
            return HttpResponseRedirect('/thanks/') # Redirect after POST
    else:
        form = ContactForm() # An unbound form

    return render(request, 'contact.html', {
        'form': form,
    })
  

2)我可能不明白这一堆 - 所以如果有的话   我想念的其他东西,请告诉我

可能有帮助的一件事是深入研究“Bound and Unbound Forms”。哦,我刚刚意识到你可能没有使用Django forms library;如果没有,那将是一个很好的起点(包括ModelForms)。此外,还有一项服务可以智能地管理名为Filepicker.io的文件上传;有一个Django package可以实现与它的完美融合。如果您最终使用带有South的Filepicker进行数据和架构迁移,请阅读以下内容:http://pydanny.com/filepicker-and-south.html