-1

I am using:


Consider this, isolated, example of the behavior I am pondering on:

from flask import Flask, url_for, render_template_string

app = Flask(__name__)

@app.route('/hi/', methods=['POST'])
@app.route('/hi/<var>')
def hi(var):
    return ''

@app.route('/')
def index():
    return render_template_string('''
<html>
    <head>
        <title>GET or POST</title>
    </head>
    <body>
        <form action="{{ url_for('path') }}">
            <input type='SUBMIT' value='GET'>
        </form>
        <form action="{{ url_for('path') }}" method='POST'>
            <input type='SUBMIT' value='POST'>
        </form>
    </body>
</html>''')

@app.route('/path/', methods=['GET', 'POST'])
def path():
    return str(url_for('hi', var='Hello', var2='xyz'))

To make my intentions clear, I will briefly describe what is happening and what I am striving to understand:

  1. /hi/ endpoint has an 'optional' parameter (<var>), which is accepted only via GET request. 'Plain' (i.e. without arguments) /hi/ endpoint can only be accessed via POST method.
  2. /path/ endpoint can be accessed via both GET and POST http methods. And it just returns path for hi generated via url_for('hi', var='Hello', var2='xyz')
  3. Now, I would expect /path/ to return the same string, regardless of which method was used to access it (GET or POST). But it is not the case: for GET it returns /hi/Hello?var2=xyz (as I, actually, would expect), but for POST I am getting /hi/?var=Hello&var2=xyz (which strikes me as odd behavior).

Through trials and errors I was able to find out that adding POST to methods allowed for /hi/<var> fixes the issue (/hi/Hello?var2=xyz is returned by /path/ for both GET and POST), i.e.:

@app.route('/hi/', methods=['POST'])
@app.route('/hi/<var>', methods=['GET', 'POST'])
def hi(var):
    ...

I hope, someone would be able to explain the following, for me:

  1. Why is this (/path/ returns different values for POST and GET) happening?
  2. Is it possible to avoid this behavior, without allowing POST on /hi/<var>?

1 Answer 1

1

I have stumbled upon answers thanks to another question =)


Addressing my own questions:

  1. (not 100% sure about that, would be grateful if someone confirmed that I am correct here) url_for has an optional _method parameter, which is defaulted to the method that was used to return current view. So, /path/ is really returning return str(url_for('hi', var='Hello', var2='xyz', _method='GET') when it was accessed via GET request and return str(url_for('hi', var='Hello', var2='xyz', _method='POST') if it was accessed via POST request. That is why allowing POST on both endpoints (/hi/<var> and /hi/) fixes the problem — if POST is allowed only for /hi/, then return str(url_for('hi', var='Hello', var2='xyz', _method='POST') is checking whether var is known only to /hi/ (and it is, obviously, is not known to it). On the other hand, if POST is allowed on both endpoints, then /hi/ and /hi/<var> are checked for the presence of var and /hi/<var> is correctly selected.

  2. Given the previous point, the fix is pretty obvious now: return str(url_for('hi', var='Hello', var2='xyz', _method='GET') should be substituted for return str(url_for('hi', var='Hello', var2='xyz'), in the original snippet.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.