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 图片验证码代码分享
Jul 04 Python
python实现删除文件与目录的方法
Nov 10 Python
python学习教程之Numpy和Pandas的使用
Sep 11 Python
python生成器与迭代器详解
Jan 01 Python
python ddt数据驱动最简实例代码
Feb 22 Python
11个Python Pandas小技巧让你的工作更高效(附代码实例)
Apr 30 Python
python tkinter canvas 显示图片的示例
Jun 13 Python
Python 最强编辑器详细使用指南(PyCharm )
Sep 16 Python
给大家整理了19个pythonic的编程习惯(小结)
Sep 25 Python
python队列原理及实现方法示例
Nov 27 Python
python怎么删除缓存文件
Jul 19 Python
解决Django响应JsonResponse返回json格式数据报错问题
Aug 09 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+dojo 的数据库保存拖动布局的一个方法dojo 这里下载
2007/03/07 PHP
基于MySQL体系结构的分析
2013/05/02 PHP
ThinkPHP的I方法使用详解
2014/06/18 PHP
php实现分页显示
2015/11/03 PHP
php-fpm添加service服务的例子
2018/04/27 PHP
PHP如何实现阿里云短信sdk灵活应用在项目中的方法
2019/06/14 PHP
JavaScript动态调整TextArea高度的代码
2010/12/28 Javascript
JS去除数组重复值的五种不同方法
2013/09/06 Javascript
JQuery的$和其它JS发生冲突的快速解决方法
2014/01/24 Javascript
JavaScript插件化开发教程 (二)
2015/01/27 Javascript
js判断文件格式及大小的简单实例(必看)
2016/10/11 Javascript
vue.js初学入门教程(2)
2016/11/07 Javascript
微信小程序 设置启动页面的两种方法
2017/03/09 Javascript
浅谈Vue组件及组件的注册方法
2018/08/24 Javascript
微信小程序自定义弹窗wcPop插件
2018/11/19 Javascript
超好用的jQuery分页插件jpaginate用法示例【附源码下载】
2018/12/06 jQuery
nodejs一个简单的文件服务器的创建方法
2019/09/13 NodeJs
解决antd 表单设置默认值initialValue后验证失效的问题
2020/11/02 Javascript
[02:00]DOTA2英雄COSPLAY闹市街头巡游助威2015国际邀请赛
2015/08/02 DOTA
python中正则表达式的使用详解
2014/10/17 Python
Python的语言类型(详解)
2017/06/24 Python
python reduce 函数使用详解
2017/12/05 Python
python pandas cumsum求累计次数的用法
2019/07/29 Python
使用 python pyautogui实现鼠标键盘控制功能
2019/08/04 Python
Python如何实现小程序 无限求和平均
2020/02/18 Python
Python socket处理client连接过程解析
2020/03/18 Python
CSS3 transforms应用于背景图像的解决方法
2019/04/16 HTML / CSS
调查研究项目计划书
2014/04/29 职场文书
书香家庭事迹材料
2014/05/09 职场文书
保卫钓鱼岛口号
2014/06/20 职场文书
天猫活动策划方案
2014/08/21 职场文书
领导班子党的群众路线对照检查材料
2014/09/25 职场文书
2015年元旦文艺晚会总结(学院)
2014/11/28 职场文书
安全教育的主题班会
2015/08/13 职场文书
Python list去重且保持原顺序不变的方法
2021/04/03 Python
Javascript 解构赋值详情
2021/11/17 Javascript