-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>?
davidism
  • 121,510
  • 29
  • 395
  • 339

1 Answers1

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.