django上传文件的三种方式


Posted in Python onApril 29, 2021

Django文件上传需要考虑的重要事项

文件或图片一般通过表单进行。用户在前端点击文件上传,然后以POST方式将数据和文件提交到服务器。服务器在接收到POST请求后需要将其存储在服务器上的某个地方。Django默认的存储地址是相对于根目录的/media/文件夹,存储的默认文件名就是文件本来的名字。上传的文件如果不大于2.5MB,会先存入服务器内存中,然后再写入磁盘。如果上传的文件很大,Django会把文件先存入临时文件,再写入磁盘。

Django默认处理方式会出现一个问题,所有文件都存储在一个文件夹里。不同用户上传的有相同名字的文件可能会相互覆盖。另外用户还可能上传一些不安全的文件如js和exe文件,我们必需对允许上传文件的类型进行限制。因此我们在利用Django处理文件上传时必需考虑如下3个因素:

  • 设置存储上传文件的文件夹地址
  • 对上传文件进行重命名
  • 对可接受的文件类型进行限制(表单验证)

注意:以上事项对于上传图片是同样适用的。

Django文件上传的3种常见方式

Django文件上传一般有3种方式(如下所示)。我们会针对3种方式分别提供代码示范。

  • 使用一般的自定义表单上传,在视图中手动编写代码处理上传的文件
  • 使用由模型创建的表单(ModelForm)上传,使用form.save()方法自动存储
  • 使用Ajax实现文件异步上传,上传页面无需刷新即可显示新上传的文件

Ajax文件上传部分见Django与Ajax交互篇。

项目创建与设置

我们先使用django-admin startproject命令创建一个叫file_project的项目,然后cd进入file_project, 使用python manage.py startapp创建一个叫file_upload的app。

我们首先需要将file_upload这个app加入到我们项目里,然后设置/media/和/STATIC_URL/文件夹。我们上传的文件都会放在/media/文件夹里。我们还需要使用css和js这些静态文件,所以需要设置STATIC_URL。

#file_project/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'file_upload',# 新增
]

STATIC_URL = '/static/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, "static"), ]

MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'

#file_project/urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    path('admin/', admin.site.urls),
    path('file/', include("file_upload.urls")),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

创建模型

使用Django上传文件创建模型不是必需,然而如果我们需要对上传文件进行系统化管理,模型还是很重要的。我们的File模型包括file和upload_method两个字段。我们通过upload_to选项指定了文件上传后存储的地址,并对上传的文件名进行了重命名。

#file_upload/models.py
from django.db import models
import os
import uuid

# Create your models here.
# Define user directory path
def user_directory_path(instance, filename):
    ext = filename.split('.')[-1]
    filename = '{}.{}'.format(uuid.uuid4().hex[:10], ext)
    return os.path.join("files", filename)

class File(models.Model):
    file = models.FileField(upload_to=user_directory_path, null=True)
    upload_method = models.CharField(max_length=20, verbose_name="Upload Method")

注意:如果你不使用ModelForm,你还需要手动编写代码存储上传文件。

URLConf配置

本项目一共包括3个urls, 分别对应普通表单上传,ModelForm上传和显示文件清单。

#file_upload/urls.py
from django.urls import re_path, path
from . import views

# namespace
app_name = "file_upload"

urlpatterns = [
    # Upload File Without Using Model Form
    re_path(r'^upload1/$', views.file_upload, name='file_upload'),

    # Upload Files Using Model Form
    re_path(r'^upload2/$', views.model_form_upload, name='model_form_upload'),

    # View File List
    path('file/', views.file_list, name='file_list'),

]

使用一般表单上传文件

我们先定义一个一般表单FileUploadForm,并通过clean方法对用户上传的文件进行验证,如果上传的文件名不以jpg, pdf或xlsx结尾,将显示表单验证错误信息。关于表单的自定义和验证更多内容见Django基础: 表单forms的设计与使用。

#file_upload/forms.py

from django import forms
from .models import File

# Regular form
class FileUploadForm(forms.Form):
    file = forms.FileField(widget=forms.ClearableFileInput(attrs={'class': 'form-control'}))
    upload_method = forms.CharField(label="Upload Method", max_length=20,
                                   widget=forms.TextInput(attrs={'class': 'form-control'}))
    def clean_file(self):
        file = self.cleaned_data['file']
        ext = file.name.split('.')[-1].lower()
        if ext not in ["jpg", "pdf", "xlsx"]:
            raise forms.ValidationError("Only jpg, pdf and xlsx files are allowed.")
        # return cleaned data is very important.
        return file

注意: 使用clean方法对表单字段进行验证时,别忘了return验证过的数据,即cleaned_data。只有返回了cleaned_data, 视图中才可以使用form.cleaned_data.get(‘xxx')获取验证过的数据。

对应一般文件上传的视图file_upload方法如下所示。当用户的请求方法为POST时,我们通过form.cleaned_data.get('file')获取通过验证的文件,并调用自定义的handle_uploaded_file方法来对文件进行重命名,写入文件。如果用户的请求方法不为POST,则渲染一个空的FileUploadForm在upload_form.html里。我们还定义了一个file_list方法来显示文件清单。

#file_upload/views.py

from django.shortcuts import render, redirect
from .models import File
from .forms import FileUploadForm, FileUploadModelForm
import os
import uuid
from django.http import JsonResponse
from django.template.defaultfilters import filesizeformat

# Create your views here.


# Show file list
def file_list(request):
    files = File.objects.all().order_by("-id")
    return render(request, 'file_upload/file_list.html', {'files': files})

# Regular file upload without using ModelForm
def file_upload(request):
    if request.method == "POST":
        form = FileUploadForm(request.POST, request.FILES)
        if form.is_valid():
            # get cleaned data
            upload_method = form.cleaned_data.get("upload_method")
            raw_file = form.cleaned_data.get("file")
            new_file = File()
            new_file.file = handle_uploaded_file(raw_file)
            new_file.upload_method = upload_method
            new_file.save()
            return redirect("/file/")
    else:
        form = FileUploadForm()

    return render(request, 'file_upload/upload_form.html', 
                  {'form': form, 'heading': 'Upload files with Regular Form'}
                 )

def handle_uploaded_file(file):
    ext = file.name.split('.')[-1]
    file_name = '{}.{}'.format(uuid.uuid4().hex[:10], ext)

    # file path relative to 'media' folder
    file_path = os.path.join('files', file_name)
    absolute_file_path = os.path.join('media', 'files', file_name)

    directory = os.path.dirname(absolute_file_path)
    if not os.path.exists(directory):
        os.makedirs(directory)

    with open(absolute_file_path, 'wb+') as destination:
        for chunk in file.chunks():
            destination.write(chunk)

    return file_path

注意:

  • handle_uploaded_file方法里文件写入地址必需是包含/media/的绝对路径,如果/media/files/xxxx.jpg,而该方法返回的地址是相对于/media/文件夹的地址,如/files/xxx.jpg。存在数据中字段的是相对地址,而不是绝对地址。
  • 构建文件写入绝对路径时请用os.path.join方法,因为不同系统文件夹分隔符不一样。写入文件前一个良好的习惯是使用os.path.exists检查目标文件夹是否存在,如果不存在先创建文件夹,再写入。

上传表单模板upload_form.html代码如下:

#file_upload/templates/upload_form.html
{% extends "file_upload/base.html" %}
{% block content %}
{% if heading %}
<h3>{{ heading }}</h3>
{% endif %}

<form action="" method="post" enctype="multipart/form-data" >
  {% csrf_token %}
  {{ form.as_p }}
 <button class="btn btn-info form-control " type="submit" value="submit">Upload</button>
</form>
{% endblock %}

显示文件清单模板file_list.html代码如下所示:

# file_upload/templates/file_list.html
{% extends "file_upload/base.html" %}

{% block content %}
<h3>File List</h3>
<p> <a href="/file/upload1/" rel="external nofollow" >RegularFormUpload</a> | <a href="/file/upload2/" rel="external nofollow" >ModelFormUpload</a>
    | <a href="/file/upload3/" rel="external nofollow" >AjaxUpload</a></p>
{% if files %}
<table class="table table-striped">
    <tbody>
    <tr>
        <td>Filename & URL</td>
        <td>Filesize</td>
        <td>Upload Method</td>
    </tr>
    {% for file in files %}
    <tr>
        <td><a href="{{ file.file.url }}" rel="external nofollow" >{{ file.file.url }}</a></td>
        <td>{{ file.file.size | filesizeformat }}</td>
        <td>{{ file.upload_method }}</td>
    </tr>
    {% endfor %}
    </tbody>
</table>

{% else %}

<p>No files uploaded yet. Please click <a href="{% url 'file_upload:file_upload' %}" rel="external nofollow" >here</a>
    to upload files.</p>
{% endif %}
{% endblock %}

注意: 

  • 对于上传的文件我们可以调用file.url, file.name和file.size来查看上传文件的链接,地址和大小。
  • 上传文件的大小默认是以B显示的,数字非常大。使用Django模板过滤器filesizeformat可以将文件大小显示为人们可读的方式,如MB,KB。

使用ModelForm上传文件

使用ModelForm上传是小编我推荐的上传方式,前提是你已经在模型中通过upload_to选项自定义了用户上传文件存储地址,并对文件进行了重命名。我们首先要自定义自己的FileUploadModelForm,由File模型重建的。代码如下所示:

#file_upload/forms.py
from django import forms
from .models import File

# Model form
class FileUploadModelForm(forms.ModelForm):
    class Meta:
        model = File
        fields = ('file', 'upload_method',)
        widgets = {
            'upload_method': forms.TextInput(attrs={'class': 'form-control'}),
            'file': forms.ClearableFileInput(attrs={'class': 'form-control'}),
        }

    def clean_file(self):
        file = self.cleaned_data['file']
        ext = file.name.split('.')[-1].lower()
        if ext not in ["jpg", "pdf", "xlsx"]:
            raise forms.ValidationError("Only jpg, pdf and xlsx files are allowed.")
        # return cleaned data is very important.
        return file

使用ModelForm处理文件上传的视图model_form_upload方法非常简单,只需调用form.save()即可,无需再手动编写代码写入文件。

#file_upload/views.py

from django.shortcuts import render, redirect
from .models import File
from .forms import FileUploadForm, FileUploadModelForm
import os
import uuid
from django.http import JsonResponse
from django.template.defaultfilters import filesizeformat

# Create your views here.
# Upload File with ModelForm

def model_form_upload(request):
    if request.method == "POST":
        form = FileUploadModelForm(request.POST, request.FILES)
        if form.is_valid():
            form.save() # 一句话足以
            return redirect("/file/")
    else:
        form = FileUploadModelForm()

    return render(request, 'file_upload/upload_form.html', 
                  {'form': form,'heading': 'Upload files with ModelForm'}
                 )

模板跟前面一样,这里就不展示了。

GitHub源码地址

https://github.com/shiyunbo/django-file-upload-download

小结

本文提供并解读了利用Django上传文件的3种主要方式(一般表单上传,ModelForm上传和Ajax上传)及示范代码。

以上就是django上传文件的三种方式的详细内容,更多关于django上传文件的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
python文件比较示例分享
Jan 10 Python
Python实现扫描局域网活动ip(扫描在线电脑)
Apr 28 Python
Python MySQLdb Linux下安装笔记
May 09 Python
python+Django+apache的配置方法详解
Jun 01 Python
快速入手Python字符编码
Aug 03 Python
JavaScript中的模拟事件和自定义事件实例分析
Jul 27 Python
PyCharm 配置远程python解释器和在本地修改服务器代码
Jul 23 Python
python中eval与int的区别浅析
Aug 11 Python
python for循环remove同一个list过程解析
Aug 14 Python
pytorch中的自定义反向传播,求导实例
Jan 06 Python
python 安装impala包步骤
Mar 28 Python
VSCode基础使用与VSCode调试python程序入门的图文教程
Mar 30 Python
详解Django的MVT设计模式
Django如何与Ajax交互
Apr 29 #Python
Python爬虫进阶之Beautiful Soup库详解
Apr 29 #Python
win10+anaconda安装yolov5的方法及问题解决方案
Python图像处理之图像拼接
4种非常实用的python内置数据结构
Apr 28 #Python
Python基础详解之描述符
Apr 28 #Python
You might like
php 之 没有mysql支持时的替代方案
2006/10/09 PHP
PHP页面间参数传递的四种方法详解
2013/06/09 PHP
php实现利用phpexcel导出数据
2013/08/24 PHP
PHP中error_log()函数的使用方法
2015/01/20 PHP
ThinkPHP防止重复提交表单的方法实例分析
2018/05/10 PHP
JsEasy简介 JsEasy是什么?与下载
2007/03/07 Javascript
JAVASCRIPT函数作用域和提前声明 分享
2013/08/22 Javascript
jQuery使用height()获取高度需要注意的地方
2014/12/13 Javascript
JavaScript使用RegExp进行正则匹配的方法
2015/07/11 Javascript
Bootstrap布局组件应用实例讲解
2016/02/17 Javascript
jQuery通过deferred对象管理ajax异步
2016/05/20 Javascript
js中遍历对象的属性和值的方法
2016/07/27 Javascript
angularjs中ng-attr的用法详解
2016/12/31 Javascript
jQuery插件HighCharts实现气泡图效果示例【附demo源码】
2017/03/13 Javascript
js 提取某()特殊字符串长度的实例
2017/12/06 Javascript
three.js实现圆柱体
2018/12/30 Javascript
解决Vue在Tomcat8下部署页面不加载的问题
2019/11/12 Javascript
Node.js API详解之 tty功能与用法实例分析
2020/04/27 Javascript
Vue使用axios引起的后台session不同操作
2020/08/14 Javascript
基于JavaScript实现大文件上传后端代码实例
2020/08/18 Javascript
[02:36]DOTA2亚洲邀请赛小组赛精彩集锦:EE凭借法力虚空拿下4杀
2017/03/30 DOTA
[02:10]三分钟回顾完美世界城市挑战赛
2019/01/24 DOTA
Python网络爬虫神器PyQuery的基本使用教程
2018/02/03 Python
解决pandas中读取中文名称的csv文件报错的问题
2018/07/04 Python
详解python tkinter教程-事件绑定
2019/03/28 Python
Pytorch Tensor基本数学运算详解
2019/12/30 Python
Python获取、格式化当前时间日期的方法
2020/02/10 Python
Python word文本自动化操作实现方法解析
2020/11/05 Python
Kipling凯浦林美国官网:世界著名时尚休闲包袋品牌
2016/08/24 全球购物
StubHub澳大利亚:购买或出售您的门票
2019/08/01 全球购物
土木工程毕业生推荐信
2013/10/28 职场文书
财务会计专业推荐信
2013/11/30 职场文书
电子商务专业自我鉴定
2013/12/18 职场文书
校园摄影活动策划方案
2014/02/05 职场文书
2014年校长工作总结
2014/12/11 职场文书
硕士毕业答辩开场白
2015/05/27 职场文书