在cookiecutter-flask应用程序中使用Flask-wtforms时,文件无法上传。

13

我在一个cookiecutter-flask应用程序(版本为0.10.1)中遇到了文件上传问题,目前无法保存上传的文件。

Cookiecutter-Flask默认安装WTForms和Flask-WTForms。我尝试添加Flask-Uploads但我并不确定该模块此时是否有用,因此我已将其卸载。这是Flask-WTF文件上传文档:http://flask-wtf.readthedocs.io/en/latest/form.html#module-flask_wtf.file

文档与我的应用程序之间的主要区别在于,根据cookiecutter的约定,我的信息似乎分散在更多的文件中。

app_name/spreadsheet/forms.py中:

from flask_wtf import Form
from wtforms.validators import DataRequired
from flask_wtf.file import FileField, FileAllowed, FileRequired

class UploadForm(Form):
    """Upload form."""

    csv = FileField('Your CSV', validators=[FileRequired(),FileAllowed(['csv', 'CSVs only!'])])

    def __init__(self, *args, **kwargs):
        """Create instance."""
        super(UploadForm, self).__init__(*args, **kwargs)
        self.user = None

    def validate(self):
        """Validate the form."""
        initial_validation = super(UploadForm, self).validate()
        if not initial_validation:
            return False

app_name/spreadsheet/views.py 文件中:
from flask import Blueprint, render_template
from flask_login import login_required
from werkzeug.utils import secure_filename
from app_name.spreadsheet.forms import UploadForm
from app_name.spreadsheet.models import Spreadsheet
from app_name.utils import flash, flash_errors

blueprint = Blueprint('spreadsheet', __name__, url_prefix='/spreadsheets', static_folder='../static')

@blueprint.route('/upload', methods=['GET', 'POST']) #TODO test without GET since it won't work anyway
@login_required
def upload():
    uploadform = UploadForm()
    if uploadform.validate_on_submit():
        filename = secure_filename(form.csv.data.filename)
        uploadform.csv.data.save('uploads/csvs/' + filename)
        flash("CSV saved.")
        return redirect(url_for('list'))
    else:
        filename = None
    return render_template('spreadsheets/upload.html', uploadform=uploadform)

这是命令行的输出结果,显示在上传文件时没有出现错误:
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [04/Sep/2016 10:29:10] "GET /spreadsheets/upload HTTP/1.1" 200 -
127.0.0.1 - - [04/Sep/2016 10:29:10] "GET /_debug_toolbar/static/css/toolbar.css?0.3058158586562558 HTTP/1.1" 200 -
127.0.0.1 - - [04/Sep/2016 10:29:14] "POST /spreadsheets/upload HTTP/1.1" 200 -
127.0.0.1 - - [04/Sep/2016 10:29:14] "GET /_debug_toolbar/static/css/toolbar.css?0.3790246965220061 HTTP/1.1" 200 -

对于uploads/csvs目录,我尝试了绝对路径和相对路径,并且该目录的权限为766。

模板文件是:

{% extends "layout.html" %}
{% block content %}
    <h1>Welcome {{ session.username }}</h1>

    {% with uploadform=uploadform  %}
        {% if current_user and current_user.is_authenticated and uploadform %}
            <form id="uploadForm" method="POST" class="" action="{{ url_for('spreadsheet.upload') }}" enctype="multipart/form-data">
              <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
              <div class="form-group">
                {{ uploadform.csv(class_="form-control") }}
              </div>
              <button type="submit" class="btn btn-default">Upload</button>
            </form>
        {% endif %}
    {% endwith %}

{% endblock %}

这将生成以下HTML:

        <form id="uploadForm" method="POST" class="" action="/spreadsheets/upload" enctype="multipart/form-data">
          <input type="hidden" name="csrf_token" value="LONG_RANDOM_VALUE"/>
          <div class="form-group">
            <input class="form-control" id="csv" name="csv" type="file">
          </div>
          <button type="submit" class="btn btn-default">Upload</button>
        </form>
4个回答

1
浏览文档,您提供的链接表明csvdata字段是werkzeug.datastructures.FileStorage的一个实例。FileStorage.save()的文档建议:

如果目标是文件对象,则在调用后必须自行关闭它。

难道因为您没有关闭文件,所以它没有被写入磁盘吗?

1

试试这个:

from flask import request

if uploadform.validate_on_submit():
    if 'csv' in request.files:
        csv = request.files['csv']
        csv.save('uploads/csvs/' + csv.filename)

1
你的问题主要出在这里:

def validate(self):
    """Validate the form."""
    initial_validation = super(UploadForm, self).validate()
    if not initial_validation:
        return False

UploadForm类的validate方法中。
让我们快速调查一下此处发生了什么。
views.py文件的行:
if uploadform.validate_on_submit():

flask_wtf 包调用了 validate 方法。所以再次查看您重写的方法:

def validate(self):
    """Validate the form."""
    initial_validation = super(UploadForm, self).validate()
    if not initial_validation:
        return False

这里有什么问题?如果initial_validationTrue,那么您的validate方法将返回None。那么应该发生什么?只需进行html渲染:
def upload():
    uploadform = UploadForm()
    if uploadform.validate_on_submit(): # <--- here it's None
        filename = secure_filename(form.csv.data.filename)
        uploadform.csv.data.save('uploads/csvs/' + filename)
        flash("CSV saved.")
        return redirect(url_for('list'))
    else:                               # <--- so this block runs
        filename = None
    # And your app will only render the same view as when using HTTP GET on that method
    return render_template('spreadsheets/upload.html', uploadform=uploadform)

所以如果不必要覆盖validate方法,就将其删除,如果需要,则调整为返回True

def validate(self):
    """Validate the form."""
    initial_validation = super(UploadForm, self).validate()
    if not initial_validation:
        return False
    return True # <-- this part is missing

当然,你可以使用缩写,我认为这更加恰当:

def validate(self):
    """Validate the form."""
    initial_validation = super(UploadForm, self).validate()
    return not initial_validation

1

我认为有一种更简单的方法来上传文件。这是我实现的,希望能对你有所帮助。因为你目前的要求看起来与我的类似,但你的解决方案看起来有点复杂。

所以我想制作一个PDF上传页面,这就是我所做的。

  1. 转到config.py文件或您定义SQL数据库链接的位置
UPLOAD_FOLDER = r'C:\location\app\upload'
ALLOWED_EXTENSIONS = {'pdf'}

前往您的视图或路由,编写以下内容,它会检查上传的文件是否符合扩展名要求。
def allowed_file(filename):
   return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
  1. 然后,我在这里编写了一个方法将文件名存储在数据库表中。当我调用该函数时,它会查找特定的文件名并将其检索并显示给我。
@app.route("/#route details here", methods=['GET', 'POST'])
def xyz():

    if request.method == 'POST': 
        if 'file' not in request.files:
            flash(f'No file part', 'danger')
            return redirect(request.url)

        file = request.files['file']

        if file.filename == '':
            flash(f'No selected file', 'danger')
            return redirect(request.url)

        if file and allowed_file(file.filename): #allowed file is the definition i created in point 2. 
            filename = secure_filename(file.filename)
            file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) #save file in a target folder.

            new_report = Report(report_name=filename, report_welder_wps_association_id=report_id) #create a database entry with exact filename

            db.session.add(new_report)
            db.session.commit()

            return redirect(url_for(#redirection on success condition))

    return render_template(#render template requirements go here)
  1. 最后,我需要一个视图来在我请求时获取文件。 我只需查询我的数据库,获取文件名,并将其作为参数重定向到此视图,然后它会从目标文件夹中输出文件。
@app.route('/upload/<filename>')
def uploaded_file(filename) -> object:
    return send_from_directory(app.config['UPLOAD_FOLDER'], filename)

这是我需要定义的唯一表单:

class XYZ(db.Model):
    __tablename__ = 'xyz'

    uploaded_file_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    uploaded_file_name = db.Column(db.String(300), nullable=False)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接