Файл не загружается с помощью 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, это то, что я сделал.
- перейдите в файл 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
- Затем я сделал метод для сохранения имени файла в таблице в базе данных. Когда я вызываю функцию, она ищет в папке это конкретное имя файла, извлекает и показывает его мне.
@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)
- И, наконец, вид, чтобы получить файл всякий раз, когда я запрашиваю его. Я просто запрашиваю свою базу данных, получаю имя файла и перенаправляю его в это представление с именем файла в качестве параметра, и он выплевывает файл из целевой папки.
@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)