Reputation: 151
I have the following code in Flask/python that involves a variable years that is a list of ints. However, when I use return redirect(url_for(...
, years becomes unicode and if it had been [2015, 2014], it becomes '[','2','0','1','5',' ', etc...
@main.route('/search', methods=('GET','POST'))
def fysearch():
form = SelectYear()
if form.validate_on_submit():
years = form.years.data
return redirect(url_for('.yearresults', years=years))
return render_template('select_year.html', form=form)
When I print the type of each element in years above, it is a normal int as I want it to be. Once it is passed into the redirect url_for, that is when years turns into unicode.
@main.route('/searchresults/<years>')
def yearresults(years):
page = request.args.get('page', 1, type=int)
print type(years)
pagination = Grant.query.filter(Grant.fy.in_(years)).\
paginate(page, per_page=current_app.config['POSTS_PER_PAGE'],
error_out=False)
return render_template('yearresults.html', years=years, entries=pagination.items, pagination=pagination
I know there are ways to revert years back to a list of ints after it has been passed to yearresults(years)
like years=json.loads(years) or replacing the [ and ] and splitting, but I was wondering if there is a different way to fix this issue. I have thought about a converter in the url routing, but I am not sure how that works since I am using flask blueprints.
Thanks in advance!
Upvotes: 0
Views: 1464
Reputation: 18908
The function url_for
returns a URL, which is effectively a string - you can't mix a list of ints into a value that's effectively a string (in better languages/frameworks you will get a type error, more work that way but less prone to conceptual errors like what you are experiencing). You can check simply by returning the result of url_for('.yearresults', years=years)
and see that the value looks something like /yearresults/%5B2014%2C%202015%5D
. Clearly that value in place for year
is a string as that is the default converter (since you did not define one). So the lazy way out is to encode years
with JSON or some sort of string format and decode that on the yearresults
handler, however you had the right idea with using a converter which is from the werkzeug package.
Anyway, putting that together you could do something like this:
from werkzeug.routing import BaseConverter
from werkzeug.routing import ValidationError
class ListOfIntConverter(BaseConverter):
def __init__(self, url_map):
super(ListOfIntConverter, self).__init__(url_map)
def validate(self, value):
if not isinstance(value, list):
return False
for i in value:
if not isinstance(i, int):
return False
return True
def to_python(self, value):
try:
return [int(i) for i in value.split(',')]
except (TypeError, ValueError) as e:
raise ValidationError()
def to_url(self, value):
if not self.validate(value):
# Or your specific exception because this should be from the
# program.
raise ValueError
return ','.join(unicode(v) for v in value)
app.url_map.converters['listofint'] = ListOfIntConverter
@app.route('/years/<listofint:years>')
def years(years):
return '%d, %s' % (len(years), years)
This has the advantage of generating a 404 directly when the input to this route do not match (i.e. someone supplying a string for <years>
), and avoids the %
encoded form of [
, ]
, and from a JSON (or the repr) based encoding (
%5B2014%2C%202015%5D
looks way ugly when compared to 2014,2015
).
Getting the converter into a specific blueprint (and also the unit tests for that) is your exercise.
Upvotes: 1