AI智能
改变未来

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

附件表设计


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,添加以下带背景色部分的代码内容
#!/usr/bin/env python# -*- coding:utf-8 -*-__author__ = \'授客\'from django.contrib import adminfrom django.urls import pathfrom django.conf.urls import includeurlpatterns = [path(\'admin/\', admin.site.urls),path(\'\', include(\'mywebsite.urls\')) #添加API路由配置(这里根据项目实际情况配置)]

  

项目settings.py配置

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

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

  

应用view视图编写

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

#!/usr/bin/env python# -*- coding:utf-8 -*-__author__ = \'授客\'from rest_framework.views import APIViewfrom rest_framework.response import Responsefrom rest_framework import statusfrom .models import Attachmentfrom django.http import FileResponsefrom django.utils import timezonefrom django.conf import settingsimport osimport uuidimport logginglogger = 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 == \'\': # hea15a8d为根目录,形如 / 、D:return Truenew_dir_path = \'\'  # 存放反转后的目录路径root = \'\'  # 存放根目录while tail:new_dir_path = new_dir_path + tail + \'/\'head, tail = os.path.split(head)root = headelse: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 + \'/\' + taildir_path = root + tempif not os.path.isdir(dir_path):os.mkdir(dir_path)head, tail = os.path.split(head)return Trueexcept Exception as e:logger.error(\'批量创建目录出错:%s\' % e)return Falseclass AttachmentAPIView(APIView):# 上传附件def post(self, request, format=None):result = {}try:files = request.FILESfile = files.get(\'file\')if not file:result[\'msg\'] =  \'上传失败,未获取到文件\'result[\'success\'] =  Falsereturn Response(result, status.HTTP_400_BAD_REQUEST)# data = request.POST #获取前端发送的,file之外的其它参数# extra = data.get(\'extra\')file_name = file.nameattachment_name = file_namecreater = request.user.usernamecreate_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 + suffixfile_relative_path = \'/myapp/attachments/\'+ time_strfile_absolute_path = settings.MEDIA_ROOT + file_relative_pathif not os.path.exists(file_absolute_path):# 路径不存在if not utils.mkdirs_in_batch(file_absolute_path):result[\'msg\'] =  \'批量创建路径(%s)对应的目录失败\' % file_absolute_pathresult[\'success\'] =  Falsereturn Response(result, status.HTTP_500_INTERNAL_SERVER_ERROR)file_relative_path += \'/\' + file_namedata[\'file_path\'] = file_relative_pathfile_absolute_path = file_absolute_path + \'/\' + file_namefile_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\' % eresult[\'success\'] =  Falsereturn Response(result, status.HTTP_400_BAD_REQUEST)result[\'msg\'] =  \'上传成功\'result[\'success\'] =  Trueresult[\'data\'] =  result_datareturn Response(result, status.HTTP_200_OK)except Exception as e:result[\'msg\'] =  \'%s\' % eresult[\'success\'] =  Falsereturn 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);

# 删除附件def delete(self, request, format=None):result = {}try:data = request.dataattachment_id = data.get(\'attachment_id\')obj = Attachment.objects.filter(id=attachment_id).first()if obj:file_absoulte_path = settings.MEDIA_ROOT + \'/\'+ obj.file_pathif os.path.exists(file_absoulte_path) and os.path.isfile(file_absoulte_path):os.remove(file_absoulte_path)obj.delete()result[\'msg\'] =  \'删除成功\'result[\'success\'] =  Truereturn Response(result, status.HTTP_200_OK)except Exception as e:result[\'msg\'] =  \'%s\' % eresult[\'success\'] =  Falsereturn Response(result, status.HTTP_500_INTERNAL_SERVER_ERROR)

  

# 下载附件def get(self, request, format=None):result = {}2088try:data = request.GETattachment_id = data.get(\'attachmentId\')obj = Attachment.objects.filter(id=attachment_id).first()if obj:file_absoulte_path = settings.MEDIA_ROOT+  obj.file_pathif 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_responseelse:result[\'msg\'] =  \'请求失败,资源不存在\'result[\'success\'] =  Falseelse:result[\'msg\'] =  \'请求失败,资源不存在\'result[\'success\'] =  Falsereturn Response(result, status.HTTP_200_OK)except Exception as e:result[\'msg\'] =  \'%s\' % eresult[\'success\'] =  Falsereturn Response(result, status.HTTP_500_INTERNAL_SERVER_ERROR)

  

说明:

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

file_response = StreamingHttpResponse(file)

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

letdisposition= res.headers[\”content-disposition\”];

letfilename=decodeURI(disposition.replace(\”attachment;filename=\”,\”\”));

# do something,比如下载:

link.setAttribute(\”download\”, filename);

应用urls.py配置

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

#!/usr/bin/env python# -*- coding:utf-8 -*-__author__ = \'授客\'from django.urls import re_pathfrom .views import AttachmentAPIViewurlpatterns = [#...略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/

赞(0) 打赏
未经允许不得转载:爱站程序员基地 » Django 实现文件上传下载API