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/

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM