getting data from wtforms FormField in a Flask route - python

I have a problem getting data from a form using it in a route
forms.py
class Calculator(Form):
amount = IntegerField('Amount')
weight = IntegerField('Weight')
class Program(Form):
cycles = IntegerField('Cycles')
volume = FormField(Calculator)
app.py
#app.route('/', methods=('GET', 'POST'))
def index():
form = forms.Program()
if form.validate_on_submit():
values = models.Progression(
cycles=form.cycles.data,
amount=form.amount.data,
weight=form.weight.data
)
return render_template('index.html', form=form, values=values)
The data for cycles comes through just fine but I am unsure of the syntax for how to access the encapsulated form within my route. The docs say FormField will return the data dict of the enclosed form but I can't seem to figure out how to grab that and put it in a variable.

I was able to grab the data I needed using
amount=form.volume.amount.data,
weight=form.volume.weight.data
The real issue came from the fact that the form was not validating when I was using FormField. A rookie error I should have checked earlier.
I had to enable CSRF protection by importing it from flask_wtf and using CsrfProtect(app)

The problem is that the form data does not come through as the attributes of the Calculator class. The data is sent as a dictionary from the volume attribute.
Test it out with: print form.volume.data
(I suggest commenting out your values object and just use print statements)
The output should be: {'amount': foo, 'weight': bar}
Thanks for teaching me something! I never knew of FormField.

Related

Flask WTForms - Retrieve form data when submitting to a different route, i.e. a different POST route than the GET route which renders the form

I would like to set up my URLs/endpoints according to REST as closely as possible, while still utilising Flask-WTForms.
I would like my form to render at GET /posts/new, and submit to POST /post.
With Flask-WTForms I can only work out how to get it to GET/POST to the same URL.
My current code looks like this:
#post_bp.route('/posts/new', methods=['GET', 'POST'])
def show_post_form():
create_post_form = CreatePostForm()
if create_post_form.validate_on_submit():
return 'success'
return render_template('create_post_form.html', form=create_post_form)
However I would like to be able to make it look something more like this, but I just can't seem to work it out:
#post_bp.route('/posts/new', methods=['GET'])
def show_post_form():
create_post_form = CreatePostForm()
return render_template('create_post_form.html', form=create_post_form)
this route only shows the form
the form submits a POST request to /post
<form action="{{url_for('shipment.C_shipment')}}" method="POST" novalidate>
the POST /post route handles the submitted form and if there are errors for example, then it redirects back to GET /posts/new:
#post_bp.route('/post', methods=['POST'])
def create_post():
create_post_form = CreatePostForm()
if create_post_form.validate_on_submit():
return "success!"
if len(create_post_form.errors) != 0:
for error in create_shipment_form.errors:
for msg in create_shipment_form.errors[error]:
flash(msg)
return redirect(url_for('shipment.show_create_shipment_form'))
i guess creating a new CreatePostForm() object here doesn't really work..
Any suggestions?
Creating a new CreatePostForm is correct as it parses the submitted form data for you. This allows you to call validate_on_submit() on the form object.
I don't think you're generating the correct URL for the form action in your HTML snippet. The argument to url_for() should be the desired endpoint (see docs) which should be <post_bp>.create_post. This would be similar to your call
return redirect(url_for('shipment.show_create_shipment_form'))
If this does not fix the issue, please provide both frontend and backend error messages you receive when trying to send the data to /post.

Passing objects between routes Flask Python

I need to be able to access an object from one route in other routes. If I only need textual data, using session variables work just fine, but what if I want to be able to access an "unjasonyfyble" object?
I managed to do it with GLOBAL VARIABLES, I just don't know if this is a good practice...
Below is the code snippet where I got it working, but please, I'd like a general case opinion.
The route:
#main.route('/start', methods=['GET', 'POST'])
def start():
global my_form
my_form = Form()
The form class
class Form(FlaskForm):
data = IntegerField('Data', validators=[DataRequired()])
submit = SubmitField('Next')
and then, on any other function, I just access my_form.data.data
I'd really like to do this the right way.

WTForms QuerySelectMultipleField Not sending List

I am working on a Flask Application to do some event scheduling. I am having problems with WTForms QuerySelectMultipleField in my form.
forms.py
class EnterEvent(Form):
...
invitees = QuerySelectMultipleField('Invitees', query_factory=lambda:
models.User.query.order_by(models.User.name).all())
and in my init.py file where I parse the Form POST data. Just to test I tried to return request.form['invitees'] just to see what is passed. Eventually I want to validate the data and add it to my SQLite3 DB.
#app.route('/event', methods=['POST', 'GET'])
def addEvent():
form = EnterEvent()
if request.method == 'POST':
...
invitees = request.form['invitees']
return invitees
the WTForm docs say that the QuerySelectMultipleField should return a list with ORM model instances but when I parse the POST request I am not getting a list. I checked the POST data in my browser and it looks like when I select multiple objects it is sending more than one.
I can't seem to figure this out. Any help will be greatly appreciated!
You would get your ORM model instances if you query your form object directly, rather then the 'raw' form data that's part of the request object
Assuming you're using Flask-WTF with it's little helpers built it, your invitees line should really read invitees = form.invitees.data.

How can I get a parameter passed in the url?

I am making a stock application, where, after a user types in a stock such as MSFT, they are redirected to .../stock?s=MSFT I have successfully made this part, but now I need Python to grab this current, unique URL so that I can parse the quote (in this case, the MSFT) out if it and save it as a new variable.
The easiest way I can think of is just to get the current URL, although I cannot seem to find a way to do this; using self.request... but this returned the error:
NameError: global name 'self' is not defined
The other ideas I came across was to use a form of urllib as it contains a specific .request_qs() method, although this requires me to pass the URL as a parameter, which should be unique every time because of the change in stock.
Here is the Python I currently have:
#app.route('/', methods=['GET', 'POST'])
def home_search():
if request.method == 'POST':
st = request.form['s']
return redirect(url_for('stock')+'?s='+st)
return render_template('stk.html')
#app.route('/stock', methods=['GET', 'POST'])
def stock():
#here I need to save the variable 'name' as the current URL. I can parse it later.
url="http://finance.yahoo.com/d/quotes.csv?s="+name"&f=snl1"
text=urllib2.urlopen(url).read()
return render_template('stock.html')
Many thanks in advance.
It's request (imported from the flask package), not self.request
The Flask docs have, as always, thorough documentation about variables in the URL: http://flask.pocoo.org/docs/quickstart/#variable-rules
#app.route('/user/<username>')
def show_user_profile(username):
# show the user profile for that user
return 'User %s' % username
On that occasion, you should read the whole Quickstart from the Flask docs: http://flask.pocoo.org/docs/quickstart/
You haven't posted any info on where the error occurs :) And I can't see where you're trying to access self.requests.
Looking at other Flask apps, it seems you should just access request instead.
Take a look at these examples: https://github.com/mitsuhiko/flask/tree/master/examples

How do I make my post form handling more flexible

I have written what I hope to be a re-usable Django app, but I have a bit of a conundrum on how to make the post form handling flexible. The simplified version of my view code looks like:
def do_form(request, entity_id, template_name, success_url):
form = MyForm(request.POST or None)
if request.method =='POST':
if form.is_valid():
#do some business logic
return HttpResponseRedirect(finished_url)
return render_to_response(template_name,
{'form': form},
context_instance=RequestContext(request))
I have followed the advice in James Bennets book "Practical Django Projects" and so you can now configure the template and the success url in the url conf, so for example my url conf could look like this:
urlpatterns = patterns('myapp.views',
url(r'^do/(?P<entity_id>\d+)/$',
view = 'do_form',
name = 'do_form_view',
kwargs={'template_name':'form.html',
'success_url':'/finish/'},),
url(r'^finish/$',
view = 'finish',
name = 'finish_view')
)
This is all very well and good but when I have come to use this in my real world application I find myself in a situation that this form sits in the middle of some workflow, and I want the success url to be something like /continue/<workflow_id>/ , and the problem is that you can only have a hardcoded url in the url conf, and the workflow_id will vary every time I hit the do_form code.
Can any one suggest a way to get around this?
You can achieve that by changing the following..
in do_form() in views.py
change the return HttpResponseRedirect to
return HttpResponseRedirect('/continue/%s' %(workflowid))
And in urls.py, you can have
url(r'^continue/(?P<workflowid>\d+)/$',
view = 'continue',
name = 'continue_view')
and for the continue() view in views.py
def continue(request, workflowid=None):
...
This way.. whenever you access the url /continue/ without a number, workflowid will be equal to None. Every other time when you do have a workflowid attached for e.g. like /continue/23/ , then inside your continue() view you can access that id through the variable workflowid.
When you pass a hypothethical "flexible" success_url to a view, that view MUST supply the desired identifier. So if you mismatch the URL and the view, we can't avoid having a "breach of contract" between the two.
Therefore if we are to have flexible URLs, some kind of contract shall have to be enforced, and there will be no loss of generality if we do this through a special syntax for URLs:
'finished_url': '/finish/<workflow_id>/'
Then, of course, the view shall have to instantiate the variable through a string replacement to honor its side of the contract: instead of
return HttpResponseRedirect(finished_url)
you will have
return HttpResponseRedirect(finished_url.replace('<workflow_id>', WorkflowID))
This should keep things reasonably simple.
When reusing code, you will have to keep in mind that <workflow_id> is whatever that app uses to call workflow id, and that's why I use a complicated string such as workflow_id instead of id or maybe $1.
EDIT: I was going to add the code for the next step (intercepting workflow ID in argument of finish), but I see that keithxm23 beat me to the punch :-)
You can do it the same way people have been "overriding" Django's function-based generic views for years: simply wrap the view in another view:
def custom_do_form(request, entity_id, template_name, success_url):
template_name = some_method_to_get_template()
return do_form(request, entity_id, template_name, success_url)

Categories