How do different Form objects communicate in Flask-wtforms? - python

A typical view is something like
#app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
# do stufff
return redirect(url_for('somewhere_else', param=param))
return render_template('login.html', form=form)
What I'm confused about: when the login() view is called, isn't a new LoginForm() instantiated with form = LoginForm()? How does this brand new form ever validate_on_submit()?

It's addressed in the first page of the quick-start guide in the documentation:
Note that you don’t have to pass request.form to Flask-WTF; it will load automatically. And the convenience validate_on_submit will check if it is a POST request and if it is valid.
So when you instantiate the form, it'll automatically load in the existing request if it can.

Look at the source code of flask-wtf (I removed unrelated fragments and added comments):
class Form(SecureForm):
# ...
def __init__(self, formdata=_Auto, obj=None, prefix='', csrf_context=None,
secret_key=None, csrf_enabled=None, *args, **kwargs):
# ...
if formdata is _Auto:
if self.is_submitted():
formdata = request.form # !!! LOOK HERE !!!
if request.files:
formdata = formdata.copy()
formdata.update(request.files)
elif request.json:
formdata = werkzeug.datastructures.MultiDict(request.json)
else:
formdata = None
# ...
So, if you don't pass formdata explicitly to form's constructor and current request "is submitted" (the method is either PUT or POST), it uses the request.form.

Related

How to rewrite this Flask view function to follow the post/redirect/get pattern?

I have a small log browser. It retrieves and displays a list of previously logged records depending on user's input. It does not update anything.
The code is very simple and is working fine. This is a simplified version:
#app.route('/log', methods=['GET', 'POST'])
def log():
form = LogForm()
if form.validate_on_submit():
args = parse(form)
return render_template('log.html', form=form, log=getlog(*args))
return render_template('log.html', form=form)
However it does not follow the post/redirect/get pattern and I want to fix this.
Where should I store the posted data (i.e. the args) between post and get? What is the standard or recommended approach? Should I set a cookie? Should I use flask.session object, create a cache there? Could you please point me in the right direction? Most of the time I'm writing backends...
UPDATE:
I'm posting the resulting code.
#app.route('/log', methods=['POST'])
def log_post():
form = LogForm()
if form.validate_on_submit():
session['logformdata'] = form.data
return redirect(url_for('log'))
# either flash errors here or display them in the template
return render_template('log.html', form=form)
#app.route('/log', methods=['GET'])
def log():
try:
formdata = session.pop('logformdata')
except KeyError:
return render_template('log.html', form=LogForm())
args = parse(formdata)
log = getlog(args)
return render_template('log.html', form=LogForm(data=formdata), log=log)
So, ultimately the post/redirect/get pattern protects against submitting form data more than once. Since your POST here is not actually making any database changes the approach you're using seems fine. Typically in the pattern the POST makes a change to underlying data structure (e.g. UPDATE/INSERT/DELETE), then on the redirect you query the updated data (SELECT) so typically you don't need to "store" anything in between the redirect and get.
With all the being said my approach for this would be to use the Flask session object, which is a cookie that Flask manages for you. You could do something like this:
#app.route('/log', methods=['GET', 'POST'])
def log():
form = LogForm()
if form.validate_on_submit():
args = parse(form)
session['log'] = getlog(*args)
return redirect(url_for('log'))
saved = session.pop('log', None)
return render_template('log.html', form=form, log=saved)
Also, to use session, you must have a secret_key set as part of you application configuration.
Flask Session API
UPDATE 1/9/16
Per ThiefMaster's comment, re-arranged the order of logic here to allow use of WTForms validation methods for invalid form submissions so invalid form submissions are not lost.
The common way to do P/R/G in Flask is this:
#app.route('/log', methods=('GET', 'POST'))
def log():
form = LogForm()
if form.validate_on_submit():
# process the form data
# you can flash() a message here or add something to the session
return redirect(url_for('log'))
# this code is reached when the form was not submitted or failed to validate
# if you add something to the session in case of successful submission, try
# popping it here and pass it to the template
return render_template('log.html', form=form)
By staying on the POSTed page in case the form failed to validate WTForms prefills the fields with the data entered by the user and you can show the errors of each field during form rendering (usually people write some Jinja macros to render a WTForm easily)

Clear valid form after it is submitted

I want to reset the form after it validates. Currently the form will still show the previous data after it is submitted and valid. Basically, I want the form to go back to the original state with all fields clean. What is the correct to do this?
#mod.route('/', methods=['GET', 'POST'])
def home():
form = NewRegistration()
if form.validate_on_submit():
#save in db
flash(gettext(u'Thanks for the registration.'))
return render_template("users/registration.html", form=form)
The issue is that you're always rendering the form with whatever data was passed in, even if that data validated and was handled. In addition, the browser stores the state of the last request, so if you refresh the page at this point the browser will re-submit the form.
After handling a successful form request, redirect to the page to get a fresh state.
#app.route('/register', methods=['GET', 'POST'])
def register():
form = RegistrationForm()
if form.validate_on_submit():
# do stuff with valid form
# then redirect to "end" the form
return redirect(url_for('register'))
# initial get or form didn't validate
return render_template('register.html', form=form)
davidism answer is correct.
But once I had to reload a form with only a few fields that had to be resetted.
So, I did this, maybe it's not the cleanest way but it worked for me:
form = MyForm()
if form.validate_on_submit():
# save all my data...
myvar1 = form.field1.data
myvar2 = form.field2.data
# etc...
# at first GET and at every reload, this is what gets executed:
form.field1.data = "" # this is the field that must be empty at reload
form.field2.data = someobject # this is a field that must be filled with some value that I know
return render_template('mypage.html', form=form)
You can clear a form by passing formdata=None
#mod.route('/', methods=['GET', 'POST'])
def home():
form = NewRegistration()
if form.validate_on_submit():
#save in db
######### Recreate form with no data #######
form = NewRegistration(formdata=None)
flash(gettext(u'Thanks for the registration.'))
return render_template("users/registration.html", form=form)
you can also return new form object using render_template if form does not validates you can also pass message
#mod.route('/', methods=['GET', 'POST'])
def home():
form = NewRegistration()
if form.validate_on_submit():
#save in db
return render_template("user/registration.html", form = NewRegistration())
return render_template("users/registration.html", form=form)

How to pass a "WTF object" in Flask

I am using flask to develop a website and now i encountered a problem.
I am thinking whether I can pass a "WTF form" object in flask.
Like,
#app.route('/')
#app.route('/index')
#login_required
def index():
user = flask.g.user
form = PostForm()
return flask.render_template("index.html",
title="Home",
user=user,
form = form)
This form, an instance of PostForm, actually will be processed by the following code:
#app.route('/note/<int:id>', methods=['POST'])
def note(id):
form = ?(how to get this form?)?
if form.validate_on_submit():
print id
content = form.body.data
currentTime = time.strftime('%Y-%m-%d', time.localtime(time.time()) )
user_id = id
return flask.redirect(flask.url_for('login'))
return flask.redirect( flask.request.args.get('next') or
flask.url_for('index') )
In the template, I set the action to be "/note/1", so it will forward to this address. But the question, how can I get the form created in the function index?
I have tried to use flask.g (Obviously, it does not work because it's another request). And I also tried to use global variable. It failed, either.
Could anyone give me a solution or any advice?
Thank you in advance!
You simply need to construct a new version of PostForm in your note route and use the posted data in request.form:
from flask import request
#app.route('/note/<int:id>', methods=['POST'])
def note(id):
form = PostForm(request.form)
# or, if you are using Flask-WTF
# you can do
# form = PostForm()
# and Flask-WTF will automatically pull from request.form

Django - being able to access page only after HttpResponseRedirect

I have a class based view in which I process the form and redirect the user on successful submission like so:
views.py
def get(self,request):
form = self.form_class()
return render(request, template_name, { 'form' : form })
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
...
return HttpResponseRedirect(reverse('success'))
return render(request, template_name, { 'form' : form })
urls.py
...
url(r'^submit/success', SubmitView.as_view(), name='success'),
...
It is possible to access url directly by typing success/submit. I don't use any authentication on the site and want the user only be able to access the submit/success page after redirection, so that they are not able to access it directly. How do I do it?
If you are using sessions, you can accomplish it like so:
# in the view where form is submitted
if form.is_valid():
request.session['form-submitted'] = True
return HttpResponseRedirect(reverse('success'))
# in the success view
def get(self, request):
if not request.session.get('form-submitted', False):
# handle case where form was not submitted
else:
# render the template
Instead of redirecting, you could POST to the 'success' page.
Then use if request.method == 'POST':
But beware, this is NOT secure, as headers can be spoofed.
Better to just call the success view from within the POST method, I think.
Have you tried something like this:
if form.is_valid():
...
return HttpResponseRedirect(SubmitView.as_view())
Not sure if this works out of the box, but with a few more tricks you might get what you want.
To add to the answer #miki725 posted I would also make sure you change
request.session['form-submitted'] = False
after you have entered the
if not request.session.get('form-submitted', False):
In order to prevent accessing the page directly or using the back and forward on the browser.

Django: construct form without validating fields?

I have a form MyForm which I update using ajax as the user fills it out. I have a view method which updates the form by constructing a MyForm from request.POST and feeding it back.
def update_form(request):
if request.method == 'POST':
dict = {}
dict['form'] = MyForm(request.POST).as_p()
return HttpResponse(json.dumps(dict), mimetype='application/javascript')
return HttpResponseBadRequest()
However, this invokes the cleaning/validation routines, and I don't want to show error messages to the user until they've actually actively clicked "submit".
So the question is: how can I construct a django.forms.Form from existing data without invoking validation?
Validation never invokes until you call form.is_valid().
But as i am guessing, you want your form filled with data user types in, until user clicks submit.
def update_form(request):
if request.method == 'POST':
if not request.POST.get('submit'):
dict = {}
dict['form'] = MyForm(initial = dict(request.POST.items())).as_p()
return HttpResponse(json.dumps(dict), mimetype='application/javascript')
else:
form = MyForm(request.POST)
if form.is_valid():
# Your Final Stuff
pass
return HttpResponseBadRequest()
Happy Coding.

Categories

Resources