2013-06-21 5 views
9

Iniziare a prendere Python e Flask come un esercizio di apprendimento e provenire da PHP/Symfony2, potrei aggiungere un campo _method nascosto a un modulo per sovrascrivere il metodo POST con un DELETE o METTERE.Cambiare il metodo di richiesta usando il campo nascosto _method in Flask

Sembra che Flask non supporti questo in modo nativo, e ho hackerato con varie idee incluso http://flask.pocoo.org/snippets/38/, che funziona, ma comporta l'override nell'azione modulo, piuttosto che come campo nascosto, che IMO rende l'URL appare sgradevole.

C'è uno snippet nei commenti dell'indirizzo precedente, il che rende _method funzionare da una prospettiva di routing, ma come discusso anche lì, fa sì che la richiesta si blocchi se si tenta di accedere a request.form nelle viste .

Qualcuno ha una soluzione alternativa per questo? In caso contrario, gestirò tutto come POST, ma sarebbe bello riuscire a trovare un modo per farlo funzionare.

Cheers.


EDIT: Ecco il codice per chi vuole dare un'occhiata:

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 

Vista:

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) 
+0

[Request.Form] (http: // flask.pocoo.org/docs/quickstart/#the-request-object) potrebbe esserti utile. – John

+0

Ciao @johnthexiii, grazie per il tuo commento. Sto già utilizzando request.form per controllare se _method è impostato, ma una volta che lo faccio e cambio REQUEST_METHOD nel middleware, non posso accedere a request.form nelle viste (l'app si blocca). Questo è il collegamento con il suggerimento che stavo cercando: http://flask.pocoo.org/snippets/38/#comment-box (secondo commento) – aleayr

+0

Ho messo un po 'di codice per dare una visione migliore di cosa Sto cercando di ottenere. – aleayr

risposta

4

Come già sottolineato, il tuo middleware rende più tardi request.form vuota. Questo perché request.form sta leggendo da un oggetto simile a un file. Citando PEP 333:

wsgi.input - un flusso di input (oggetto simile a file) da cui la richiesta HTTP corpo può essere letto. (Il server o il gateway possono eseguire letture su richiesta, come richiesto dall'applicazione, oppure possono pre-leggere il corpo della richiesta del client e bufferizzarlo in memoria o su disco, o utilizzare qualsiasi altra tecnica per fornire tale flusso di input, secondo a sua preferenza.)

Si noti che questo paragrafo non ci dice se questo "oggetto tipo file" fornirà la possibilità di reimpostare il puntatore all'inizio del file. Infatti, se cerchiamo la seguente applicazione:

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) 

non mostra alcun indici che questo oggetto file ha un metodo seek.

Allora, che cosa si potrebbe fare è leggere tutto in un bytestring chiamato data, e sostituirlo con wsgi.inputBytesIO(data), che non ha un metodo seek si può usare.Questo comporta numerosi svantaggi con lo, il più ovvio è che tutti i dati caricati sono garantiti per essere letti completamente in memoria prima di passarli all'applicazione. Probabilmente ci sono anche alcuni casi limite pericolose che io non mi conosco di, motivo per cui non ho mai rischierebbe di provare quanto segue in una vera e propria applicazione:

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) 
+0

Grazie Markus, sembra essere il caso. Anche Django non sembra supportarlo. TastyPie però lo fa (non usando un _method, ma usando un override dell'intestazione), ma potrebbe essere adattato per i moduli. Allo stato attuale, sto semplicemente usando post dai moduli e facendo la logica PUT/POST/DELETE nella vista Flask. Non elegante come essere in grado di utilizzare il decoratore di routing, ma dovrà farlo. Grazie per l'input. – aleayr

1

È possibile utilizzare MethodView da flask.views e inviarlo ai metodi corretti. Ho creato una semplice app Flask per dimostrarlo.

from flask import Flask, jsonify, request 
from flask.views import MethodView 

app = Flask(__name__) 

class MyView(MethodView): 

    def get(self): 
     return jsonify({'method': 'GET'}) 

    def post(self): 
     method = request.form.get('_method', 'POST') 
     if method == 'POST': 
      return jsonify({'method':method}) 
     else: 
      if hasattr(self, method.lower()):    
       return getattr(self, method.lower())() 
      else: 
       return jsonify({'method': 'UNKNOWN'}) 

    def put(self): 
     return jsonify({'method': 'PUT'}) 

    def delete(self): 
     return jsonify({'method': 'DELETE'}) 

    def create(self): 
     # NOT A HTTP VERB 
     return jsonify({'method': 'CREATE'}) 

app.add_url_rule('/', view_func=MyView.as_view('myview')) 

if __name__ == "__main__": 
    app.run(debug=True) 
Problemi correlati