Bad request error when chosing file on server with form - python

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.

Related

How to prevent uploaded files from having the same name Flask Python

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)))

store file name in column when upload file using FileField wtforms

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.

Flask-WTF: variable choices for SelectField in FieldList of FormField

I have a setup with a FieldList(FormField(SubForm)) where SubForm includes a SelectField whose choices are determined at runtime (in the view function). Currently I have the following setup, which basically derives a new class for SubForm with the choices each time it is called:
from flask import Flask, render_template_string, url_for
from wtforms import SelectField, StringField, FieldList, FormField
from flask_wtf import FlaskForm as Form
app = Flask(__name__)
app.secret_key = 'secret_key'
class BaseForm(Form):
#classmethod
def append_field(cls, name, field):
setattr(cls, name, field)
return cls
class SubForm(BaseForm):
title = StringField('SubForm')
class RootForm(BaseForm):
entries = FieldList(FormField(SubForm))
#app.route('/test/', methods=['GET', 'POST'])
def form_viewer():
form = RootForm(title='Title')
subformclass = SubForm
subformclass = subformclass.append_field('options',
SelectField('Options', choices=[('%i' %i,'option%i' %i) for i in range(1,5)]))
if form.validate_on_submit():
while form.entries:
subform = form.entries.pop_entry()
print(subform.options.data)
else:
print(form.errors)
for entry in range(1,3):
subform = subformclass()
subform.title = 'SubTitle%i' %entry
form.entries.append_entry(subform)
html_template = '''<html>
<form action="{{ url_for('form_viewer') }}" method=post>
{{ form.hidden_tag() }}
{{ form.entries() }}
<input type=submit value=Submit>
</form>
</html>'''
return render_template_string(html_template, form=form)
My problem is now, that if the app restarts (because things happen) between a GET and POST request, then it raises AttributeError: 'UnboundField' object has no attribute 'data' while trying to access the submitted form data.
How can this be fixed? Or is there maybe another, better way to have a variable number of SelectFields with variable choices?
edit:
I think the problem was, that after FieldList was created, I changed SubForm. So now I am creating FieldList in a similar way
like SelectField:
formclass = RootForm.append_field('entries', FieldList(FormField(subformclass)))
But I am still wondering whether this is the way I am supposed to do it? It feels like, there might be a more elegant solution.

File not uploading with Flask-wtforms in cookiecutter-flask app

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)

Cannot add comment to SQL db in python Flask app

I am new to programming and have setup a small website with a comments section on pythonanywhere.com, relaying heavily on their tutorial. But when I post a comment in the form, the comment is not added to the database and for some reason the program redirects me to the index page (the intention is to redirect to stay on the same page)
Any suggestions as to what I might be doing wrong would be greatly appreciated!
The pyhthon code:
import random
from flask import Flask, request, session, redirect, url_for, render_template, flash
from flask.ext.sqlalchemy import SQLAlchemy
from werkzeug.routing import RequestRedirect
app = Flask(__name__)
app.config["DEBUG"] = True
SQLALCHEMY_DATABASE_URI = "mysql+mysqlconnector://{username}:{password}#{hostname}/{databasename}".format(
username="username",
password="password",
hostname="hostname",
databasename="majaokholm$majaokholm",
)
app.config["SQLALCHEMY_DATABASE_URI"] = SQLALCHEMY_DATABASE_URI
app.config["SQLALCHEMY_POOL_RECYCLE"] = 299
db = SQLAlchemy(app)
class Comment(db.Model):
__tablename__ = "comments"
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.String(4096))
#app.route("/")
def index():
return render_template("index_page.html")
#app.route('/post', methods=["GET", "POST"])
def post():
if request.method == "GET":
return render_template("post_page.html", comments=Comment.query.all())
comment = Comment(content=request.form["contents"])
db.session.add(comment)
db.session.commit()
return redirect(url_for('post'))
and the form from the HTML template:
<form action="." method="POST">
<textarea class="form-control" name="contents" placeholder="Enter a
comment"></textarea>
<input type="submit" value="Post comment">
</form>
Thanks a lot in advance!
Currently, the action="." in the form actually points to the root of the current directory, which for /post happens to be just / and thus points to the index.
It's always better to use action="{{ url_for('your_target_view') }}" instead.
get rid of action=".", you can use action=""

Categories