前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Django 实现文件上传下载API

Django 实现文件上传下载API

作者头像
授客
发布2020-09-01 11:24:07
2.2K0
发布2020-09-01 11:24:07
举报
文章被收录于专栏:授客的专栏

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

附件表设计

代码语言:javascript
复制
from django.db import models
代码语言:javascript
复制
# Create your models here.
代码语言:javascript
复制
# 附件表
代码语言:javascript
复制
class Attachment(models.Model):
代码语言:javascript
复制
    id = models.AutoField(primary_key=True, verbose_name='自增id')
代码语言:javascript
复制
    name = models.CharField(max_length=200, verbose_name='附件名称')
代码语言:javascript
复制
    file_path = models.CharField(max_length=200, verbose_name='附件相对路径')
代码语言:javascript
复制
    create_time =  models.DateTimeField(verbose_name='上传时间')
代码语言:javascript
复制
    class Meta:
代码语言:javascript
复制
        db_table = 'tb_attachment'
代码语言:javascript
复制
        verbose_name = '附件表'
代码语言:javascript
复制
        verbose_name_plural = verbose_name

项目urls.py配置

代码语言:javascript
复制
修改项目根目录下的urls.py,添加以下带背景色部分的代码内容
代码语言:javascript
复制
#!/usr/bin/env python
代码语言:javascript
复制
# -*- coding:utf-8 -*-
代码语言:javascript
复制
__author__ = '授客'
代码语言:javascript
复制
from django.contrib import admin
代码语言:javascript
复制
from django.urls import path
代码语言:javascript
复制
from django.conf.urls import include
代码语言:javascript
复制
urlpatterns = [
代码语言:javascript
复制
    path('admin/', admin.site.urls),
代码语言:javascript
复制
    path('', include('mywebsite.urls'))
代码语言:javascript
复制
]

项目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')

代码语言:javascript
复制
# 批量创建目录
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)

 

        

                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保持一致,前端代码如下

let form = 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配置

代码语言:javascript
复制
代码语言:javascript
复制
            new_dir_path = new_dir_path + tail + '/'
代码语言:javascript
复制
            head, tail = os.path.split(head)
代码语言:javascript
复制
            root = head
代码语言:javascript
复制
        else:
代码语言:javascript
复制
            new_dir_path = root + new_dir_path
代码语言:javascript
复制
            # 批量创建目录
代码语言:javascript
复制
            new_dir_path = os.path.normpath(new_dir_path)
代码语言:javascript
复制
            head, tail = os.path.split(new_dir_path)
代码语言:javascript
复制
            temp = ''
代码语言:javascript
复制
            while tail:
代码语言:javascript
复制
                temp = temp + '/' + tail
代码语言:javascript
复制
                dir_path = root + temp
代码语言:javascript
复制
                if not os.path.isdir(dir_path):
代码语言:javascript
复制
                    os.mkdir(dir_path)
代码语言:javascript
复制
                head, tail = os.path.split(head)
代码语言:javascript
复制
        return True
代码语言:javascript
复制
    except Exception as e:
代码语言:javascript
复制
        logger.error('批量创建目录出错:%s' % e)
代码语言:javascript
复制
        return False
代码语言:javascript
复制
class AttachmentAPIView(APIView):

前端实现

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-08-30 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 开发环境
  • 附件表设计
  • 项目urls.py配置
  • 项目settings.py配置
  • 应用view视图编写
  • 应用urls.py配置
  • 前端实现
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档