zoukankan      html  css  js  c++  java
  • 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 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配置

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

    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 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);

     

        # 删除附件 
        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) 
    

      

      
        # 下载附件 
        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,文件内容如下:

    #!/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/

     

     

  • 相关阅读:
    【RAY TRACING THE REST OF YOUR LIFE 超详解】 光线追踪 3-7 混合概率密度
    【RAY TRACING THE REST OF YOUR LIFE 超详解】 光线追踪 3-6 直接光源采样
    【RAY TRACING THE REST OF YOUR LIFE 超详解】 光线追踪 3-5 random direction & ONB
    【RAY TRACING THE REST OF YOUR LIFE 超详解】 光线追踪 3-4 基于重要性采样的材质初探
    【RAY TRACING THE REST OF YOUR LIFE 超详解】 光线追踪 3-3 蒙特卡罗 (三)
    区域生长算法 全局分类 C++ & matlab
    智能优化 之 下山单纯形法 C++
    图形暂时停更
    【RAY TRACING THE REST OF YOUR LIFE 超详解】 光线追踪 3-2 蒙特卡罗(二) 重要性采样
    【RAY TRACING THE REST OF YOUR LIFE 超详解】 光线追踪 3-1 蒙特卡罗 (一)
  • 原文地址:https://www.cnblogs.com/shouke/p/13961336.html
Copyright © 2011-2022 走看看