Flask - url_for automatically escapes '=' to '%3D' - python-3.x

So.. I'm having some issues with Flask's url_for . The code still works.. but when users navigate to a link that was generated by url_for the link looks bad in the address bar.
Namely, I have a decorated view function as follows:
#app.route("/")
#app.route("/page=<int:number")
def index(number=0):
return "Index Page: {}".format(number)
This all works fine except when I try to generate a url for that route. Calling:
url_for("index", number=10)
Yields: domain.tld:80/page%3D10
Is there any way to circumvent this issue? I'd like for '=' to be returned instead of '%3D' when it's built into the route itself.
I only noticed it was doing this when I was testing it in an assert and discovered that the routes were ending up different from what I expected them to be.
At the moment, I have my test case circumvent the issue by using urllib.parse.unquote to fix the url for testing purposes. I could probably just do that for all urls since I won't have any user input to worry about those causing problems.. but it's there for a reason so.... :P

One option you have is to not build the parameter in to the route itself, but use query parameters instead:
from flask import Flask, render_template, request, url_for
app = Flask(__name__)
#app.route("/")
def index():
page = request.args.get('page', 0, type=int)
print(url_for("index", page=10)) # yields /?page=10
return "Index Page: {}".format(page)
app.run(debug=True)
My making use of query parameters for the route, you avoid the issue of Flask encoding the = sign in the route definition.

Related

Flask List Routes on Init is really Flaky

I'm trying to get all the routes for Flask when it's initially loaded but it seems to be super flaky. It'll give me all the routes half the time and the other half the time it'll give me this:
['/static/path:filename HEAD,GET,OPTIONS /static/path:filename']
I'm using this codeblock in the before_first_request and in the init constructor the Flask app. Any ideas how I can make this consistent? I want to do this as soon as the app is fully loaded automatically.
output = []
for rule in self.url_map.iter_rules():
try:
methods = ','.join(rule.methods)
line = urllib.parse.unquote("{:50s} {:20s} {}".format(str(rule), methods, rule))
output.append(line)
except Exception as e:
print("error with rule: " + str(rule))
dct = {"endpoints": output}
you can iterate over the flask dictionary using this app property view_functions
something like this:
app = Flask(__name__)
... #your code here
for str_function, function_object in app.view_functions:
print(f"the function url is {str_function}")
something I've read from the Flask doc documentation on flask

Stop Scrapy from converting "—" to "—" from URL

One of the websites that I am working on, frequently uses — in URL and Scrapy converts it in to — before processing; I am trying to modify it back to — through adding few lines in default Download Middleware and it does print everything fine however Scrapy converts it back again, which eventually results as 404.
'DOWNLOADER_MIDDLEWARES' : {
'something.middlewares.MyDownloaderMiddleware': 540
}
middlewares.py
from urllib.parse import unquote
from html import escape, unescape
class MyDownloaderMiddleware(object):
def process_request(self, request, spider):
new_url = unescape(unquote(request.url))
print (new_url)
request = request.replace(url=new_url)
return None
I tried return request instead of return none in the middleware, however still it doesn't seem to work either.
Solution:
I have placed couple of .replace("—", "—") in spider code, it's working now though my first approach was to do it via middleware, which should have been a cleaner approach.

How do I create an alert based on the last page visited?

I'm trying to mimic the 'dashboard' of https://finance.cs50.net/ (once you've logged in), which displays a 'bought' alert if you are redirected there from /buy, but no alert if you end up there otherwise.
I'm using Flask, jinja2, python, and HTML. The address provided is ("/"), which works fine with return redirect, but not with return render_template. I tried passing in a flag with return redirect, but my computer didn't like that. You always know the UserId, but navigation is not being tracked (i.e. no cookies). Simple answers that I can understand are preferred to clever ones that I cannot.
# from application.py, at the end of the 'buy' function
# flag says local to 'buy' so this does not help me
flag = True
return redirect("/")
## also tried, but computer got angry
return render_template("/")
I don't really know how the alert looks like but it sounds like you might be looking for flash. The documentation is available here
Also using the example provided in the documentation, the handler for '/buy' should look like buy() below:
from flask import Flask, flash, redirect, render_template, \
request, url_for
app = Flask(__name__)
app.secret_key = 'some_secret'
#app.route('/')
def index():
return render_template('index.html')
#app.route('/buy', methods=['GET', 'POST'])
def buy():
if False:
pass
else:
flash('You were redirected from /buy')
return redirect(url_for('index'))
return render_template('buy.html')

Flask_Sqlalchemy with multithreaded Apache. Sessions out of sync with database

Background: Apache server using mod_wsgi to serve a Flask app using Flask_Sqlalchemy connecting to MySQL. This is a full stack application so it is nearly impossible to create a minimal example but I have tried.
My problem is that when I make some change that should modify the database subsequent requests don't always seem to reflect that change. For example if I create an object, then try to edit that same object, the edit will sometimes fail.
Most of the time if I create an object then go to the page listing all the objects, it will not show up on the list. Sometimes it will show up until I refresh, when it will disappear, and with another refresh it shows up again.
The same happens with edits. Example code:
bp = Blueprint('api_region', __name__, url_prefix='/app/region')
#bp.route('/rename/<int:region_id>/<string:name>', methods=['POST'])
def change_name(region_id, name):
region = Region.query.get(region_id)
try:
region.name = name
except AttributeError:
abort(404)
db.session.add(region)
db.session.commit()
return "Success"
#bp.route('/name/<int:region_id>/', methods=['GET'])
def get_name(region_id):
region = Region.query.get(region_id)
try:
name = region.name
except AttributeError:
abort(404)
return name
After object is created send a POST
curl -X POST https://example.com/app/region/rename/5/Europe
Then several GETs
curl -X GET https://example.com/app/region/name/5/
Sometimes, the GET will return the correct info, but every now and then it will return whatever it was before. Further example output https://pastebin.com/s8mqRHSR it happens at varying frequency but about one in 25 will fail, and it isn't always the "last" value either, when testing it seems to get 'stuck' at a certain value no matter how many times I change it up.
I am using the "dynamically bound" example of Flask_Sqlalchemy
db = SQLAlchemy()
def create_app():
app = Flask(__name__)
db.init_app(app)
... snip ...
return app
Which creates a scoped_session accessible in db.session.
Apache config is long and complicated but includes the line
WSGIDaemonProcess pixel processes=5 threads=5 display-name='%{GROUP}'
I can post more information if required.
For reference if anyone finds this thread with the same issue, I fixed my problem.
My Flask App factory function had the line app.app_context().push() leftover from the early days when it was based off a Flask tutorial. Unfortunately snipped out of the example code otherwise it might have been spotted by someone. During a restructuring of the project this line was left out and the problem fixed itself. Not sure why or how this line would cause this issue, and only for some but not all requests.

How to get resource path in flask-RESTPlus?

I am fairly new at working with flask and flask-RESTPlus. I have the following and it is not clear how can I determine which path was used in the get request?
ns = api.namespace('sample', description='get stuff')
#ns.route(
'/resource-settings/<string:address>',
'/unit-settings/<string:address>',
'/resource-proposals/<string:address>',
'/unit-proposals/<string:address>')
#ns.param('address', 'The address to decode')
class Decode(Resource):
#ns.doc(id='Get the decoded result of a block address')
def get(self, address):
# How do I know what get path was called?
pass
A better solution would be to use the request context. To get the full path, you can do:
from flask import request
def get(self, address):
# How do I know what get path was called?
print(request.full_path)
Through lot's of digging I found that url_for in flask import.
Still feels a bit wonky but I can create a fully qualified link with:
result = api.base_url + url_for('resource-settings', address=id)
So this works and I get the desired results.

Resources