Issue
I'm creating a site using the Flask framework, and am implementing a confirmation page for (mainly administrative) actions; i.e. deleting a user.
My current method (detailed below) works, but feels quite clunky and seems like a huge amount of work for a simple task. Is there a more optimal solution to this?
Currently I have a route to initiate the action:
@admin.route('/user/<int:user_id>/delete', methods=['GET'])
@login_required
@admin_required
def del_user(user_id):
user = User.query.get_or_404(user_id)
desc = "delete"
subject = user.username
action = 'admin.do_del_user'
next = url_for('admin.get_user', user_id=user.id)
return redirect(url_for('main._confirm', desc=desc, subject=subject, action=action, next=next, user_id=user.id))
Which redirects over to the confirm route:
@main.route('/confirm', methods=['GET', 'POST'])
def _confirm():
form = Confirm()
kwargs = {}
for arg in request.args:
if arg != 'action' or arg != 'desc' or arg != 'subject':
kwargs[arg] = request.args[arg]
action = request.args.get('action')
desc = request.args.get('desc')
subject = request.args.get('subject')
if action is None:
abort(404)
if form.validate_on_submit():
return redirect(url_for(action, confirm=form.confirm.data, **kwargs))
return render_template('_confirm.html', form=form, desc=desc, subject=subject)
Which then redirects again to do the actual action after validating the confirmation form:
@admin.route('/user/<int:user_id>/do_delete', methods=['GET'])
@login_required
@admin_required
def do_del_user(user_id):
confirm = request.args.get('confirm')
next = request.args.get('next')
if confirm:
user = User.query.get_or_404(user_id)
db.session.delete(user)
db.session.commit()
return redirect(next)
I hope that makes sense! Just to note, desc and subject are passed for the confirmation template, and the kwargs is just to catch anything url_for() needs in building the urls.
Solution
I think the simplest approach would be to do the confirmation client-side. It's not visually pretty, but a window.confirm('Are you sure?');
would do the same thing.
That said, if you're only looking for a server-side solution, you could create a @confirmation_required
decorator to handle the redirects. Then, you can wrap any view you need confirmation for with it, passing in a function to get the message you want to display.
from functools import wraps
from urllib import urlencode, quote, unquote
from flask import Flask, request, redirect, url_for, render_template
app = Flask(__name__)
def confirmation_required(desc_fn):
def inner(f):
@wraps(f)
def wrapper(*args, **kwargs):
if request.args.get('confirm') != '1':
desc = desc_fn()
return redirect(url_for('confirm',
desc=desc, action_url=quote(request.url)))
return f(*args, **kwargs)
return wrapper
return inner
@app.route('/confirm')
def confirm():
desc = request.args['desc']
action_url = unquote(request.args['action_url'])
return render_template('_confirm.html', desc=desc, action_url=action_url)
def you_sure():
return "Are you sure?"
@app.route('/')
@confirmation_required(you_sure)
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
app.run(debug=True)
_confirm.html:
<html xmlns="http://www.w3.org/1999/html">
<body>
<h1>{{ desc }}</h1>
<form action="{{ action_url }}" method="GET">
<input type="hidden" name="confirm" value="1">
<input type="submit" value="Yes">
</form>
</body>
</html>
Note though that doing this redirecting will only work if the view you are wrapping accepts a GET, and it's not a good idea to allow GETs for any operation that modifies data. (See Why shouldn't data be modified on an HTTP GET request?)
Update: If you really wanted a generic solution that worked with POSTs, I would switch to class-based views and create a mixin that handles the confirmation logic. Something like:
class ConfirmationViewMixin(object):
confirmation_template = '_confirm.html'
def get_confirmation_context(self):
# implement this in your view class
raise NotImplementedError()
def post(self):
if request.args.get('confirm') == '1':
return super(ConfirmationViewMixin, self).post()
return render_template(
self.confirmation_template, **self.get_confirmation_context())
(That's untested, not sure how that would fare. But you get the idea.)
Answered By - keithb
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.