Search code examples
flaskflask-wtformswtforms

Flask WTForms: Why is my POST request to upload a file not sending the file data?


I am trying to make a form to upload a file, but the file data is not being sent with the request. I'm manually navigating to my file and hitting submit. My FileRequired validator fails. (And if I don't include it the data field on form.scan_file is empty.)

Here's my form:

from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileAllowed, FileRequired

class ScanForm(FlaskForm):
    scan_file = FileField(validators=[FileAllowed(['nii', 'nii.gz', 'zip']), FileRequired()])

Here's my views.py:

from flask import Blueprint, render_template, request, flash, redirect, url_for, session
from .models import Scan
from .forms import ScanForm
from .service import ScanService
from cookiecutter_mbam.utils import flash_errors

blueprint = Blueprint('scan', __name__, url_prefix='/scans', static_folder='../static')

@blueprint.route('/add', methods=['GET', 'POST'])
def add():
    """Add a scan."""
    form = ScanForm(request.form)
    if form.validate_on_submit():
        f = form.scan_file.data
        service = ScanService()
        xnat_uri = service.upload(session['user_id'], session['curr_experiment'], f)
        Scan.create(xnat_uri=xnat_uri)
        flash('You successfully added a new scan.', 'success')
        return redirect(url_for('experiment.experiments'))
    else:
        flash_errors(form)
    return render_template('scans/upload.html',scan_form=form)

Here's my upload.html:

{% extends "layout.html" %}

{% block content %}


<form method="POST" action="{{ url_for('scan.add') }}" enctype="multipart/form-data">
    {{ scan_form.csrf_token }}

    <input type="file" name="file">
    <input class="btn btn-primary" type="submit" value="Submit">

</form>

{% endblock %}

It doesn't look like I'm making the same mistake as this person. What am I doing wrong?

EDIT: Since posting, I have found this question, but on working through the offered solutions, none seem relevant to my situation.

EDIT 2: At one point, I printed request.files in the Werkzeug debugger and it was an empty dict. I can't reconstruct exactly what I did to get that result. Since then, I've inserted some print statements and in fact, request.files has my file object. So I have a way to retrieve my file. But I am supposed to be able to retrieve my file object at form.scan_file.data (see here). Right now this evaluates to None. More specifically, form.scan_file.has_file() evaluates to False. form.data evaluates to {'scan_file': None, 'csrf_token': <long-random-string> }

Even if I have another way of retrieving my file object, a consequence of this problem is that validation isn't working. My form doesn't pass the FileRequired() validation.

EDIT 3: With my new understanding of my problem, I see that it's similar to this question. However, it's at least apparently not duplicative because none of form = ScanForm(request.form), form = ScanForm(), or form = ScanForm(CombinedMultiDict((request.files, request.form))) make any difference to the behavior outlined in Edit 2.


Solution

  • First of all, check if your data gets posted on that route. Second, I think you don't need to pass request.form to ScanForm, you just need to instantiate it like:

    def add():
        """Add a scan."""
        form = ScanForm()
        ...
    

    To check what gets posted with form, instead of

    if form.validate_on_submit():
    

    you can use, and print form.scan_file.data:

    if form.is_submitted():
        print(form.scan_file.data)
    

    Lastly, you can render input file with {{scan_form.scan_file }} or <input type="file" name="scan_file"> (name attribute of input element should be equal to "scan_file")

    Here is my example:

    Form:

    class ArticleForm(FlaskForm):
        article_image = FileField('Article_image', validators=[FileRequired()])
    

    Form in template:

    <form action="" method="post" enctype="multipart/form-data">
        {{ article_form.csrf_token }}
        {{ article_form.article_image }}
        <input type="submit" value="submit"/>
    </form>
    

    Controller (saving file):

    article_form = ArticleForm()
    
            if article_form.validate_on_submit():
    
                f = article_form.article_image.data
                name = current_user.username + "__" + f.filename
                name = secure_filename(name)
                f.save(os.path.join("./static/article_images/", name))