Django 实现文件上传下载API
Django 实现文件上传下载API
by:授客 QQ:1033553122 欢迎加入全国软件测试交流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/
作者:授客
微信/QQ:1033553122
全国软件测试QQ交流群:7156436
Git地址:https://gitee.com/ishouke
友情提示:限于时间仓促,文中可能存在错误,欢迎指正、评论!
作者五行缺钱,如果觉得文章对您有帮助,请扫描下边的二维码打赏作者,金额随意,您的支持将是我继续创作的源动力,打赏后如有任何疑问,请联系我!!!
微信打赏
支付宝打赏 全国软件测试交流QQ群
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库