werkzeug.routing.BuildError coming for Flask app - python

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):
[..]

Related

Flask_wtf - validate_on_submit() error on page load

Flask_wtf's validate_on_submit() is never True on visiting the page for the first time, so it always flashes the else part's (code below) Error message which is always an empty dict.
But the form validation and submission are working properly - the success flash message can be seen on a valid post. And the Error flash doesn't disappear after a valid submission.
Reproducible code:
# necessary import stmts & other stuff
class MyForm(FlaskForm):
sub = StringField(validators=[DataRequired("Choose the title")])
body = TextAreaField(validators=[DataRequired(),Length(min=20)])
subm = SubmitField('Submit')
app.config['SECRET_KEY'] = 'my key'
#app.route('/', methods=['GET','POST'])
def index():
fo = MyForm()
flash('Submitted:'+str(fo.is_submitted())) # False on first time visit
#flash('After Validate:'+str(fo.validate()))
if fo.validate_on_submit():
ex = mytable(bodys = fo.body.data, subs = fo.sub.data)
# DB session add & commit stmt here
flash('Submitted','success')
return redirect(url_for('index'))
else:
flash('After val Errors:'+str(fo.errors))
return render_template('index.html',form=fo)
If I un-comment fo.validate()...it flashes csrf_token': ['The CSRF token is missing.'] and the other data required error msgs but as shown below the html template has form.hidden_tag(). Also used {{ form.csrf_token }} instead of hidden_tag()...no success.
<form method="POST" action="">
{{ form.hidden_tag() }}
{{ form.sub }}
{{ form.body }}
{{ form.subm }}
</form>
Please help to get rid of the validation error on page load, Thank you
So on initial get you don't need to validate your form because there's no data yet, only do it when it's actually posted, like so:
if request.method == 'POST':
if fo.validate_on_submit():
# DB session add & commit stmt here
flash('Submitted', 'success')
return redirect(url_for('index'))
else:
flash('After val Errors:' + str(fo.errors))

Flask form appends form data to url on submit

I have a HTML form
<form>
<input type="text" name="keyword" id="keyword" />
<input type="submit">Submit</input>
on submits I want to pass form data to flask app
requests.py
def search_news(keyword):
search_news_url = 'https://newsapi.org/v2/everything?q={}&language=en&apiKey={}'.format(keyword,api_key)
with urllib.request.urlopen(search_news_url) as url:
search_news_data = url.read()
search_news_response = json.loads(search_news_data)
search_news_results = None
if search_news_response['articles']:
search_news_list = search_news_response['articles']
search_news_results = process_search_results(search_news_list)
return search_news_results
def process_search_results(search_news_list):
news_results = []
for search_results_item in search_news_list:
author = search_results_item.get('author')
title = search_results_item.get('title')
description = search_results_item.get('description')
url = search_results_item.get('url')
urlToImage = search_results_item.get('urlToImage')
publishedAt = search_results_item.get('publishedAt')
content = search_results_item.get('content')
totalResults = search_results_item.get('totalResults')
if content:
news_results_object = Everything(author,title,description,url,urlToImage,publishedAt,content,totalResults)
news_results.append(news_results_object)
return news_results
views.py
from ..requests import get_everything,search_news
....
#main.route('/')
def index():
everything = get_everything()
title = 'News Highlight'
searching_news = request.args.get('keyword')
if searching_news:
redirect(url_for('.search',keyword = searching_news))
return render_template('index.html',title = title,everything = everything)
....
#main.route('/search/<keyword>')
def search(keyword):
keyword_list = keyword.split(" ")
keyword_format = '%20'.join(keyword_list)
searched_news = search_news(keyword_format)
title = f'Search results for {keyword} '
return render_template('search.html',searched_news = searched_news)
on form submits it appends form data to url like this:
http://127.0.0.1:5000/?keyword=game+of+thrones
I've tried using post methods but i get a server does not support method error. Can anyone help please.
but when I append link like this:
http://127.0.0.1:5000/search/game%20%of%thrones
the app searches and displays results
By default, form data is submitted via the URL's query string if you don't tell it to behave differently (the method defaults to GET).
Post
If you want to send the data to flask using a POST request, this is the way to go:
Make sure to tell your HTML form to use method="POST" and tell it where to send it's data via the action attribute:
<form method="post" action="/search">
<input type="text" name="keyword" id="keyword" />
<input type="submit">Submit</input>
</form>
Tell your server-side view to accept POST requests and fetch the sent data from request.form. In views.py use
#main.route('/search/' methods=['POST'])
def search():
keyword = request.form['keyword']
...
Get
If you want to use a GET request and pass the data via query string set your form's method to get
<form method="get" action="/search">
<input type="text" name="keyword" id="keyword" />
<input type="submit">Submit</input>
</form>
On submit, your browser will append the values entered in the input field to the URL as a query string: ?keyword=<whatever-you-entered>.
Tell your server-side view to accept GET requests and fetch query string data via request.args.get(). In views.py use
#main.route('/search/' methods=['GET'])
def search():
keyword = request.args.get('keyword')
...
MDN has got a nice article with more details around sending and retreiving form data with HTML and Python, this might be worth a read, too.

MultiFileField Doesn't Return Files, returns _str_

I'm trying to upload multiple images using WTForms in Flask using "MultiFileField, however, it returns a string instead of the file object.
So I tried using the below:
request.files.getlist(form.upload_field.data)
But it returns an empty list, so anyway I can handle this to save the photos to a directory
There is documentation of file uploads with Flask here, and you are going about it the right way through accessing the request.files object. I've come across two ways to get an empty list back from there:
1. enctype form html attribute not set
Here's an example template that renders the MultipleFileField():
template = """
<form action="" method="POST" enctype="multipart/form-data">
{{ form.upload_field() }}
{{ form.submit() }}
</form>
"""
If I remove the enctype=... part, the list of files returns empty, where it otherwise would have values. A page on the internet says:
This value is required when you are using forms that have a file upload control
2. Passing the wrong Key to request.files.getlist()
request.files is a werkzeug.MultiDict, which is a mapping of keys to values, designed to handle having multiple values for the same key.
Using the same form template as above, inspecting the keys of request.files (print(list(request.files.keys()))) upon POST reveals ['upload_field'].
werkzeug.MultiDict.getlist has a single required parameter:
key - The key to be looked up.
So the only key in the MultiDict instance at this point is the string 'upload_field', if we want to get anything back from the getlist method, this needs to be the key that we pass to getlist. In your example code, you pass the value of the form.upload_field.data attribute (which in my tests is None). Change that to 'upload_field' and you should be away.
Here's a working minimal example that will print the result of calling request.files.getlist() upon form submit. Run the script, visit http://127.0.0.1:5000 in your browser, upload a couple of files and watch the terminal output.
from flask import Flask, render_template_string, request
from wtforms import Form, MultipleFileField, SubmitField
app = Flask(__name__)
class MyForm(Form):
upload_field = MultipleFileField()
submit = SubmitField()
template = """
<form action="" method="POST" enctype="multipart/form-data">
{{ form.upload_field() }}
{{ form.submit() }}
</form>
"""
#app.route("/", methods=["GET", "POST"])
def route():
form = MyForm()
if request.method == "POST":
print(request.files.getlist("upload_field"))
return render_template_string(template, form=form)
if __name__ == "__main__":
app.run(debug=True)

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)

What does this error mean: ValueError: renderer was passed non-dictionary as value

Right now I have a pyramid (python) app with a home page that is reached via this route in the init.py file:
config.add_route('home_page', '/')
in my views.py file I have:
#view_config(route_name='home_page', renderer='templates/edit.pt')
def home_page(request):
if 'form.submitted' in request.params:
name= request.params['name']
body = request.params['body']
page=Page(name,body)
DBSession.add(page)
return HTTPFound(Location=request.route_url('view_page',pagename=name))
and in my edit.pt template I have
<form action="${save_url}" method="post">
<textarea name="name" tal:content="page.data" rows="10"
cols="60"/><br/>
<textarea name="body" tal:content="page.name" rows="10"
cols="60"/><br/>
<input type="submit" name=form.submitted value="Save"/>
</form>
So basically the goal is to have the homepage show this edit.pt template which contains a form for submitting two pieces of information, a page name and page body. Upon submitting the form, the return HTTPFound function should redirect to the view_page created which shows the page name page body on a new permanent url.
I am not sure what I should add after the if statement in my home_page view_config. If the form hasn't been submitted I don't want anything to happen, it should just continue to show that edit.pt template. Right now I am getting an error when I try to visit the home page: ValueError: renderer was passed non-dictionary as value.
It looks like you are missing a condition
#view_config(route_name='home_page', renderer='templates/edit.pt')
def home_page(request):
if 'form.submitted' in request.params:
name= request.params['name']
body = request.params['body']
page=Page(name,body)
DBSession.add(page)
return HTTPFound(Location=request.route_url('view_page',pagename=name))
# form was not submitted here, need to return context
# ValueError: renderer was passed non-dictionary as value < beacuase you were
# not returning context for this case before
return {} # template contenxt here

Categories