1-Django4.2 - 文件上传下载示例

about

django4.2.3 + python3.10 + win11 + openpyxl3.1.2

本篇博客主要演示了,使用Django完成对本地Excel表格的上传下载操作,当然,其他类型的文件也一样。

文件上传

基于form表单上传

urls.py

from django.contrib import admin
from django.urls import path
from api import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('index/', views.index),
]

models.py

from django.db import models


class User(models.Model):
    name = models.CharField(max_length=64, default='')

    def __str__(self):
        return self.name

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index</title>

</head>
<body>
<div>
    <form action="" method="post" enctype="multipart/form-data">
        {% csrf_token %}
        <input type="file" name="f1">
        <input type="text" name="user" value="张开">
        <input type="submit" value="提交">
        <span style="color: red;">{{ error }}</span>
    </form>
</div>
</body>
</html>

一般的,在普通的form表单提交时,请求头中的CONTENT_TYPE: application/x-www-form-urlencoded,然后数据是以键值对的形式传输的,服务端从request.POST取值,这没问题,并且CONTENT_TYPE: application/x-www-form-urlencoded这种编码类型满足大多数情况,一切都挺好的。

而要说使用form表单上传文件,就不得不多说两句了。
起初,http 协议中没有上传文件方面的功能,直到 rfc1867 为 http 协议添加了这个功能。当然在 rfc1867 中限定 form标签的 method 必须为 POSTenctype = "multipart/form-data" 以及<input type = "file">
所以,当使用form表单上传文件的时候,请求头的content_typemultipart/form-data这种形式的,所以,我们需要在form标签添加enctype="multipart/form-data属性来进行标识。
如果你能打印上传文件的请求头,你会发现CONTENT_TYPE是这样的content_type:multipart/form-data; boundary=----WebKitFormBoundarylZZyJUkrgm6h34DU,那boundary=----WebKitFormBoundarylZZyJUkrgm6h34DU又是什么呢?
multipart/form-data 后面有boundary以及一串字符,这是分界符,后面的一堆字符串是随机生成的,目的是防止上传文件中出现分界符导致服务器无法正确识别文件起始位置。那分界符又有啥用呢?
对于上传文件的post请求,我们没有使用原有的 http 协议,所以 multipart/form-data 请求是基于 http 原有的请求方式 post 而来的,那么来说说这个全新的请求方式与 post 的区别:

  1. 请求头的不同,对于上传文件的请求,contentType = multipart/form-data是必须的,而 post 则不是,毕竟 post 又不是只上传文件~。
  2. 请求体不同,这里的不同也就是指前者(上传文件请求)在发送的每个字段内容之间必须要使用分界符来隔开,比如文件的内容和文本的内容就需要分隔开,不然服务器就没有办法正常的解析文件,而后者 post 当然就没有分界符直接以key:value的形式发送就可以了。

当然,其中的弯弯绕绕不是三言两语能解释的清楚的,我们先知道怎么用就行。

views.py

import os
from openpyxl import load_workbook
from django.shortcuts import render, redirect, HttpResponse
from django.db import transaction
from api.models import User
def index(request):
    """ 导入Excel数据 """
    if request.method == 'POST':
        try:
            with transaction.atomic():   # 事物
                # post请求,我们仍然可以正常提交其他数据
                user = request.POST.get("user")
                # print(user)
                excel = request.FILES.get('f1')
                """
                # 先写到本地也行
                path = os.path.join("media", "files", excel.name)
                with open(path, 'wb') as f:
                    for line in excel:
                        f.write(line)
                wb = load_workbook(path)
                """
                # 直接openpyxl直接load也行
                wb = load_workbook(excel)
                sheet = wb.worksheets[0]
                for row in sheet.iter_rows():
                    # 获取每一行的内容
                    # print(row[0].value)    # 这里取出来每行的数据,打印如果是乱码,不要怕,正常写入数据就没问题的
                    User.objects.create(name=row[0].value)
                return HttpResponse('OK')
        except Exception as e:
            print(e)
            return render(request, 'index.html', {"error": "上传文件类型有误,只支持 xls 和 xlsx 格式的 Excel文档"})

    return render(request, 'index.html', {"error": ""})

基于Ajax上传

urls.py

from django.contrib import admin
from django.urls import path
from api import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('index/', views.index),
    path('upload/', views.upload),
]

models.py

from django.db import models


class User(models.Model):
    name = models.CharField(max_length=64, default='')

    def __str__(self):
        return self.name

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!-- ajax上传文件开始 -->
<div>
    <!-- csrf_token 固定写法 -->
    {% csrf_token %}
    <input type="text" id="user" value="张开">
    <input type="file" id="ajaxFile">
    <button id="ajaxBtn">上传</button>
    <span style="color: red;" id="msg">{{ error }}</span>
</div>
<!-- ajax上传文件结束 -->
</body>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script>
    console.log($("[name='csrfmiddlewaretoken']").val());
    $("#ajaxBtn").click(function () {
        // 首先,实例化一个formdata对象
        var formData = new FormData();
        // 然后使用formdata的append来添加数据,即获取文件对象
        // var file_obj = $("#ajaxFile")[0].files[0];    // 使用jQuery获取文件对象
        var file_obj = document.getElementById('ajaxFile').files[0];   // 使用dom也行
        formData.append('f1', file_obj );
        // 处理csrftoken
        formData.append("csrfmiddlewaretoken", $("[name='csrfmiddlewaretoken']").val());
        // 也可以将其他的数据,以键值对的形式,添加到formData中
        formData.append('user',$("#user").val());
        $.ajax({
            url: "/upload/",
            type: "POST",
            data: formData,
            processData:false,  // 默认情况下会将发送的数据序列化以适应默认的内容类型application/x-www-form-urlencoded,如果发送不想转换的的信息的时候需要手动将其设置为false
            contentType:false,  // 固定写法
            success:function (dataMsg) {
                console.log(dataMsg);
                $("#msg").text(dataMsg['message'])
            }
        })
    })
</script>
</html>

在 ajax 中 contentType 设置为 false 是为了避免 JQuery 对请求头content_type进行操作,从而失去分界符,而使服务器不能正常解析文件。
在使用jQuery的$.ajax()方法的时候参数processData默认为true(该方法为jQuery独有的),默认情况下会将发送的数据序列化以适应默认的内容类型application/x-www-form-urlencoded
如果想发送不想转换的信息的时候需要手动将其设置为false即可。

views.py

from openpyxl import load_workbook
from django.shortcuts import render, redirect, HttpResponse
from django.http import JsonResponse
from django.db import transaction
from api.models import User


def upload(request):
    if request.method == 'POST':
        user = request.POST.get("user")
        print(user)
        f1 = request.FILES.get('f1')
        wb = load_workbook(f1)
        sheet = wb["北京"]
        for row in sheet.iter_rows():
            # 获取每一行的内容
            print(row[0].value)    # 这里取出来每行的数据,打印如果是乱码,不要怕,正常写入数据就没问题的
            User.objects.create(name=row[0].value)
        return JsonResponse({"message": "upload successful"})
    else:
        return render(request, 'upload.html')

文件下载

文件下载这里主要就是从前端获取要下载的文件。

这个文件可以是从数据库中读出来的,临时写入到服务器某个文件夹内,或者文件就在服务器上存着,这都无所谓。主要就是open读这个文件,然后让浏览器帮我们下载就行了。

文件下载通过两个类来完成:

from django.http import StreamingHttpResponse
from django.http import FileResponse  # 是StreamingHttpResponse的子类,都是流式下载,用哪个都行
from django.utils.encoding import escape_uri_path   # 导入这个家伙防止中文文件名乱码或者打不开

来看示例。

urls.py

from django.contrib import admin
from django.urls import path
from api import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('index/', views.index),
    path('upload/', views.upload),
    path('download1/', views.download1),
    path('download2/', views.download2),
    path('users/', views.users),
]

models.py

from django.db import models


class User(models.Model):
    name = models.CharField(max_length=64, default='')

    def __str__(self):
        return self.name

user_list.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<a href="/download1/">点我下载</a>
<a href="/download2/">点我下载</a>
</body>
</html>

重点在views.py

import os
from openpyxl import load_workbook
from openpyxl.workbook import Workbook
from django.shortcuts import render, redirect, HttpResponse
from django.http import StreamingHttpResponse, FileResponse
from django.utils.encoding import escape_uri_path   # 导入这个家伙
from django.http import JsonResponse
from django.db import transaction
from api.models import User



def create_file(data):
    filename = '学生表.xlsx'
    file_path = os.path.join("media", "files", filename)
    wb = Workbook()
    sheet = wb['Sheet']

    for index, user in enumerate(data, 1):
        cell = sheet[f'A{index}']
        cell.value = user.name
    wb.save(file_path)
    return file_path, filename

def users(request):
    user_list = User.objects.all()
    return render(request, 'user_list.html', {"user_list": user_list})

def download1(request):
    user_list = User.objects.all()
    file_path, filename = create_file(user_list)
    file = open(file_path, 'rb')
    response = StreamingHttpResponse(file)
    response['Content-Type'] = 'application/octet-stream'
    # escape_uri_path中填写路径,防止中文文件名下载后乱码或者无法打开
    response['Content-Disposition'] = 'attachment;filename="{}"'.format(escape_uri_path(filename))
    return response

def download2(request):
    user_list = User.objects.all()
    file_path, filename = create_file(user_list)
    file = open(file_path, 'rb')
    response = FileResponse(file)
    response['Content-Type'] = 'application/octet-stream'
    # escape_uri_path中填写路径,防止中文文件名下载后乱码或者无法打开
    response['Content-Disposition'] = 'attachment;filename="{}"'.format(escape_uri_path(filename))
    return response
posted @ 2023-07-29 17:04  听雨危楼  阅读(218)  评论(0编辑  收藏  举报