Django 实现文件上传下载API

Django 实现文件上传下载API

by:授客 QQ1033553122 欢迎加入全国软件测试交流QQ群7156436

开发环境

 

Win 10

 

Python 3.5.4

 

Django-2.0.13.tar.gz

官方下载地址:

https://www.djangoproject.com/download/2.0.13/tarball/

 

vue 2.5.2

 

djangorestframework-3.9.4

下载地址:

https://github.com/encode/django-rest-framework

附件表设计


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from django.db import models
 
# Create your models here.
 
 
# 上传文件表
class Attachment(models.Model):
    id = models.AutoField(primary_key=True, verbose_name='自增id')
    name = models.CharField(max_length=200, verbose_name='附件名称')
    file_path = models.CharField(max_length=200, verbose_name='附件相对路径')
    create_time =  models.DateTimeField(verbose_name='上传时间')
 
    classMeta:
    db_table = 'tb_attachment'
    verbose_name = '附件表'
    verbose_name_plural = verbose_name

 

 

项目urls.py配置

修改项目根目录下的urls.py,添加以下带背景色部分的代码内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env python
# -*- coding:utf-8 -*-
  
__author__ = '授客'
  
from django.contrib import admin
from django.urls import path
  
from django.conf.urls import include
  
  
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('mywebsite.urls')) #添加API路由配置(这里根据项目实际情况配置)
]

  

 

项目settings.py配置

在文件末尾添加以下配置,用于存放附件

1
2
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media').replace('\\', '/')

   

应用view视图编写

例中直接在views.py视图编写视图,代码如下

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#!/usr/bin/env python
# -*- coding:utf-8 -*-
   
__author__ = '授客'
   
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .models import Attachment
from django.http import FileResponse
from django.utils import timezone
from django.conf import settings
import os
import uuid
   
   
import logging
   
logger = logging.getLogger('mylogger')
   
# 批量创建目录
def mkdirs_in_batch(path):
    try:
        path = os.path.normpath(path)  # 去掉路径最右侧的 \\ 、/
        path = path.replace('\\', '/') # 将所有的\\转为/,避免出现转义字符串
        head, tail = os.path.split(path)
        if not os.path.isdir(path) and os.path.isfile(path):  # 如果path指向的是文件,则分解文件所在目录
            head, tail = os.path.split(head)
 
        if tail == '': # head为根目录,形如 / 、D:
            return True
 
        new_dir_path = ''  # 存放反转后的目录路径
        root = ''  # 存放根目录
        while tail:
            new_dir_path = new_dir_path + tail + '/'
            head, tail = os.path.split(head)
            root = head
        else:
            new_dir_path = root + new_dir_path
 
            # 批量创建目录
            new_dir_path = os.path.normpath(new_dir_path)
            head, tail = os.path.split(new_dir_path)
            temp = ''
            while tail:
                temp = temp + '/' + tail
                dir_path = root + temp
                if not os.path.isdir(dir_path):
                    os.mkdir(dir_path)
                head, tail = os.path.split(head)
        return True
    except Exception as e:
        logger.error('批量创建目录出错:%s' % e)
        return False
 
   
class AttachmentAPIView(APIView):
# 上传附件
def post(self, request, format=None):
    result = {}
    try:
        files = request.FILES
        file = files.get('file')
 
        if not file:
            result['msg'] =  '上传失败,未获取到文件'
            result['success'] =  False
            return Response(result, status.HTTP_400_BAD_REQUEST)
 
        # data = request.POST #获取前端发送的,file之外的其它参数
        # extra = data.get('extra')
        file_name = file.name
        attachment_name = file_name
        creater = request.user.username
        create_time = timezone.now()
        time_str = create_time.strftime('%Y%m%d')
        name, suffix = os.path.splitext(file_name)
        file_name = str(uuid.uuid1()).replace('-', '') + time_str + suffix
        file_relative_path = '/myapp/attachments/'+ time_str
        file_absolute_path = settings.MEDIA_ROOT + file_relative_path
        if not os.path.exists(file_absolute_path):# 路径不存在
            if not utils.mkdirs_in_batch(file_absolute_path):
                result['msg'] =  '批量创建路径(%s)对应的目录失败' % file_absolute_path
                result['success'] =  False
                return Response(result, status.HTTP_500_INTERNAL_SERVER_ERROR)
        file_relative_path += '/' + file_name
        data['file_path'] = file_relative_path
 
        file_absolute_path = file_absolute_path + '/' + file_name
        file_handler = open(file_absolute_path, 'wb')    # 打开特定的文件进行二进制的写操作
 
        try:
            for chunk in file.chunks():      # 分块写入文件
                file_handler.write(chunk)
        finally:
            file_handler.close()
        # 记录到数据库
        try:
            obj = Attachment(file_path=file_path, name=attachment_name, create_time=create_time, creater=creater)
            obj.save()
        except Exception as e:
            result['msg'] =  '上传失败:%s' % e
            result['success'] =  False
            return Response(result, status.HTTP_400_BAD_REQUEST)
 
        result['msg'] =  '上传成功'
        result['success'] =  True
        result['data'] =  result_data
        return Response(result, status.HTTP_200_OK)
    except Exception as e:
        result['msg'] =  '%s' % e
        result['success'] =  False
        return Response(result, status.HTTP_500_INTERNAL_SERVER_ERROR)

 

  

注意:这里采用UploadedFile.chunks()分块写入,而不是直接使用UploadedFile.read()一次性读取整个文件,是因为如果文件比较大,一次性读取过多内容,会占用系统过多的内存,进而让系统变得更低效。默认的chunks分块默认值为2.5M

 

file = files.get('file')# 注意:这里的字典key'file'要和前端提交form表单请求时,文件对象对应的表单key保持一致,前端代码如下

letform = newFormData();

form.append("file", file);

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 删除附件
def delete(self, request, format=None):
    result = {}
    try:
        data = request.data
        attachment_id = data.get('attachment_id')
        obj = Attachment.objects.filter(id=attachment_id).first()
        if obj:
            file_absoulte_path = settings.MEDIA_ROOT + '/'+ obj.file_path
            if os.path.exists(file_absoulte_path) and os.path.isfile(file_absoulte_path):
                os.remove(file_absoulte_path)
                obj.delete()
        result['msg'] =  '删除成功'
        result['success'] =  True
        return Response(result, status.HTTP_200_OK)
    except Exception as e:
        result['msg'] =  '%s' % e
        result['success'] =  False
        return Response(result, status.HTTP_500_INTERNAL_SERVER_ERROR)

 

  

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 下载附件
def get(self, request, format=None):
    result = {}
    try:
        data = request.GET
        attachment_id = data.get('attachmentId')
        obj = Attachment.objects.filter(id=attachment_id).first()
        if obj:
            file_absoulte_path = settings.MEDIA_ROOT+  obj.file_path
            if os.path.exists(file_absoulte_path) and os.path.isfile(file_absoulte_path):
                file = open(file_absoulte_path, 'rb')
                file_response = FileResponse(file)
                file_response['Content-Type']='application/octet-stream'
                file_response["Access-Control-Expose-Headers"] = 'Content-Disposition' # 设置可以作为响应的一部分暴露给外部的请求头,如果缺少这行代码,会导致前端请求响应中看不到该请求头
                file_response['Content-Disposition']='attachment;filename={}'.format(urlquote(obj.name)) # 这里使用urlquote函数主要为针对文件名为中文时,对文件名进行编码,编码后,前端获取的文件名称形如“%E5%AF%BC%E5%87%BA%E6%B5%8B%E8%AF%95%E7%94%A8%E4%BE%8B”
                return file_response
            else:
                result['msg'] =  '请求失败,资源不存在'
                result['success'] =  False
        else:
            result['msg'] =  '请求失败,资源不存在'
            result['success'] =  False
            return Response(result, status.HTTP_200_OK)
    except Exception as e:
        result['msg'] =  '%s' % e
        result['success'] =  False
        return Response(result, status.HTTP_500_INTERNAL_SERVER_ERROR)

 

   

说明:

file_response = FileResponse(file),可以在引入StreamingHttpResponse之后(from django.http import StreamingHttpResponse),替换为

file_response = StreamingHttpResponse(file)

 

 

前端获取响应头中文件名方法如下:

let disposition = res.headers["content-disposition"];

let filename = decodeURI(disposition.replace("attachment;filename=", "") );

 

# do something,比如下载:

link.setAttribute("download", filename);

 

应用urls.py配置

新建urls.py,文件内容如下:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/usr/bin/env python
# -*- coding:utf-8 -*-
  
__author__ = '授客'
  
from django.urls import re_path
  
from .views import AttachmentAPIView
  
  
urlpatterns = [
#...略
re_path('^api/v1/testcase/\d+/attachment$', testcase_attachment_views.TestcaseAttachmentAPIView.as_view()), # 给测试用例添加附件
re_path('^api/v1/testcase/\d+/attachment/\d+$', testcase_attachment_views.TestcaseAttachmentAPIView.as_view()), # 删除、下载测试用例关联的附件

 

  

 

 

前端实现

参考文档“ElementUI Upload上传(利用http-request自定义上传)&下载&删除附件”

 

参考链接

https://docs.djangoproject.com/zh-hans/2.1/topics/http/file-uploads/

https://docs.djangoproject.com/zh-hans/2.0/ref/files/uploads/

 

 

posted @   授客  阅读(1708)  评论(0编辑  收藏  举报
编辑推荐:
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
阅读排行:
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示