关于python:翻译使用-Flask-处理文件上传

2次阅读

共计 13975 个字符,预计需要花费 35 分钟才能阅读完成。

翻译
Handling File Uploads With Flask

Web 应用程序的一个常见个性是容许用户将文件上传到服务器。在 RFC 1867 中协定记录了客户端上传文件的机制,咱们最喜爱的 Web 框架 Flask 齐全反对这一机制,然而对于许多开发者来说,还有许多实现细节未遵循该正式标准。诸如在何处存储上传的文件,如何预先应用它们,或者如何爱护服务器不受歹意文件上传的影响,这些都会产生很多凌乱和不确定性。

在本文中,我将向你展现如何为 Flask 服务器实现弱小的文件上传性能,该性能不仅反对基于 Web 浏览器中的标准文件上传并且与基于 JavaScript 的上传小部件兼容:

根本文件上传表单

从高层次的角度来看,上传文件的客户端与其余任何表单数据提交一样。换句话说,你必须定义一个蕴含文件字段的 HTML 表单。

上面是一个简略的 HTML 页面,该表单承受一个文件:

<!doctype html>
<html>
  <head>
    <title>File Upload</title>
  </head>
  <body>
    <h1>File Upload</h1>
    <form method="POST" action=""enctype="multipart/form-data">
      <p><input type="file" name="file"></p>
      <p><input type="submit" value="Submit"></p>
    </form>
  </body>
</html>


你可能晓得,<form>元素的 method 属性能够是 GETPOST。应用 GET 时,数据将在申请 URL 的查问字符串中提交,而应用 POST 时,数据将进入申请主体。在表单中蕴含文件时,必须应用 POST,因为不可能在查问字符串中提交文件数据。

没有文件的表单通常不蕴含 <form>元素中的 enctype 属性。此属性定义浏览器在将数据提交到服务器之前应该如何格式化数据。HTML 标准为其定义了三个可能的值:

  • application/x-www-form-urlencoded:

这是默认格局,也是不蕴含文件字段的表单的最佳格局

  • multipart/form-data:

如果表单中至多有一个字段是文件字段,则须要此格局

  • text/plain:

这种格局没有理论用处,所以你应该疏忽它

理论的文件字段是咱们用于大多数其余表单字段的规范 <input> 元素,其类型设置为 file。在下面的示例中,我没有蕴含任何其余属性,然而 file 字段反对两个有时有用的属性:

  • multiple

可用于容许在单个文件字段中上载多个文件。例如:

<input type="file" name="file" multiple>
  • accept

能够用于筛选容许的文件类型,这些文件类型能够通过文件扩展名或媒体类型抉择。例子:

<input type="file" name="doc_file" accept=".doc,.docx">
<input type="file" name="image_file" accept="image/*">

应用 Flask 承受文件提交

对于惯例表单,Flask 提供了对 request.form 字典中提交的表单字段的拜访。然而,文件字段蕴含在 request.files 字典中。request.formrequest.files 字典实际上是“multi-dicts”,它是一种反对反复键的专门字典实现。这是必要的,因为表单能够蕴含多个具备雷同名称的字段,通常状况下是由多组复选框组成。对于容许多个文件的文件字段,也会产生这种状况。

临时疏忽诸如验证和安全性等重要方面,上面简短的 Flask 应用程序承受应用上一节中定义的表单上传的文件,并将提交的文件写入当前目录:

from flask import Flask, render_template, request, redirect, url_for
app = Flask(__name__)
@app.route('/')
def index():
    return render_template('index.html')

@app.route('/', methods=['POST'])
def upload_file():
    uploaded_file = request.files['file']
    if uploaded_file.filename != '':
        uploaded_file.save(uploaded_file.filename)
    return redirect(url_for('index'))

upload_file() 函数应用 @app.route 装璜,以便在浏览器发送 POST 申请时调用该函数。请留神,同一个根 URL 是如何在两个视图函数之间进行拆分的,并将 index() 设置为承受 GET 申请,将 upload_file`() 上传为 POST` 申请。

uploaded_file变量保留提交的文件对象。这是 Flask 从 Werkzeug 导入的 FileStorage 类的实例。

FileStorage 中的 filename 属性提供客户端提交的文件名。如果用户提交表单时没有在 file 字段中抉择文件,那么文件名将是一个空字符串,因而始终查看文件名以确定文件是否可用是很重要的。

Flask 收到文件提交后,不会主动将其写入磁盘。这实际上是一件坏事,因为它使应用程序有机会查看和验证文件提交,这一点将在前面看到。能够从 stream 属性拜访理论文件数据。如果应用程序只想将文件保留到磁盘,则能够调用 save()办法,并将所需门路作为参数传递。如果未调用文件的 save() 办法,则该文件将被抛弃。​

是否要应用此应用程序测试文件上传?为你的应用程序创立目录,并将下面的代码编写为 app.py。而后创立一个模板子目录,并将上一节中的 HTML 页面编写为 templates/index.html。创立一个虚拟环境并在其上装置 Flask,而后应用 flask run 运行该应用程序。每次提交文件时,服务器都会把它的正本写到当前目录中。

在持续探讨安全性主题之前,我将探讨下面的代码的一些变体,你可能会发现这些变体很有用。如前所述,能够将文件上传字段配置为承受多个文件。如果像下面那样应用 request.files['file'],则只会失去一个提交的文件,然而应用 getlist() 办法,你能够在 for 循环中拜访所有文件:

for uploaded_file in request.files.getlist('file'):
    if uploaded_file.filename != '':
        uploaded_file.save(uploaded_file.filename)

许多人在 Flask 中编写表单解决路由时,对 GET 和 POST 申请应用单个视图函数。应用单视图函数的示例应用程序的版本编码如下:

@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'POST':
        uploaded_file = request.files['file']
        if uploaded_file.filename != '':
            uploaded_file.save(uploaded_file.filename)
        return redirect(url_for('index'))
    return render_template('index.html')

最初,如果应用 Flask-WTF 扩大来解决表单,则能够应用 FileField 对象上传文件。到目前为止,你看到的例子中应用的表单能够应用 Flask-WTF 编写如下:

from flask_wtf import FlaskForm
from flask_wtf.file import FileField
from wtforms import SubmitField
class MyForm(FlaskForm):
    file = FileField('File')
    submit = SubmitField('Submit')

留神,FileField 对象来自 flask_wtf 包,与大多数其余字段类不同,后者间接从 wtforms 包导入。Flask-WTF 为文件字段提供了两个验证器,FileRequired 和 FileAllowed,前者执行相似于空字符串查看的查看,后者确保文件扩展名蕴含在容许的扩展名列表中。

当您应用 Flask-WTF 表单时,file 字段对象的 data 属性指向 FileStorage 实例,因而将文件保留到磁盘的工作形式与下面的示例雷同。

爱护文件上传

上一节中给出的文件上传示例是一个非常简单的实现,不是很强壮。Web 开发中最重要的规定之一是永远不要信赖客户提交的数据,因而在应用惯例表单时,像 Flask-WTF 这样的扩大会在承受表单和整合数据到应用程序中之前对所有字段进行严格验证。对于蕴含文件字段的表单,也须要进行验证,因为如果不进行文件验证,服务器将为攻打敞开大门。例如:

  • 攻击者能够上传一个十分大的文件,以至于服务器中的磁盘空间齐全被填满,从而导致服务器呈现故障
  • 攻击者能够应用文件名(例如../../../.bashrc 或相似文件)的上传申请,以试图坑骗服务器重写零碎配置文件。
  • 攻击者能够上传带有病毒或其余类型恶意软件的文件到应用程序须要应用的地位,例如,用户头像

    限度上传文件的大小

    为了避免客户端上传十分大的文件,您能够应用 Flask 提供的配置选项。MAX_CONTENT_LENGTH 选项管制申请主体能够领有的最大大小。尽管这不是一个特定于文件上传的选项,但设置一个最大的申请体大小无效地使 Flask 应用 413 状态码抛弃大于容许的申请体大小的申请

让咱们批改上一节中的 app.py 示例,只承受最大为 1 MB 的申请:

app.config['MAX_CONTENT_LENGTH'] = 1024 * 1024

如果你试图上传一个大于 1 MB 的文件,应用程序当初将回绝它。

验证文件名

咱们不能齐全置信客户端提供的文件名是无效的和能够平安应用的,所以随上传文件一起提供的文件名必须通过验证。

要执行的一个非常简单的验证是确保文件扩展名是应用程序违心承受的扩展名,这与应用 Flask-WTF 时 F FileAllowed验证器所做的相似。假如应用程序承受图像,那么它能够配置容许的文件扩展名列表:

app.config['UPLOAD_EXTENSIONS'] = ['.jpg', '.png', '.gif']

对于每个上传的文件,应用程序能够确保文件扩展名是容许的:

filename = uploaded_file.filename
if filename != '':
    file_ext = os.path.splitext(filename)[1]
    if file_ext not in current_app.config['UPLOAD_EXTENSIONS']:
        abort(400)

应用这种逻辑,任何不在容许的文件扩展名的文件名,都会呈现 400 谬误。

除了文件扩展名之外,验证文件名以及提供的任何门路也很重要。如果你的应用程序不关怀客户端提供的文件名,则解决上传的最平安办法是疏忽客户端提供的文件名,而是生成本人的文件名,而后传递给 save() 办法。这种技术工作良好的示例是头像上传。每个用户的头像都能够应用用户 ID 保留为文件名,因而客户端提供的文件名能够抛弃。如果你的应用程序应用 Flask-Login,则能够实现以下 save()调用:

uploaded_file.save(os.path.join('static/avatars', current_user.get_id()))

在其余状况下,保留客户端提供的文件名可能更好,因而必须首先清理文件名。对于这些状况,Werkzeug 提供了 secure_filename()函数。让咱们通过在 Python shell 中运行一些测试来看看这个函数是如何工作的:

>>> from werkzeug.utils import secure_filename
>>> secure_filename('foo.jpg')
'foo.jpg'
>>> secure_filename('/some/path/foo.jpg')
'some_path_foo.jpg'
>>> secure_filename('../../../.bashrc')
'bashrc'

正如你在示例中看到的,无论文件名有如许简单或如许歹意,secure_filename() 函数都将其缩减为一个单位文件名。

让咱们将 secure_filename()合并到示例上传服务器中,并增加一个配置变量,该变量定义文件上传的专用地位。上面是带有平安文件名的残缺 app.py 源文件:

import os
from flask import Flask, render_template, request, redirect, url_for, abort
from werkzeug.utils import secure_filename
app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 1024 * 1024
app.config['UPLOAD_EXTENSIONS'] = ['.jpg', '.png', '.gif']
app.config['UPLOAD_PATH'] = 'uploads'
@app.route('/')
def index():
    return render_template('index.html')
@app.route('/', methods=['POST'])
def upload_files():
    uploaded_file = request.files['file']
    filename = secure_filename(uploaded_file.filename)
    if filename != '':
        file_ext = os.path.splitext(filename)[1]
        if file_ext not in app.config['UPLOAD_EXTENSIONS']:
            abort(400)
        uploaded_file.save(os.path.join(app.config['UPLOAD_PATH'], filename))
    return redirect(url_for('index'))

留神
secure_filename 函数将过滤所有非 ASCII 字符,因而,如果 filename 是 “ 头像.jpg” 之类的,则后果为 ”jpg”,但没有格局,这是个问题,我倡议应用 uuid 模块重命名上传的文件,以避免出现上述情况。

验证文件内容

我将要探讨的第三层验证是最简单的。如果您的应用程序承受某种文件类型的上传,那么现实状况下,它应该执行某种模式的内容验证,并回绝任何不同类型的文件。

如何实现内容验证在很大水平上取决于应用程序承受的文件类型。对于本文中的示例应用程序,我应用的是图像,因而能够应用 Python 规范库中的 imghdr 包验证文件头实际上是一个图像。

让咱们编写一个 validate_image() 函数,对图像执行内容验证:

import imghdr
def validate_image(stream):
    header = stream.read(512)
    stream.seek(0)
    format = imghdr.what(None, header)
    if not format:
        return None
    return '.' + (format if format != 'jpeg' else 'jpg')

这个函数以一个字节流作为参数。它首先从流中读取 512 个字节,而后重置流指针,因为稍后当调用 save ()函数时,咱们心愿它看到整个流。前 512 字节的图像数据将足以辨认图像的格局。

如果第一个参数是文件名,imghdr.what()函数能够查看存储在磁盘上的文件; 如果第一个参数是 None,数据在第二个参数中传递,则能够查看存储在内存中的数据。FileStorage 对象为咱们提供了一个流,因而最不便的选项是从它中读取平安数量的数据,并在第二个参数中将其作为字节序列传递。

imghdr.what() 的返回值是检测到的图像格式。该函数反对多种格局,其中包含风行的 jpegpnggif。如果未检测到已知的图像格式,则返回值为 None。如果检测到格局,则返回该格局的名称。最不便的是将格局作为文件扩展名返回,因为应用程序能够确保检测到的扩展名与文件扩展名匹配,所以 validate_image() 函数将检测到的格局转换为文件扩展名。这很简略,只需为除 jpeg 外的所有图像格式增加一个点作为前缀,jpeg 除外,通常应用 .jpg 扩展名。

上面是残缺的 app.py,蕴含后面几节中的所有个性和内容验证:

import imghdr
import os
from flask import Flask, render_template, request, redirect, url_for, abort
from werkzeug.utils import secure_filename
app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 1024 * 1024
app.config['UPLOAD_EXTENSIONS'] = ['.jpg', '.png', '.gif']
app.config['UPLOAD_PATH'] = 'uploads'
def validate_image(stream):
    header = stream.read(512)
    stream.seek(0) 
    format = imghdr.what(None, header)
    if not format:
        return None
    return '.' + (format if format != 'jpeg' else 'jpg')
@app.route('/')
def index():
    return render_template('index.html')
@app.route('/', methods=['POST'])
def upload_files():
    uploaded_file = request.files['file']
    filename = secure_filename(uploaded_file.filename)
    if filename != '':
        file_ext = os.path.splitext(filename)[1]
        if file_ext not in app.config['UPLOAD_EXTENSIONS'] or \
                file_ext != validate_image(uploaded_file.stream):
            abort(400)
        uploaded_file.save(os.path.join(app.config['UPLOAD_PATH'], filename))
    return redirect(url_for('index'))

在视图函数中惟一的变动就是退出了最初一个验证逻辑:

            if file_ext not in app.config['UPLOAD_EXTENSIONS'] or \
                file_ext != validate_image(uploaded_file.stream):
            abort(400)

这个扩大查看首先确保文件扩展名在容许的列表中,而后确保通过查看数据流检测到的文件扩展名与文件扩展名雷同。

在测试这个版本的应用程序之前,创立一个名为 _uploads_的目录(或者你在 UPLOAD_PATH 配置变量中定义的门路),以便能够将文件保留在那里。

应用上传的文件

你当初晓得如何解决文件上传。对于某些应用程序,这就是所须要的全部内容,因为这些文件用于某些外部过程。然而对于大量的应用程序,特地是那些具备社交性能的应用程序,比方头像,用户上传的文件必须与应用程序集成。以 avatar为例,一旦用户上传了他们的 avatars 图片,任何提到用户名的中央都须要上传的图片显示在侧面。

我将文件上传分为两大类,具体取决于用户上传的文件是供公众应用还是对每个用户公有。本文中屡次探讨过的 avatar 图像显然属于第一类,因为这些 avatar 旨在与其余用户公开共享。另一方面,对上传的图像执行编辑操作的应用程序可能在第二类中,因为你心愿每个用户只能拜访本人的图像。

公共文件上传

当图像属于公共性质时,使图像可供应用程序应用的最简略办法是将上传目录放在应用程序的动态文件夹中。例如,能够在 static 中创立 _avatars_子目录,而后应用用户 id 作为名称在该地位保留头像。

应用 url_for() 函数以与应用程序的惯例动态文件雷同的形式援用存储在动态文件夹的子目录中的这些上传文件。我之前倡议在保留上传的头像图像时应用用户 id 作为文件名。这就是图片保留的形式:

uploaded_file.save(os.path.join('static/avatars', current_user.get_id()))

应用这个实现,给定一个用户 id,能够生成用户头像的 URL 如下:

url_for('static', filename='avatars/' + str(user_id))

或者,能够将上传保留到动态文件夹外的目录中,而后能够增加新的路由来为其提供服务。在示例 app.py 应用程序文件中,上传的文件保留到 UPLOAD_PATH 配置变量中设置的地位。为了从该地位提供这些文件,咱们能够实现以下路由:

from flask import send_from_directory
@app.route('/uploads/<filename>')
def upload(filename):
    return send_from_directory(app.config['UPLOAD_PATH'], filename)

这个解决方案比在动态文件夹中存储上传的一个长处是,在返回这些文件之前,你能够实现额定的限度,要么间接在函数体内应用 Python 逻辑,要么应用 decorator。例如,如果你只心愿向登录的用户提供对上传的拜访,那么你能够将 Flask-Login 的 @login_required 装璜器增加到这个路由中,或者增加用于失常路由的任何其余身份验证或角色查看机制。

让咱们应用这种实现思维在示例应用程序中显示上传的文件。上面是 app.py 的一个新的残缺版本:

import imghdr
import os
from flask import Flask, render_template, request, redirect, url_for, abort, \
    send_from_directory
from werkzeug.utils import secure_filename


app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 1024 * 1024
app.config['UPLOAD_EXTENSIONS'] = ['.jpg', '.png', '.gif']
app.config['UPLOAD_PATH'] = 'uploads'


def validate_image(stream):
    header = stream.read(512)  # 512 bytes should be enough for a header check
    stream.seek(0)  # reset stream pointer
    format = imghdr.what(None, header)
    if not format:
        return None
    return '.' + (format if format != 'jpeg' else 'jpg')


@app.route('/')
def index():
    files = os.listdir(app.config['UPLOAD_PATH'])
    return render_template('index.html', files=files)


@app.route('/', methods=['POST'])
def upload_files():
    uploaded_file = request.files['file']
    filename = secure_filename(uploaded_file.filename)
    if filename != '':
        file_ext = os.path.splitext(filename)[1]
        if file_ext not in app.config['UPLOAD_EXTENSIONS'] or \
                file_ext != validate_image(uploaded_file.stream):
            abort(400)
        uploaded_file.save(os.path.join(app.config['UPLOAD_PATH'], filename))
    return redirect(url_for('index'))


@app.route('/uploads/<filename>')
def upload(filename):
    return send_from_directory(app.config['UPLOAD_PATH'], filename)

除了新的 upload() 函数之外,index() 视图函数应用 os.listdir ()获取上传地位中的文件列表,并将其发送到模板以进行出现。更新后的 _index.html _模板内容如下:

<!doctype html>
<html>
  <head>
    <title>File Upload</title>
  </head>
  <body>
    <h1>File Upload</h1>
    <form method="POST" action=""enctype="multipart/form-data">
      <p><input type="file" name="file"></p>
      <p><input type="submit" value="Submit"></p>
    </form>
    <hr>
    {% for file in files %}
      <img src="{{url_for('upload', filename=file) }}" style="width: 64px">
    {% endfor %}
  </body>
</html>

有了这些扭转,每次你上传一张图片,页面底部就会增加一个缩略图:

公有文件上传

当用户将公有文件上传到应用程序时,须要进行额定的查看,以避免一个用户与未经受权的方共享文件。这些状况的解决方案须要下面所示的 upload() 视图函数的变体,以及额定的拜访查看。

一个常见的要求是只与所有者共享上传的文件。当存在此需要时,存储上传的一种不便办法是为每个用户应用一个独自的目录。例如,能够将给定用户的上传保留到 uploads/<user_id> 目录,而后能够批改 uploads() 函数,使其只服务于用户本人的上传目录,这样一来,一个用户就不可能从另一个用户那里查看文件。上面你能够看到这个技术的一个可能的实现,再次假如应用了 Flask-Login:

@app.route('/uploads/<filename>')
@login_required
def upload(filename):
    return send_from_directory(os.path.join(app.config['UPLOAD_PATH'], current_user.get_id()), filename)

显示上传进度

到目前为止,咱们始终依赖 web 浏览器提供的原生文件上传小部件来启动咱们的文件上传。我置信咱们都批准这个小工具不是很吸引人。不仅如此,因为短少上传进度显示,它无奈用于上传大文件,因为用户在整个上传过程中不能收到任何反馈。尽管本文的范畴是涵盖服务器端,但我认为,如果可能给你提供一些对于如何实现一个基于 JavaScript 的古代文件上传小部件以显示上传进度的想法将很有用。​

好消息是,在服务器上不须要进行任何大的更改,无论你在浏览器中应用哪种办法来启动上传,上传机制都以雷同的形式工作。为了向你展现一个示例实现,我将用与风行的文件上传客户端 dropzone.js 兼容的 index.html 替换 HTML 表单。

上面是一个新版本的 templates/index.html,它从 CDN 加载下拉区 CSS 和 JavaScript 文件,并依据 dropzone documentation 实现一个上传表单:

<html>
  <head>
    <title>File Upload</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.7.1/min/dropzone.min.css">
  </head>
  <body>
    <h1>File Upload</h1>
    <form action="{{url_for('upload_files') }}" class="dropzone">
    </form>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.7.1/min/dropzone.min.js"></script>
  </body>
</html>

在实现 dropzone 时,我发现了一件乏味的事件,那就是它要求设置 <form> 元素中的 action 属性,即便规范 forms 承受一个空的动作来批示提交到雷同的 URL。

用这个新版的模板启动服务器,你会失去以下后果:

基本上就是这样!当初你能够拖拽文件,它们将上传到服务器,并带有一个进度条和胜利或失败的最终批示。

如果文件上传失败,无论是因为文件太大或有效,dropzone 想要显示一个谬误音讯。因为咱们的服务器正在返回 413 和 400 谬误的规范 Flask 谬误页面,您将在谬误弹出窗口中看到一些乌七八糟的 HTML。为了纠正这个谬误,咱们能够更新服务器以文本模式返回谬误响应。

当申请无效负载大于配置中设置的大小时,Flask 会生成文件过大条件的 413 谬误。要笼罩默认的谬误页面,咱们必须应用 app.errorhandler 装璜器:

@app.errorhandler(413)
def too_large(e):
    return "File is too large", 413

当任何验证查看失败时,应用程序将生成第二个谬误条件。在这种状况下,谬误是通过一个 abort(400) 调用生成的。取而代之的是,能够间接生成响应:

        if file_ext not in app.config['UPLOAD_EXTENSIONS'] or \
                file_ext != validate_image(uploaded_file.stream):
            return "Invalid image", 400

我要做的最初一个扭转并不是真的须要,然而它节俭了一点带宽。对于胜利上传,服务器返回一个 redirect() 到主路由。这将导致上传表单再次显示,并刷新页面底部的上载缩略图列表。当初这些都不须要了,因为上传是作为后盾申请通过 dropzone 实现的,所以咱们能够打消重定向,并应用代码 204 切换到空响应。

上面是 app.py 的残缺更新版本,能够与 dropzone.js 一起应用:

import imghdr
import os
from flask import Flask, render_template, request, redirect, url_for, abort, \
    send_from_directory
from werkzeug.utils import secure_filename


app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 2 * 1024 * 1024
app.config['UPLOAD_EXTENSIONS'] = ['.jpg', '.png', '.gif']
app.config['UPLOAD_PATH'] = 'uploads'


def validate_image(stream):
    header = stream.read(512)
    stream.seek(0)
    format = imghdr.what(None, header)
    if not format:
        return None
    return '.' + (format if format != 'jpeg' else 'jpg')


@app.errorhandler(413)
def too_large(e):
    return "File is too large", 413


@app.route('/')
def index():
    files = os.listdir(app.config['UPLOAD_PATH'])
    return render_template('index.html', files=files)


@app.route('/', methods=['POST'])
def upload_files():
    uploaded_file = request.files['file']
    filename = secure_filename(uploaded_file.filename)
    if filename != '':
        file_ext = os.path.splitext(filename)[1]
        if file_ext not in app.config['UPLOAD_EXTENSIONS'] or \
                file_ext != validate_image(uploaded_file.stream):
            return "Invalid image", 400
        uploaded_file.save(os.path.join(app.config['UPLOAD_PATH'], filename))
    return '', 204


@app.route('/uploads/<filename>')
def upload(filename):
    return send_from_directory(app.config['UPLOAD_PATH'], filename)

用这个更新重新启动应用程序,当初谬误将会有一个正确的音讯:

dropzone.js 库非常灵活,有许多定制选项,因而我激励你拜访他们的文档,理解如何使其适应你的需要。你也能够寻找其余的 JavaScript 文件上传库,因为它们都遵循 HTTP 规范,这意味着你的 Flask 服务器能够很好地与它们一起工作。

正文完
 0