Файл не загружается с помощью Flask-wtforms в приложении cookiecutter-flask

У меня возникла проблема с загрузкой файла для работы в приложении cookiecutter-flask (v. 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>

Ответ 1

Просматривая документацию, ссылка, которую вы указали, указывает, что поле data csv является экземпляром werkzeug.datastructures.FileStorage. Документация для FileStorage.save() предполагает, что:

Если пункт назначения является файловым объектом, вы должны закрыть его самостоятельно после вызова.

Может быть, потому, что вы не закрываете файл, он не записывается на диск?

Ответ 2

Попробуйте следующее:

from flask import request

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

Ответ 3

Основная причина ваших проблем здесь:

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

поэтому в validate метод класса UploadForm.

Позвольте быстро исследовать, что здесь происходит.

В 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_validation будет True, ваш метод validate вернет None. Так что должно случиться? Только рендеринг html:

def upload():
    uploadform = UploadForm()
    if uploadform.validate_on_submit(): # <--- here it 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

Ответ 4

Существует более простой способ загрузки файлов, на мой взгляд. Это то, что я реализовал, надеюсь, это может помочь вам. Потому что ваше текущее требование выглядит похожим на мое, ваше решение выглядит немного сложным.

Поэтому я хотел сделать страницу загрузки PDF, это то, что я сделал.

  1. перейдите в файл config.py или где вы определяете ссылку базы данных sql
UPLOAD_FOLDER = r'C:\location\app\upload'
ALLOWED_EXTENSIONS = {'pdf'}
  1. перейдите к вашим представлениям или маршрутам и напишите это, он проверяет, соответствует ли загруженный файл требованию расширения.
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)