EDITED. My original question wasn't clear enough. I want to allow a user to pass values into a TextField in wtforms, then the value they entered appears after they add it. This would allow the user to pass multiple values before then hitting a final "Sumbit" button on all the values that were originally entered.
I found this question for doing something with the entered text, which is what I tried.
My Python code:
from flask import Flask, request, render_template, redirect
from wtforms import TextField, Form, SubmitField
def redirect_url(default='index'):
return request.args.get('next') or \
request.referrer or \
url_for(default)
class RegionForm(Form):
field = TextField('Region')
Submit = SubmitField('AddRegion')
fieldList = []
def main():
app = Flask(__name__)
#app.route('/region/', methods=['GET'])
def region():
form = RegionForm(request.form)
return render_template("region.html",
form=form)
#app.route('/add/', methods=['POST'])
def add():
request.form['fieldList'].append(request.form['field'])
return redirect(redirect_url())
app.run(debug=True)
My html code:
<form action="/add/" method="Post">
{% for field in form %}
<tr>
<th>{{ field.label }}</th>
<td>{{ field }}</td>
</tr>
{% endfor %}
</form>
{% for item in form.fieldList %}
{{ item }}
{% endfor %}
But after I enter the text and click the "AddRegion" button, I get the following error: The browser (or proxy) sent a request that this server could not understand. However, if I comment out the line request.form['fieldList'].append(request.form['field']), then the redirect happens, but the text hasn't been added to the hidden list on the form. How do I both add the text to the list and redirect back to the original page, so the user can add more text? It seems like there must be an error with this line only, because the rest works fine.
How can I allow a user to dynamically add text to a field, then have that field display in the browser?
Then once the complete region fields have been added, I want to be able to retrieve that list to process in a separate function later.
Part One
So after looking at your code, I think I have found your problem. Flask is very particular about its app routes.
The app route that you have in your flask is:
#app.route('/add', methods=['POST'])
However, your current action on your form which is:
<form action="/add/" method="Post">
In flask /add and /add/ are actually two different web-routes. Therefore, the #app.route is not being triggered. If you change your code to:
`<form action="/add" method="post">`
You should be good to go from here.
Part Two
I think I may have an additional issue. So within your HTML right now, you actually close your </form> tag before looping through your items in the fieldList.
</form>
{% for item in form.fieldList %}
{{ item }}
{% endfor %}
Try:
{% for item in form.fieldList %}
{{ item }}
{% endfor %}
</form>
What I believe to be happening is that your form inputs are not actually being placed inside of the form so when you try to access them you are getting a KeyError.
I second what Cody Myers said. However there's a simple way to guarantee correct routes even if you later change them: in your form use action="{{ url_for('region') }}" and Flask will automatically substitute the correct route for the given function name.
Related
I will try my best to be as concise as possible.
In my back end, I have a list of dictionaries saved to a variable. Each dictionary represents a post from reddit and includes the score, url, and title.
Respectively, the template will loop through this list and then return the values of each of these keys to the user like so:
<table>
<tr>
{% for x in data[0:5] %}
<td>
{{ x['score'] }}
{{ x['title'] }}
<br>
<a href='/add_to_favorites'> Add To Favorites </a>
</td>
{% endfor %}
</tr>
</table>
As you can see, there's an tag which is linked to a function on my utils.py that is attempting to save the respective dictionary to the database (I have a model that represents the url, title, and score).
I feel as though my template is not representing the dictionary in the correct way, for my link to include the html as when it is pressed I receive a 404 error (though I have this route already defined in views.py - '/add_to_favorites' which calls my 'save_post' function).
def save_post():
data = get_info()
for post in data:
fav= Favorite(title=post.get('title'), url=post.get('url'), score=post.get('score'), user_id=current_user.id)
db.session.add(fav)
db.session.commit()
return redirect(url_for('favorites'))
and:
#app.route('/add_to_favorites')
#login_required
def add_to_favorites():
return save_post()
Am i going about this the wrong way? How can i make sure that the link/button is associated with only the html of the that it is included in?
Just need some guidance into the right direction here, not necessarily the code to fix it. Thank you
In my Flask app I have a form generated with wtforms and jinja templates. If validation passes I want to redirect in a new tab, else I want to stay on the same page and display the errors. However if I set target="_blank" in my form, it opens a new tab without validation passing and shows the errors there. Removing target="_blank" will not open a new tab. Is there a way of achieving this without rewriting the whole validation in js? Thanks!
Code:
from wtforms import Form, TextAreaField, StringField, validators
class SubmitForm(Form):
field1 = StringField(u'field1', [validators.DataRequired()])
field2 = TextAreaField(u'field2', [validators.DataRequired()])
#app.route('/', methods=['POST'])
def sub():
form = SubmitForm(request.form)
if request.method == 'POST' and form.validate():
# great success
return redirect('/my_redirect_uri')
return render_template('index.html', form=form)
#app.route('/')
def layout():
return render_template('index.html', form=SubmitForm())
index.html:
{% from "_formhelpers.html" import render_field %}
<form method=post action="/" target="_blank">
<dl>
{{ render_field(form.field1) }}
{{ render_field(form.field2) }}
</dl>
<p><input type=submit value=Submit>
</form>
_formhelpers.html(not that relevant but for the sake of completness):
{% macro render_field(field) %}
<dt>{{ field.label }}
<dd>{{ field(**kwargs)|safe }}
{% if field.errors %}
<ul class=errors>
{% for error in field.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</dd>
{% endmacro %}
Main issue
You must pass the form object as well as the variables representing your form fields namely field1 and field2.
Details
What is being stated above means that you need to change:
return redirect('/my_redirect_uri')
to
return redirect('/my_redirect_uri', form=form, field1=field1, field2=field2)
This also means that you have to adjustments to your view method:
#app.route('/', methods=['POST'])
def sub():
form = SubmitForm(request.form)
if request.method == 'POST' and form.validate():
# great success
field1 = form.field1.data
field2 = form.field2.data
return redirect('/my_redirect_uri', form=form, field1=field1, field2=field2)
return render_template('index.html', form=form)
Now there are some improvements to your program:
Code less principle:
Replace if request.method == 'POST' and form.validate(): by if form.validate_on_submit(): (you save one instruction)
Use url_for():
For the sake of scalability because in future versions and reedits of your program, any changes made to route names will be automatically available when using url_for() as it generates URLs using the URL map. You may use it in return redirect('/my_redirect_uri') (you may check the link I provided for further information)
Use sessions:
Sessions will make your application "able to remember" the form data being sent. In your example, you can use a session this way:
session['field1'] = form.field1.data
session['field2'] = form.field2.data
This means, for example, the above line:
return redirect('/my_redirect_uri', form=form, field1=field1, field2=field2)
must be changed to:
return redirect('my_redirect_uri', form=form, session.get('field1'), session.get('field2'))
Note that if you want to implement sessions, you will need to set a secret key because they are stored on the client side, so they need to be protected (cryptographically in Flask's philosophy). This mean you must configure your application this way:
app.config['SECRET_KEY'] = 'some secret phrase of your own'
Actually, the problem is not related to the browser tab you opened. The whole issue emanates from the redirect() statement.
More details on why it is good to use sessions? Check the next last section:
A word about redirect:
Your POST request is handled by redirect, consequently you loose access to the form data when the POST request ends.
The redirect statement is simply a response which, when received, the browser issues a GET request. This mechanism is there mainly for the following (and similar) situation:
If you try to refresh the same browser window on which you submitted the form data, the browser will prompt you a pop up window to confirm that you want to send the data (again) because the browser remembers, by design, the last request it performed. Of course, this is nasty for the user of your application. There we need sessions. This is also helpful for the browser tab to which you redirect.
I'm trying to make a uncertainty calculator, where I need a variable number of Fields (this is the final idea). However, while testing for number of 1 field of fields, I came across a issue. Instead of the fields being rendered in the page, there is only some random code in its place:
I tried checking if the issue was related to them having the same name "values" or something, but it seems that is not the issue. I don't know what to try anymore.
forms.py
from flask.ext.wtf import Form
from wtforms import StringField, BooleanField, DecimalField
from wtforms.validators import DataRequired
class Receiver(Form):
expression = StringField('expression', validators=[DataRequired()])
# ve_list = [[StringField('expreson'), DecimalField('expression', places=10)], [StringField('expreson'), DecimalField('expression', places=10)]]
# remember_me = BooleanField('remember_me', default=False)
ve_list = [[DecimalField('value', validators=[DataRequired()]), DecimalField('value', validators=[DataRequired()])]]
The views.py:
from flask import render_template, flash, redirect
from app import app
from .forms import Receiver
#app.route('/login', methods=['GET', 'POST'])
def login():
form = Receiver()
return render_template('request.html',
title='Calculate',
form=form)
request.html:
{% block content %}
<h1>Error propagation calculator</h1>
<form action="" method="post" name="login">
{{ form.hidden_tag() }}
<p>
Please enter the expression:<br>
{{ form.expression }}<br>
</p>
Enter the value and respective error:<br>
{% for ve in form.ve_list %}
{{ ve[0] }} +/- {{ ve[1] }}<br>
{% endfor %}
<p><input type="submit" value="Calculate"></p>
</form>
{% endblock %}
Fields are classes and need to be called in order to run their call method which will render the html to your page.
Example 1:
{{ form.expression() }}
Your field is rendering but it is best to call the field properly.
Edited:
Your list of fields is not going to work because you need to have an instantiated class attached to the form class attribute. When you load up the field like this it is an UnboundField.
I recommend adding fields dynamically in your view. You can see an answer to that problem here.
The fields that are part of the form need to be class variables of the form class. If they are not, then WTForms is not going to find them, so they are never bound to the form.
If you want to add a list of fields, you can do so by setting the attributes on the Form class. Something like this:
class Receiver(Form):
expression = StringField('expression', validators=[DataRequired()])
setattr(Receiver, 've_list0', DecimalField('value', validators=[DataRequired()]))
setattr(Receiver, 've_list1', DecimalField('value', validators=[DataRequired()]))
Then in the template, you can iterate on the form fields to render them, instead of rendering them one by one.
I want to make sure I'm following best practices here. I have a table of data that pulls from the database and in the last column I have links to edit or delete that row. I feel like I've always been told to never modify data on the server with a GET request. How would I handle deleting this data row with anything other than a GET request?
Here's the code for the data table:
<table class="table table-hover notifications">
<thead><tr><th>Search Parameter</th><th>Subreddits</th><th>Actions</th></thead>
{% for notification in notifications %}
<tr>
<td>{{ notification.q }}</td>
<td>{% for s in notification.subreddits %} {{ s.r }}<br> {% endfor %}</td>
<td>Edit | Delete</td>
</tr>
{% endfor %}
</table>
I guess I'm not sure how to approach building the url for Delete. I can build a delete method and pass the id of the element I want to delete (ex: /delete/1), but is that not modifying data with a GET request then?
You can create a form that makes a POST request on submit, connected to a view that deletes the object when request.method is POST (passing the object ID in the URL, as you said).
I am not a Flask expert, but taking this code as example, your view should look something like:
#app.route('/delete/<int:id>', methods=['POST'])
def remove(id):
object = Object.query.get_or_404(id)
delete(object)
return redirect(url_for('index'))
And your form like:
<form method="post" action="{{ url_for('remove', id=object.id) }}">
<button type="submit">Delete</button>
</form>
The form action attribute forces the form to submit its information to the given URL/view.
How do i do specify the many database field when in a wtf form, so i can insert a row in the database correctly. I need something like this in my template
{{ wtf.form_field(gform.GHF(value="{{ project.name }}")) }}
because I'm iterating over one (Projects) to many (Goals)
Project-(has many goals)
-goal-
and my goal form shows up multiple times.
{% for project in P %}
{% for pgoal in project.goals.all() %}
<li>
Goal: {{ pgoal.goal }}<br>
{% if loop.last %}
<form class="form form-horizontal" method="post" role="gform">
{{ gform.hidden_tag() }}
{{ wtf.form_errors(gform) }}
{{ wtf.form_field(gform.goal) }}
Help here? do i need a hiddenfield to know which project?
{{ wtf.form_field(gform.submit) }}<br>
and so on...
Once I have the correct project, I will use it in my view here
u=models.Projects.query.get(correct project?)
p=models.Goals(goal=gform.goal.data,proj=u)
I wouldn't do it with a hidden field. I'd make each form submit a little differently.
You should have something like
<form class="form form-horizontal" method="post" role="gform"
action="{{ url_for('add_goal_to_project', project_id=project.id) }}">
And the route would be
#app.route('.../<int:project_id>', methods=['POST'])
def add_goal_to_project(project_id):
gform = GForm(....)
if gform.validate_on_submit():
project = models.Projects.query.get(project_id)
goal = models.Goals(gform.goal.data, proj=project)
# Do anything else you need to do, such as adding and committing
# the new object
return redirect(...)
return render_template(...)
I'm skipping the details in the form creation, redirect and render_template calls, but this should get the idea across. Each goal form's action points to a route built from the project id.
You could extend this to allow for the editing of goals, and you'd be able to make it a lot better with some nice ajax posts as well.