美文网首页
Django REST framework 实现文件上传的两种方

Django REST framework 实现文件上传的两种方

作者: rollingstarky | 来源:发表于2019-12-21 11:59 被阅读0次

    一、基于 Django models

    Django 框架的数据模型(models 类)中定义了 ImageFieldFileField 等类型的字段,可以用来存储图片或者文件对象。
    ImageFieldFileField 针对文件对象的属性和行为封装了易于使用的 API,配合 Django REST framework 提供的一系列组件,可以在编写很少量代码的情况下完成初步的文件上传功能。

    各组件代码

    Models

    from django.db import models
    
    class FileModel(models.Model):
        name = models.CharField(max_length=50)
        file = models.FileField(upload_to='upload')
    

    Serializers

    from .models import FileModel
    from rest_framework import serializers
    
    
    class FileSerializer(serializers.ModelSerializer):
        class Meta:
            model = FileModel
            fields = '__all__'
    

    Views

    from rest_framework import viewsets
    from .models import FileModel
    from .serializers import FileSerializer
    
    class FileViewSet(viewsets.ModelViewSet):
        queryset = FileModel.objects.all()
        serializer_class = FileSerializer
    

    Urls

    from django.urls import path, include
    from <appname>.views import FileViewSet
    from rest_framework import routers
    
    router = routers.DefaultRouter()
    router.register(r'upload', FileViewSet)
    
    urlpatterns = [
        path('', include(router.urls)),
    ]
    
    功能测试

    使用 HTTPie 工具利用 POST 方法以 Form 表单的形式(-f)提交上传的文件:

    $ http -f POST http://127.0.0.1:8000/upload/ name="test" file@test.txt
    HTTP/1.1 201 Created
    
    {
        "file": "http://127.0.0.1:8000/upload/upload/test.txt",
        "id": 4,
        "name": "test"
    }
    

    同时也可以直接访问 http://127.0.0.1:8000/upload/ ,通过 Django REST framework 提供的前端界面手动上传文件。

    Django REST framework

    或者也可以自定义前端界面,HTML 上传页面示例代码:

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    
    <body>
        <form action="http://xx.xx.xx.xx:8000/upload/" method="post" enctype="multipart/form-data">
            <input type="text" name="name" placeholder="name"><br>
            <input type="file" name="file"><br>
            <input type="submit" value="submit">
        </form>
    </body>
    
    </html>
    

    二、FileField

    对于 FileField 类型的文件字段,后台的视图代码可以通过 requests.FILES 获取上传的文件数据。如 requests.FILES['file']
    只有当请求方法为 POST 且前端的 <form> 带有属性 enctype="multipart/form-data" 时,request.FILES 才能接收到数据,否则为空。

    FileField 字段的 upload_to 属性用于指定上传文件的保存位置,以 settings.py 中定义的 MEDIA_ROOT 为路径前缀。
    upload_to 属性可以接收包含 strftime() 格式的日期字符串(/%Y/%m/%d),用来定义类似 /year/month/day 格式的路径。如:

    # 文件上传至类似 MEDIA_ROOT/uploads/2019/12/20 的路径下
    upload = models.FileField(upload_to='uploads/%Y/%m/%d/')
    

    可以使用 models 提供的查询接口获取已上传文件的相关信息:

    $ python manage.py shell
    (InteractiveConsole)
    >>> from <appname>.models import FileModel
    >>> test = FileModel.objects.get(name='test')  # 获取某一条数据记录
    >>> test.file  # 数据记录关联的文件对象
    <FieldFile: upload/test.txt>
    >>> test.file.name  # 文件名
    'upload/test.txt'
    >>> test.file.url  # 文件 URL
    'upload/test.txt'
    >>> test.file.size  # 文件大小
    22
    >>> test.file.path  # 文件路径
    '/home/starky/program/python/web/django/filestorage/media/upload/test.txt'
    

    通过数据模型检索到的文件(test.file)为 FieldFile 对象。FieldFile 类封装了一些便捷的 API 可以用来操作关联的底层文件,以下是一些简单的示例。

    读取文件内容

    >>> test = FileModel.objects.get(name='test')
    >>> test.file
    <FieldFile: upload/test.txt>
    >>> with test.file.open('rb') as f:
    ...     f.read()
    ...
    b'sdfsdfsdfweofssdnvdvs\n'
    

    写入新的内容

    >>> with test.file.open('wb') as f:
    ...     f.write(b'Hello, World')
    ...
    12
    >>> test.file.open().read()
    b'Hello, World'
    

    删除关联的底层文件

    >>> test.file.delete()
    >>> test.file
    <FieldFile: None>
    

    新建文件
    语法格式为 FieldFile.save(name, content, save=True)
    其中 content 参数必须接收 django.core.files.File 类或者其子类的实例,比如 ContentFile,不能直接使用 Python 内置的 file 对象。

    不管是删除(FieldFile.delete())还是新建(FieldFile.save())文件,save 参数默认都为 True。即自动调用模型实例的 save() 方法提交对数据库的改动。

    >>> testnew = FileModel(name='testnew', file=None)
    >>> testnew.file
    <FieldFile: None>
    >>> from django.core.files.base import ContentFile
    >>> content = ContentFile('Hello, Django!')
    >>> testnew.file.save('testnew.txt', content)
    >>> testnew.file
    <FieldFile: upload/testnew.txt>
    >>> testnew.file.open().read()
    b'Hello, Django!'
    

    三、FileUploadParser

    Django REST framework 提供了 parsers.FileUploadParser 类,可以用来处理原始格式的文件内容的上传。后端获取到的 request.data 为字典结构,其中包含的 'file' 键对应的值即为上传的文件对象。

    如果 FileUploadParser 类被包含 filename 参数的 URL 调用,则该参数会作为文件保存到服务端后的文件名。若 URL 中不包含 filename 参数,则客户端发起的请求必须包含 Content-Disposition 请求头及 filename 参数。如 Content-Disposition: attachment; filename=upload.jpg

    示例代码
    # views.py
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from rest_framework.parsers import FileUploadParser
    
    
    class FileUploadView(APIView):
        parser_classes = [FileUploadParser, ]
    
        def put(self, request, filename, format=None):
            file_obj = request.data['file']
            with open(filename, 'wb') as f:
                for chunk in file_obj.chunks():
                    f.write(chunk)
    
            return Response(f'{filename} uploaded',status=204)
    
    # urls.py
    from django.urls import re_path
    
    urlpatterns = [
        re_path(r'^files/(?P<filename>[^/]+)$', views.FileUploadView.as_view()),
    ]
    

    上传接口的 URL 为 http://xx.xx.xx.xx/files/<filename> ,其中 <filenmae> 用于指定上传成功后在服务器端的文件名。客户端使用 PUT 请求上传文件。

    使用 postman 测试文件上传,截图如下:

    postman

    前端上传代码示例如下(使用 jQuery,有可能出现跨域问题,可参考网上资料解决):

    <!DOCTYPE html>
    <html>
    
    <head>
        <meta charset="UTF-8">
        <title>文件上传</title>
    </head>
    
    <body>
        <form>
            <p>上传文件: <input type="file" name="files" id='files' /></p>
            <input type="button" value="上传" onclick="doUpload()" />
        </form>
        <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
        <script type="text/javascript">
            function doUpload() {
                $.ajax({
                    url: 'http://xx.xx.xx.xx:8000/files/test.jpg',
                    type: 'PUT',
                    data: $('#files')[0].files[0],
                    cache: false,
                    processData: false,
                    contentType: false,
                    async: false
                }).done(function (res) {
                    alert("上传成功")
                }).fail(function (res) {
                    alert("上传失败:" + res)
                });
            }
        </script>
    </body>
    
    </html>
    

    参考资料

    Managing files
    models.FileField
    parsers

    相关文章

      网友评论

          本文标题:Django REST framework 实现文件上传的两种方

          本文链接:https://www.haomeiwen.com/subject/vaemnctx.html