Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Changing request method using hidden field _method in Flask

Started picking up Python and Flask as a learning exercise, and coming from PHP/Symfony2, I could add a hidden _method field to a form to override the POST method with either a DELETE or PUT.

It seems Flask doesn't support this natively, and I've been hacking around with various ideas including http://flask.pocoo.org/snippets/38/, which works, but involves putting the override in the form action, rather than as a hidden field, which IMO makes the URL look unsightly.

There is a snippet in the comments of the above address, which makes _method work from a routing perspective, but as discussed there as well, does cause the request to hang if you then try to access request.form in the views.

Does anyone have a workaround for this? If not, I'll just handle everything as POST, but would be nice to be able to find a way to get it to work.

Cheers.


EDIT: Here's the code for anyone who wants to take a look:

Template:

<form action="{{ url_for('login') }}" method="POST">
    <input type="hidden" name="_method" value="PUT">
    <input class="span12" name="email" type="text" placeholder="E-mail address" value="{{ email }}">
    <input class="span12" name="password" type="password" placeholder="Your password">
    <a href="{{ url_for('reset_password') }}" class="forgot">Forgot password?</a>
    <div class="remember">
        <input id="remember-me" type="checkbox">
        <label for="remember-me">Remember me</label>
    </div>
    <input class="btn-glow primary login" type="submit" name="submit" value="Log in">
</form>

app/__init__.py

from flask import Flask
from werkzeug.wrappers import Request

class MethodRewriteMiddleware(object):
    def __init__(self, app, input_name='_method'):
        self.app = app
        self.input_name = input_name

    def __call__(self, environ, start_response):
        request = Request(environ)

        if self.input_name in request.form:
            method = request.form[self.input_name].upper()

            if method in ['GET', 'POST', 'PUT', 'DELETE']:
                environ['REQUEST_METHOD'] = method

        return self.app(environ, start_response)

app = Flask(__name__)
app.wsgi_app = MethodRewriteMiddleware(app.wsgi_app)
from app import views

View:

from flask import render_template
@app.route('/user/login', methods=['GET','POST','PUT'])
def login():
    emailvalue = '[email protected]'
    if request.method == 'PUT':
        emailvalue = request.form['email']
    return render_template('login.html', email=emailvalue)
like image 280
aleayr Avatar asked Jun 21 '13 11:06

aleayr


People also ask

What is request endpoint in Flask?

Request. endpoint. The endpoint that matched the request. This in combination with view_args can be used to reconstruct the same or a modified URL. If an exception happened when matching, this will be None .

How do you make middleware in Flask?

Middlewares are created in Flask by creating a decorator; a function can have multiple middlewares, and the order matters a lot. You need to add a secret key to your application; this is what you should pass to JWT. Add the following to your app.py file below the app declaration.


1 Answers

As you already pointed out, your middleware makes the later request.form empty. This is because request.form is reading from a file-like object. Quoting PEP 333:

wsgi.input -- An input stream (file-like object) from which the HTTP request body can be read. (The server or gateway may perform reads on-demand as requested by the application, or it may pre- read the client's request body and buffer it in-memory or on disk, or use any other technique for providing such an input stream, according to its preference.)

Note that this paragraph doesn't tell us if this "file-like object" will provide any possibility to reset the pointer to the beginning of the file. In fact, if we try the following application:

from werkzeug.serving import run_simple

def app(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    yield str(dir(environ['wsgi.input']))

run_simple('localhost', 5000, app)

It doesn't show any indices that this file object has a seek method.

So, what you could do is read everything into a bytestring called data, and replace wsgi.input with BytesIO(data), which does have a seek method one can use. Doing this brings several disadvantages with it, the most obvious being that all uploaded data is guaranteed to get completely read into memory before passing it to the application. Probably there are also some dangerous edge cases that i don't know myself of, which is why i never would risk trying the following in a real application:

from werkzeug.formparser import parse_form_data
from werkzeug.wsgi import get_input_stream
from io import BytesIO

class MethodMiddleware(object):
    """Don't actually do this. The disadvantages are not worth it."""
    def __init__(self, app):
        self.app = app

    def __call__(self, environ, start_response):
        if environ['REQUEST_METHOD'].upper() == 'POST':
            environ['wsgi.input'] = stream = \
                BytesIO(get_input_stream(environ).read())
            formdata = parse_form_data(environ)[1]
            stream.seek(0)

            method = formdata.get('_method', '').upper()
            if method in ('GET', 'POST', 'PUT', 'DELETE'):
                environ['REQUEST_METHOD'] = method

        return self.app(environ, start_response)
like image 187
Markus Unterwaditzer Avatar answered Nov 04 '22 13:11

Markus Unterwaditzer