Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Django 实现文件上传下载API

Django 实现文件上传下载API

作者头像
授客
发布于 2025-04-19 14:35:40
发布于 2025-04-19 14:35:40
11200
代码可运行
举报
文章被收录于专栏:授客的专栏授客的专栏
运行总次数:0
代码可运行

Django 实现文件上传下载API

开发环境

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
代码运行次数:0
运行
复制
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
代码语言:javascript
代码运行次数:0
运行
复制

项目urls.py配置

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
修改项目根目录下的urls.py,添加以下带背景色部分的代码内容
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#!/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配置

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
MEDIA_URL = '/media/' 
MEDIA_ROOT = os.path.join(BASE_DIR, 'media').replace('\\', '/')

应用view视图编写

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#!/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);

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    # 删除附件 
    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) 
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    # 下载附件 
    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,文件内容如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#!/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/

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

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

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

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【Spring源码】讲讲Bean的生命周期
如果是普通Bean的生命周期,那么上述的回答是真正确的。确实会经历“实例化 -> 属性赋值 -> 初始化 -> 销毁”四个阶段。但是请时刻记住,Spring是个框架,框架的特性除了封装以外,还应当具备扩展性。因此,Spring Bean的生命周期除了上述常见的4个阶段外,还应该具体了解每个阶段的扩展能力,以及Spring提供的一些扩展机制。
有一只柴犬
2024/01/25
2870
【Spring源码】讲讲Bean的生命周期
彻底弄懂Spring中Bean的解析、创建和使用
Spring加载Bean、实例化Bean、获取Bean流程 本文旨在通过分析源码的方式,来剖析Spring加载Bean、实例化Bean、获取Bean的流程,部分核心内容会在源码中说明。内容比较枯燥,慎入! 第一步,启动一个Spring项目 Spring启动入口 从官方文档中我们可以获取到下面这种Spring的启动方式。 我们传入一个test.xml文件 ApplicationContext context = new ClassPathXmlApplicationContext("classp
石奈子
2020/06/28
3.6K0
Spring5.0源码深度解析之Spring是如何利用三级缓存解决循环依赖的问题
Spring已经成为了开发项目的不可缺少的组件了,我们在平常开发项目中难免会遇到以下这些情况,比如说,我有A类和B类,两个业务类都注入到Spring容器里了,且双方都互相注入了,这个时候就会造成循环依赖的问题,相信之前有很多开发者遇到这样的问题吧,不过现在Spring底层已经通过三级缓存来解决了这个循环依赖的问题了。
黎明大大
2021/03/09
1.6K0
【Spring 学习系列】Bean 的生命周期之初始化与销毁
本文将结合一个简单案例,学习 Bean 生命周期中的初始化和销毁阶段的具体内容。
明明如月学长
2022/09/21
3230
【Spring 学习系列】Bean 的生命周期之初始化与销毁
Spring - InstantiationAwareBeanPostProcessor 扩展接口
注意下: Initialization 表示 实例化 (意思是对象还未生成) 。 Instantiation 表示 初始化 (意思是对象已经生成) 。
小小工匠
2022/12/01
5100
万字长文!带你探索 Bean 加载流程
宏观地说,Bean 加载流程大致有三个阶段,分别是实例化 createBeanInstance() 、属性填充 populateBean() 和 初始化 initializeBean(),当 Bean 加载流程执行完毕,Bean 才具备使用条件!对 Bean 加载流程的探索是一段非常煎熬的旅程,你准备好了吗?
程序猿杜小头
2023/03/05
4750
万字长文!带你探索 Bean 加载流程
【Spring源码】- 03 Spring IoC容器启动之Bean创建流程
上篇已经分析完refresh()中大部分方法,也已经把Bean解析成BeanDefinition注册到IoC容器中,refresh还剩下一个非常重要的方法,就是下面将要分析的:finishBeanFactoryInitialization,用以完成Bean创建、依赖注入和初始化等工作。
Reactor2020
2023/03/22
3200
【Spring源码】- 03 Spring IoC容器启动之Bean创建流程
Spring源码解析之八finishBeanFactoryInitialization方法即初始化单例bean
Spring源码解析之八finishBeanFactoryInitialization方法即初始化单例bean
程序员田同学
2022/03/09
7710
Spring源码解析之八finishBeanFactoryInitialization方法即初始化单例bean
Spring加载流程源码分析03【refresh】
  前面两篇文章分析了super(parent)和setConfigLocations(configLocations)的源代码,本文来分析下refresh的源码,
用户4919348
2019/04/02
1.2K0
Spring加载流程源码分析03【refresh】
深入理解-Spring-之源码剖析IOC(二)
我们刚刚创建了Bean工厂,并创建 BeanDefinitions 放进Map里,以beanName为key。那么我们现在有了Bean定义,但还没有实例,也没有构建Bean与Bean之间的依赖关系。
Bug开发工程师
2018/09/21
4350
深入理解-Spring-之源码剖析IOC(二)
Spring源码分析:bean加载流程
在Spring中,Bean的加载和管理是其核心功能之一,包括配置元数据解析、Bean定义注册、实例化、属性填充、初始化、后置处理器处理、完成创建和销毁等步骤。
后台技术汇
2024/10/14
1200
Spring源码分析:bean加载流程
这一次搞懂Spring的Bean实例化原理
前两篇文章分析了Spring XML和注解的解析原理,并将其封装为BeanDefinition对象存放到IOC容器中,而这些只是refresh方法中的其中一个步骤——obtainFreshBeanFactory,接下来就将围绕着这些BeanDefinition对象进行一系列的处理,如BeanDefinitionRegistryPostProcessor对象方法的调用、BeanFactoryPostProcessor对象方法的调用以及Bean实例的创建都离不开这些BeanDefinition对象。下面就来看看Spring是如何处理这些对象的。
夜勿语
2020/09/07
9010
Spring源码学习笔记(8)——Bean的生命周期
Bean的声明周期是指Bean从创建、初始化到销毁的整个过程。在Spring中,Bean的生命周期都是交给IoC容器管理的。Bean的主要生命周期主要有四个阶段:
张申傲
2020/09/03
2.4K0
深度解析 Spring Bean 的加载
这个方法首先从缓存中去获取,这个时候缓存中当然没有数据,因为此时是初始化,我们还没有将bean方到singletonObjects这个map中去,如下图代码。
用户1516716
2019/08/23
3850
(四)Spring源码解析:bean的加载流程
在前几讲中,我们着重的分析了Spring对xml配置文件的解析和注册过程。那么,本节内容,将会试图分析一下bean的加载过程。具体代码,如下图所示:
爪哇缪斯
2023/05/10
8300
(四)Spring源码解析:bean的加载流程
一文读懂 Spring Bean 的生命周期「建议收藏」
今天我们来说一说 Spring Bean 的生命周期,小伙伴们应该在面试中经常遇到,这是正常现象。因为 Spring Bean 的生命周期是除了 IoC、AOP 几个核心概念之外最重要概念,大家务必拿下。可 Spring 源代码又比较复杂,跟着跟着就不知道跟到哪里去了,不太好拿下呀。这倒是真的,而且网上一上来就各种贴流程源码,对初学者来说是真的一脸懵逼,就像字都看的懂,但连在一块就不知道意思了,太绕了。
全栈程序员站长
2022/11/04
1.1K0
一文读懂 Spring Bean 的生命周期「建议收藏」
Spring IOC 源码解析(下)
上一步创建了BeanFactory,并将BeanDefinition注册到了BeanFactory中的ConcurrentHashMap中了。并且以BeanName为key,BeanFactory为value。那么我们现在有了Bean定义,但还没有实例,也没有构建Bean之间的依赖关系。我们知道,构建依赖关系是 IOC 的一个重要的任务,我们怎么能放过。那么是在哪里做的呢?在 finishBeanFactoryInitialization(beanFactory) 方法中,方法定义如下:
黑洞代码
2021/01/14
4090
Spring IOC 源码解析(下)
手撕spring bean的生命周期
org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String, java.lang.Class)
程序员小强
2019/11/04
4860
Spring Bean 的生命周期
Spring是一个IOC容器框架,拥有DI依赖注入(Dependency Injection),DL依赖查找(Dependency Lookup)等功能。
兜兜毛毛
2021/04/28
4040
Spring Bean 的生命周期
InstantiationAwareBeanPostProcessor源码解析
文章目录 1. 简介 2. Bean加载顺序 3. InstantiationAwareBeanPostProcessor接口方法的执行顺序 4. 方法解析 5. 实例 6. 源码梳理 7. Autowired源码解析 8. 总结 简介 继承BeanPostProcessor接口,在此基础上又定义了三个方法,分别在Bean实例化前后【不是初始化】执行。 从上面的介绍可以看到,这个接口相对于BeanPostProcessor功能更加强大,一个接口承担了Bean的实例化前后、初始化前后责任。 Bean加载顺
爱撒谎的男孩
2019/12/31
1.1K0
推荐阅读
相关推荐
【Spring源码】讲讲Bean的生命周期
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验