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)))
Related
I am trying to make an application that allows users to upload images from their computer. This is as part of my CS50x final project, so I'm working in the CS50 IDE. Here is the code for my application:
application.py
import os
from flask import *
app = Flask(__name__)
UPLOAD_FOLDER = 'uploads'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
#app.route('/')
def myindex():
return render_template("file_upload_form.html")
#app.route('/upload', methods = ['POST'])
def upload():
if request.method == 'POST':
file = request.files['file']
file.save(os.path.join(app.config['UPLOAD_FOLDER'], "test.jpg"))
return redirect("/")
if __name__ == "__main__":
app.run(debug=True)
file_upload_form.html
<html>
<head>
<title>upload</title>
</head>
<body>
<form action = "/upload" method = "post" enctype="multipart/form-data">
<input type="file" name="file" />
<input type = "submit" value="Upload">
</form>
</body>
</html>
For the vast majority of files, when I submit the form, I get a 500 Internal Server Error with no traceback or explanation. Curiously though, some files it seems to work with. I have found two images that work: both JPEGs and both relatively small. All other images I have tried have caused the error.
I cannot work out what is causing the Server Error, or why some images work and others don't. Can anyone see any reason why it wouldn't work for other images?
Thanks
I tested out your code and it worked fine on my side, but there is a couple of additions i would make, just to make it more robust.
You are calling all your uploads test.jpg and there is no mechanism in place to ensure that the file that gets uploaded is a jpg file. So technically, i could upload a pdf and it would be renamed in to a test.jpg.
A better method is to just give the file - the same name it had, including the extension. If you want to re-name the file, rather strip the extension first and add it to the new filename like so:
ext = file.filename.rsplit('.', 1)[1].lower()
newFilename = 'newname.{}'.format(ext)
Also there is no MAX_CONTENT_LENGTH - I would add that as well.
So i re-wrote your python code to look like this:
import os
from flask import *
from werkzeug.utils import secure_filename
app = Flask(__name__)
UPLOAD_FOLDER = './uploads'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['ALLOWED_IMAGES'] = set(['png', 'jpg', 'jpeg'])
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
def allowed_image(filename):
return '.' in filename and filename.rsplit('.', 1)[1].lower() in app.config['ALLOWED_IMAGES']
#app.route('/')
def myindex():
return render_template("index.html")
#app.route('/upload', methods = ['POST'])
def upload():
if request.method == 'POST':
file = request.files['file']
if file and allowed_image(file.filename):
file.save(os.path.join(app.config['UPLOAD_FOLDER'], secure_filename(file.filename)))
return redirect(request.url)
if __name__ == "__main__":
app.run(debug=True)
You are sending the post to: <form action = "/success"...>
Where is your /success handler?
UPDATE:
Try:
if request.method == 'POST':
print(request.files)
print(request.form)
print(request.POST)
You need to check the logs to see what there error is.
I prepared a script in which user can upload a file as an input file (text.dat) and then after importing it into a function (compute), another file as an output (server.log) can be downloaded by user.
But the script does not work properly - it requests for the input file but can not be downloaded at the end.
import os
from flask import Flask, request, render_template, url_for, redirect, send_file,
from compute import compute
app = Flask(__name__)
#app.route("/")
def fileFrontPage():
return render_template('fileform.html')
#app.route("/handleUpload", methods=['GET','POST'])
def handleFileUpload():
if 'photo' in request.files:
photo = request.files['photo']
if photo.filename != '':
photo.save(os.path.join('/home/Documents/web', photo.filename))
return redirect(url_for('fileFrontPage'))
#app.route('/download', methods = ['GET', 'POST'])
def tes():
with open("/home/Documents/web/download/server.log", "w") as fd, open('/home/Documents/web/text.dat', 'r') as out:
path = "server.log"
for i in out:
s = compute(float(i))
fd.write("{} \n".format(s))
return send_file('/home/Documents/web/download/server.log',as_attachment=True, attachment_filename='server.log')
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
My html file as follows:
<html>
<head>
<title>Simple file upload using Python Flask</title>
</head>
<body>
<form action="/handleUpload" method="post" enctype="multipart/form-data">
Choose the file: <input type="file" name="photo"/><BR>
<input type="submit" value="Upload"/>
</form>
</body>
</html>
Change the route of tes() to something that doesn't conflict with fileFrontPage(), e.g. #app.route('/download')
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)
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...