I am having a problem getting a file upload to work in a cookiecutter-flask app (v. 0.10.1). Right now, it is not saving the file uploaded.
Cookiecutter-Flask by default installs WTForms and Flask-WTForms. I have tried adding Flask-Uploads to this but I'm not convinced that module adds anything at this point so I have uninstalled it. This is the Flask-WTF file upload documentation: http://flask-wtf.readthedocs.io/en/latest/form.html#module-flask_wtf.file
The main difference between the documentation and my app is that I seem to have information across more files, in keeping with the conventions of the cookiecutter.
In 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
In 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)
This is the command line output showing no errors when I upload a file:
* 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 -
For the uploads/csvs directory I have tried absolute and relative paths and the directory is permissioned 766.
The template file is:
{% 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 %}
Which generates this 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>
Looking through the documentation, the link you provided indicates that the data field of csv is an instance of werkzeug.datastructures.FileStorage. The documentation for FileStorage.save() suggests that:
If the destination is a file object you have to close it yourself after the call.
Could it be that because you aren't closing the file, it isn't being written to disk?
Try this:
from flask import request
if uploadform.validate_on_submit():
if 'csv' in request.files:
csv = request.files['csv']
csv.save('uploads/csvs/' + csv.filename)
The main reason of your problem lands here:
def validate(self):
"""Validate the form."""
initial_validation = super(UploadForm, self).validate()
if not initial_validation:
return False
so in validate method of UploadForm class.
Let's quick investigate what is happening here.
In views.py in line:
if uploadform.validate_on_submit():
flask_wtf package calls validate method. So take a look again on your overwritten method:
def validate(self):
"""Validate the form."""
initial_validation = super(UploadForm, self).validate()
if not initial_validation:
return False
what is wrong here? In case initial_validation would be True, your validate method will return None. So what should happen? Only html rendering:
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)
So if overwriting validate method is not necessary, then just remove it, and if is, then adjust it to return 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
Of course you can use shortened and I think more appropriate version:
def validate(self):
"""Validate the form."""
initial_validation = super(UploadForm, self).validate()
return not initial_validation
There is a simpler way to upload files in my opinion.
This is something I implemented, hope it can be of help to you. Cause your current requirement looks similar to mine yet, your solution looks a little complex.
So I wanted to make a pdf uploader page, this is what I did.
go to the config.py file or where you define the sql database link
UPLOAD_FOLDER = r'C:\location\app\upload'
ALLOWED_EXTENSIONS = {'pdf'}
go to your views or routes and write this, it checks if the file uploaded matchs the extension requirement.
def allowed_file(filename):
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
Then,
what i did here is i made a method to store a filename in a table in database. When i call a function,it looks in the folder for that particular filename and retrieves and shows it to me.
#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)
And finally a view to obtain the file whenever i request it.
I just query my database, get the filename and redirect it to this view with the filename as parameter, and it spits out the file from the target folder.
#app.route('/upload/<filename>')
def uploaded_file(filename) -> object:
return send_from_directory(app.config['UPLOAD_FOLDER'], filename)
And this is the only form i need to define :
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)
Related
I am trying to build a website in python using flask that takes file uploads and saves them to a folder (called uploads). However, when two files with the same name are uploaded, the first one is overwritten by the last one. How can I prevent this in a way that means that I don't lose any files? Would adding a timestamp to the filename help or would there still be an issue if two files are uploaded at the same time?
Thanks!
Filesystem
FlaskProject:
├───static
├───templates
├───uploads
└───app.py
Html
{%extends "base.html" %}
{%block content%}
<p>
<h2>Upload Below!</h2>
<div></div>
<form action = "http://localhost:5000/upload_complete" method = "POST"
enctype = "multipart/form-data">
<input type = "file" name = "file" />
<input type = "submit"/>
</form>
{% endblock %}
Python
from flask import Flask, redirect, url_for, render_template, request
from werkzeug.utils import secure_filename
import os
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = "D:/Me/FlaskProject/uploads"
#app.route('/upload')
def upload():
return render_template("upload.html", page="Upload Images")
#app.route('/upload_complete', methods = ['GET', 'POST'])
def upload_complete():
if request.method == 'POST':
f = request.files['file']
f.save(os.path.join(app.config['UPLOAD_FOLDER'], secure_filename(f.filename)))
return redirect(url_for('upload_complete'))
return render_template("upload_complete.html", page="Upload Complete")
if __name__ == '__main__':
app.debug = True
app.run()
app.run(debug = True)
(upload_complete.html is just a thank you screen)
You can generate an uuid and update the filename with it.
You can generate uuid like this,
import uuid
... your code ..
hash_value = uuid.uuid4().hex
f.save(os.path.join(app.config['UPLOAD_FOLDER'], hash_value + secure_filename(f.filename)))
I got an error when trying to run my Flask app of: BuildError: Could not build url for endpoint 'result' with values ['resultFound']. Did you mean 'menu' instead?
The problem has to do with the calling of POST. I have attached the code for 4 files that relate to this error, but I left out the import packages and other parts of the files. I would greatly appreciate your help. Thanks a lot. If you would like any other code I could add it.
This is my main python function that is running the flask app.
#app.route("/search", methods=["POST", "GET"])
def search():
if request.method == "POST":
user = request.form["searching"]
return redirect(url_for('result', resultFound = user))
else:
return render_template("search.html")
app.route("/<resultFound>")
def result(resultFound):
return render_template('result.html', nameartist = artistName(resultFound), numfollowers = artistfollower(resultFound))
This is the python file that is getting the information for the results.html with the input value from the search.html.
def artists(searchinput):
searchResults = spotifyObject.search(searchinput,1,0,"artist")
artist = searchResults['artists']['items'][0]
return artist
def artistname(inputvalue):
value = artists(inputvalue)
artistName = value['name']
return artistName
def artistfollower(inputvalue):
value = artists(inputvalue)
artistfollowers = value['followers']['total']
return artistfollowers
This is the search.html that gets the input value.
<form action="#" method="post">
<input type="text" id="myText" name="searching" value="input artist">
<p><input type="submit" value="submit" /></p>
</form>
This is the result.html that is using the input value from search.html and getting data with the help of the python file.
<p>The artist {{ nameartist }} has {{ numfollowers }} followers.</p>
BuildError: Could not build url for endpoint 'result' with values ['resultFound'].
because you are missing # in result route decorator (#app and not app)
#app.route("/<resultFound>")
def result(resultFound):
[..]
I have a table using the following code, which includes two columns: name and filename. My problem is that when I upload the file , then its file name is stored in the filename column. How do I do this?
Now when I upload the file, 'None' is placed in the filename.i can only upload file or enter name in database,i think that problem is enctype="multipart/form-data".
from flask_wtf import FlaskForm
from flask_wtf.file import FileField
from wtforms import StringField,SelectField,IntegerField
from wtforms.ext.sqlalchemy.fields import QuerySelectField
from wtforms.validators import DataRequired, Email, Length
class ContactForm(FlaskForm):
name = IntegerField('name', validators=[Length(min=-1, max=100, message='You cannot have more than 100 characters')])
filename = FileField()
this is my app.py:
def new_contact():
'''
Create new contact
'''
form = ContactForm()
if form.validate_on_submit():
return render_template('web/new_contact.html',form = form)
f = form.filename.data
f.save(os.path.join("./static/upload/", f.filename))
return redirect(url_for('new_contact'))
print(f)
my_contact = Contact()
form.populate_obj(my_contact)
db.session.add(my_contact)
try:
db.session.commit()
# User info
flash('Contact created correctly', 'success')
return redirect(url_for('contacts'))
except:
db.session.rollback()
flash('Error generating contact.', 'danger')
return render_template('web/new_contact.html', form=form)
With the limited info you gave, I will try to implement the functionality you want.
Your ContactForm can stay like that:
class ContactForm(FlaskForm):
name = IntegerField('File Name', validators=[Length(min=-1, max=100, message='You cannot have more than 100 characters')])
filename = FileField()
Then you pass the form object to template, from custom flask route, lets call it for the purpose of explaining, contact route:
#app.route('/contact')
def contact():
contact_form = ContactForm()
return render_template('contact.html'
contact_form = contact_form)
And in your template, that I called in this example contact.html, you render your form:
<form action="" method="post" enctype="multipart/form-data">
{{ contact_form.csrf_token }}
{{ contact_form.name }}
{{ contact_form.filename}}
<input type="submit"/>
</form>
In this form, we want with action="" to POST data on the same route, that is, contact route. So in this example we should also validate data in contact() method of flask app. But what is enctype="multipart/form-data" you may be wondering ?
First result searching for what it is gave us results:
The enctype attribute specifies how the form-data should be encoded when submitting it to the server.
Note: The enctype attribute can be used only if method="post".
And for multipart/form-data:
No characters are encoded. This value is required when you are using forms that have a file upload control.
Lastly, we update the flask app contact route like so:
#app.route('/contact')
def contact():
contact_form = ContactForm()
if form.validate_on_submit():
f = contact_form.filename.data
name = contact_form.name.data
f.save(os.path.join("./static/contacts/", name))
redirect(url_for('contact'))
return render_template('contact.html'
contact_form = contact_form)
We've successfully collected data from a form, and saved file in contacts folder in static, with the name from a form. Maybe additionally we could use secure_filename from werkzeug.utils.
from flask.ext.wtf import Form
from flask import send_from_directory
from wtforms import StringField, BooleanField
from wtforms import SelectField
import os
from os import listdir
from os.path import isfile, join
crewPath = ("/myproject/app/static/Crews")
filenamesCrew = [f for f in listdir(crewPath) if isfile(join(crewPath,f)) ]
class userInput(Form):
json_fileCrew = SelectField(u"json_fileCrew", choices=[(f, f) for f in filenamesCrew])
def get_data(self):
json = send_from_directory (crewPath, self.json_fileCrew.data)
return json
#app.route('/CastCrew', methods=['GET', 'POST'])
def castCrew():
form = userInput(request.form["crewYear"])
return render_template('CastCrew.html', title = 'Cast Crew View', form = form)
#app.route("/data", methods=['GET', 'POST']) #the javascript will call this
def data():
form = userInput(request.form["crewYear"])
return form.get_data()
<form class = "form" action="/data" method="post" name="crewYear">
{{ form.hidden_tag() }} <!--CSFR config -->
<p>Please choose a year:<br>
{{ form.json_fileCrew }}<br></p>
<p><input type="submit" value="Submit"></p>
</form>
I am getting a "Bad Request" error when I submit the form. How do I fix this?
The layout of project files:
---app
---views.py
---forms.py
---static
---Crews (about 100 .json files in this folder)
---1981.json
---css
---js
---templates
---base.html
---crew.html
I modified code according answer below but I still get a 404 Not Found Error when I click the button.
The immediate problem is that you're passing the value of request.form["crewYear"] to your form, rather than the entire request.form.
There are a lot of minor problems with your code. You don't need to use send_from_directory, since there's a more specific function to send from the static directory. You should populate the select field in the form init, otherwise it will not reflect any files added after the app starts. You should use app.static_folder rather than hard coding the path. You have two routes that do the same thing.
import os
from flask import current_app
from flask_wtf import Form
from wtforms.field import SelectField
class CrewForm(Form):
filename = SelectField()
def __init__(self, *args, **kwargs):
root = os.path.join(current_app.static_folder, 'Crews')
choices = [(f, f) for f in os.listdir(root) if os.path.isfile(os.path.join(root, f))]
self.filename.kwargs['choices'] = choices
super(CrewForm, self).__init__(*args, **kwargs)
#app.route('/crew', methods=['GET', 'POST'])
def crew():
form = CrewForm()
if form.validate_on_submit():
return current_app.send_static_file('Crews/{}'.format(form.filename.data))
return render_template('crew.html', form=form)
<form method="post">
{{ form.hidden_tag() }}
{{ form.filename }}
<input type="submit" value="Get Data"/>
</form>
Consider reading the Flask tutorial and Flask-WTF docs, as they clearly explain how to use forms. Reading PEP 8 would also be beneficial, as your code style is very inconsistent.
I'm trying to make upload site that takes request and stores the files in 'static' folder in flask. I always get bad request when I try sending "POST" request on in.The only reason that I need it is to test my drag and drop javascript uploader. Can someone point me in the right direction?
import os
from flask import Flask, request, redirect, url_for
from werkzeug import secure_filename
UPLOAD_FOLDER = 'static'
ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'])
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS
#app.route('/', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
file = request.files['xhr2upload'] # [0]
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return redirect(url_for('uploaded_file',
filename=filename))
return '''
<!doctype html>
<title>Upload new File</title>
<h1>Upload new File</h1>
<form action="" method=post enctype=multipart/form-data>
<p><input type=file name=file>
<input type=submit value=Upload>
</form>
'''
if __name__ == '__main__':
app.run()
This is a common problem to have with Flask early on - when you attempt to access request.args, request.form, request.values and request.files they will throw a KeyError if the key does not exist in them - just as an ordinary dictionary would in Python (try {}["test"] in any Python interpreter). Flask adds a bit of sugar to the request dictionaries - its KeyError is actually a subclass of HTTPException that raises a 400 Bad Request if it is not caught. (See this part of the quickstart documentation)
The line that is causing this issue is request.files['xhr2upload']. You do not include any JavaScript on the page and the only <input type="file"> you have on the page has the name of "file", not "xhr2upload". Either change request.files['xhr2upload'] to request.files['file'] or load an ajax uploader into your page and have it post your files using the xhr2upload name.
If you have a form that may have a field and you want to check if a field is defined without raising a KeyError then you can use the .get method:
request.form["not_here"] # KeyError -> 400 Bad Request
request.form.get("might_be_here") # Value if provided, else None
you must add "multiple" to file input tag, like so:
<input type="file" name=file multiple>
and made an for bucle instead "file" on flask backend
files = ...
for file in files
file.save(os.path.join...