Django 實作檔案上傳下載API
by:授客 QQ:1033553122 歡迎加入全國軟體測驗交流QQ群
開發環境
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
附件表設計
fromdjango.dbimport models
# Create your models here.
# 附件表
classAttachment(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__ = '授客'
fromdjango.contribimport admin
fromdjango.urlsimport path
fromdjango.conf.urlsimport include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('mywebsite.urls'))
]
專案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
importlogging
logger = logging.getLogger('mylogger')
# 批量創建目錄
defmkdirs_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 = '' # 存放根目錄
whiletail:
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 = ''
whiletail:
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
exceptException as e:
logger.error('批量創建目錄出錯:%s' % e)
return False
classAttachmentAPIView(APIView):
# 上傳附件
defpost(self, request, format=None):
result = {}
try:
files = request.FILES
file = files.get('file')
if not file:
result['msg'] = '上傳失敗,未獲取到檔案'
result['success'] = False
returnResponse(result, status.HTTP_400_BAD_REQUEST)
# data = https://www.cnblogs.com/shouke/p/request.POST #獲取前端發送的,file之外的其它引數
# extra = data.get('extr')
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
returnResponse(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()
exceptException as e:
result['msg'] = '上傳失敗:%s' % e
result['success'] = False
returnResponse(result, status.HTTP_400_BAD_REQUEST)
result['msg'] = '上傳成功'
result['success'] = True
result['data'] = result_data
returnResponse(result, status.HTTP_200_OK)
exceptException as e:
result['msg'] = '%s' % e
result['success'] = False
returnResponse(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 = https://www.cnblogs.com/shouke/p/request.data
attachment_id = data.get('attachment_id')
obj = Attachment.objects.filter(id=attachment_id).first()
ifobj:
file_absoulte_path = settings.MEDIA_ROOT + '/'+ obj.file_path
ifos.path.exists(file_absoulte_path) and os.path.isfile(file_absoulte_path):
os.remove(file_absoulte_path)
obj.delete()
result['msg'] = '洗掉成功'
result['success'] = True
returnResponse(result, status.HTTP_200_OK)
exceptException as e:
result['msg'] = '%s' % e
result['success'] = False
returnResponse(result, status.HTTP_500_INTERNAL_SERVER_ERROR)
# 下載附件
defget(self, request, format=None):
result = {}
try:
data = https://www.cnblogs.com/shouke/p/request.GET
attachment_id = data.get('attachmentId')
obj = Attachment.objects.filter(id=attachment_id).first()
ifobj:
file_absoulte_path = settings.MEDIA_ROOT+ obj.file_path
ifos.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”
returnfile_response
else:
result['msg'] = '請求失敗,資源不存在'
result['success'] = False
else:
result['msg'] = '請求失敗,資源不存在'
result['success'] = False
returnResponse(result, status.HTTP_200_OK)
exceptException as e:
result['msg'] = '%s' % e
result['success'] = False
returnResponse(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.urlsimport 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()), # 洗掉、下載測驗用例關聯的附件
]
前端實作
略
參考鏈接
https://docs.djangoproject.com/zh-hans/2.1/topics/http/file-uploads/
https://docs.djangoproject.com/zh-hans/2.0/ref/files/uploads/
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/211549.html
標籤:Python
上一篇:Django 繼承AbstractUser擴展用戶模型
下一篇:Django 檔案匯入實作方案
