I am learning how to use sqlalchemy and I am developing small app. I am having some issues trying to update data stored in the db using the ORM. I'm not sure what I am missing. The form edit_product.html gets populated with the right data however the return redirect(url_for statement is fired without updating the data. I am using merge(), I tried using add() as suggested by some tutorials, however if I do this I get an error saying that the record already exists.
This is my edit_product function in the views.py:
from database import db_session
#app.route('/product/edit/<int:product_id>', methods=['GET', 'POST'])
def edit_product(product_id):
product = Product.query.filter(Product.id == product_id).first()
form = NewOtrosForm(obj=product)
if request.method == 'POST':
print request.form
if form.validate():
form.populate_obj(product)
db_session.commit()
return redirect(url_for('product'))
else:
return render_template('edit_product.html', form=form)
This is what I have in my edit form in the jinja2 template:
{% from "_formhelpers.html" import render_field %}
<form method=post action="">
<dl>
{{ render_field(form.name) }}
{{ render_field(form.price) }}
{{ render_field(form.description) }}
{{ render_field(form.provider) }}
{{ render_field(form.detalles) }}
</dl>
<p><input type="submit" value="Save Changes"></p>
</form>
Can you try to change the below from :
form = NewOtrosForm(obj=product)
to:
form = NewOtrosForm(request.form, obj=product)
You don't need to use use merge() here, its purpose is to transfer state from an outside object into a new or already existing instance within a session. Your product object already belongs to the session, so it's enough to call form.populate_obj(product) (changes to product marks it as "dirty" in this session) and db_session.commit() after that.
Related
I implemented WTForm's FieldList with SelectField's to render multiple select choices based on data in a JSON file (as stated in this answer). It displays the select choices fine, but when I try to pass all the values that the user selected via a SubmitField() form, the request only contains the value from the first one.
Question: How can I access ALL the selected values from my multiple SelectFields from my route?
Interesting Note:
I did this same FieldList approach with multiple StringFields and get each of the values as expected in the request. (Why would this be different for SelectFields?)
The multiple select fields:
Here Ford, Associate, and Bucks are the values from the select fields.
The request response: (from request.form):
Notice how only Ford is shown. Why not also Associate and Bucks?
Notice how all the text fields show up correctly
My forms (forms.py):
class SelectForm(FlaskForm):
select = SelectField(choices=[]) #TODO testing without placeholder
class SelectFormList(FlaskForm):
"""A form that supports multiple select forms"""
select_entries = FieldList(FormField(SelectForm))
My template (home.html):
<form id="metadata_home_fom" action="{{ url_for('save_metadata') }}" method="POST">
{% for select_form in select_metadata_form_list.select_entries %}
{{ select_form.hidden_tag() }}
{{ select_form.select.label }}: {{ select_form.select }}
{% endfor %}
{% for text_form in text_metadata_form.text_fields %}
{{ text_form.hidden_tag() }}
{{ text_form.text }}
{% endfor %}
</form>
My view (routes.py):
#app.route('/home', methods=['POST', 'GET'])
def home():
select_metadata_form_list = SelectFormList()
select_metadata_form_list.select_entries = get_select_entries()
context = {"text_metadata_form": text_metadata_form,
"select_metadata_form_list": select_metadata_form_list}
return render_template('configuration.html', **context)
The problem was I was creating my forms in a loop without overriding the form's
name, so each of the SelectFields had the same name.
The quick fix was just appending a unique id to the name:
for k, v in select_dict.items():
some_id = uuid.uuid1()
select_entry = SelectForm()
select_entry.select.name = f"select_entry-{some_id}" # each SelectForm() unique
select_entry.select.label = k
select_entry.select.choices = v
all_select_items.append(select_entry)
I am looking to build a multiple choice quiz using python/flask/flask-wtf. I am successfully able to pull random questions from a database as well as pull random choices as the possible answers. I am using a for loop on the template page that first displays the question and then the possible answers.
Here is my template.
<div class="quote-block">
{% for quote in quotes %}
{{ quote.line_text }}
<form method="POST">
{{ form.hidden_tag() }}
{{ form.question }}
<input type="submit">
</form>
{% endfor %}
</div>
My problem is that the possible answers are the same for each question and in the same order. I understand why this is happening. The query into the database for the RadioField choices is only happening once. And then the results of that query are being pulled for each instance of the for loop.
Here is my view route.
#app.route('/quiz/', methods=['POST', 'GET'])
def quiz():
quotes = Quotes.query.order_by(func.rand()).limit(5)
form = Quiz()
form.question.choices = [(choice.speaker_id, choice.speaker) for choice in Characters.query.order_by(func.rand()).limit(4)]
return render_template('television/quiz.html', form=form, quotes=quotes, options=options)
And my form.
class Quiz(FlaskForm):
question = RadioField('Label', choices=[])
Like I said, all of this works. I just can't figure out how to start a new choices query for each question. Any help would be greatly appreciated. Thanks!
You could try dynamically extending your FlaskForm:
def Quiz(number_of_questions):
class _Quiz(FlaskForm):
pass
for n in range(number_of_questions):
setattr(_Quiz, RadioField('Label', choices=[]), 'question_' + str(n))
return _Quiz()
Now your form has a question_[n] attribute for each question so you can iterate it in jijna2:
<div class="quote-block">
<form method="POST">
{{ form.hidden_tag() }}
{% for q_num in range(n) %}
{{ quotes[q_num].line_text }}
{{ form['question_' + q_num|string }}
{% endfor %}
<input type="submit">
</form>
</div>
After a lot of reading and research, I realized I was trying to do too much too soon. So I took a step back and built the blocks of what I was trying to do and then it finally came together. Of course, I am having new issues with the form validation portion, but I'll include that in a new question.
My biggest roadblock was that I was trying to pull random choice options for the RadioField. Instead, I added three columns to the database table and supplied the options in the table. That made it easier to pull the options and the question with one query.
Basically, I am running a for loop over the fields in the form. If the field is a RadioField, it runs a query against the database and pulls a random row. I then use another for loop over the pulled row and assign different elements to the pieces of the RadioField (label, choices).
If you know of a more elegant way to do this, I'd love to hear it. But for now it works.
My form and template stayed the same. But here is my new route view.
#app.route('/quiz/', methods=['GET', 'POST'])
def quiz():
form = Quiz()
for field in form:
if field.type != "RadioField":
continue
else:
pulls = Quotes.query.order_by(func.rand()).limit(1)
for pull in pulls:
answer = pull.speaker
option_two = pull.option_two
option_three = pull.option_three
option_four = pull.option_four
question = pull.line_text
field.label = pull.line_text
field.choices = [(answer, answer), (option_two, option_two), (option_three, option_three), (option_four, option_four)]
return redirect(url_for('you_passed'))
return render_template('television/quiz.html', form=form)
I try to learn Flask and I can't find an answer to the question: Is there a way to get a “clean” URL, that can be bookmarked, if I use GET as form method with the Flask-WTF plugin?
If I use in a template the method POST:
<form method="POST" action="">
The URL in the browser will not alter, in Flask's debug mode it would be:
http://127.0.0.1:5000/
If I submit the form.
If I use the method GET, the URL would look like this:
http://127.0.0.1:5000/?name=test&submit=Submit&csrf_token=1453393786%23%23a327427d
But I would like to use the Flask-WTF plugin to make forms and get back a nice, bookmarkable URL in the browser like this:
http://127.0.0.1:5000/?name=test
Is that possible?
What I try to achieve is something like this in PHP:
<?php
if( $_GET["name"] ) {
echo "Hello, ". $_GET['name']. "!";
exit();
}
?>
<html>
<body>
<form action = "<?php $_PHP_SELF ?>" method = "GET">
Name: <input type = "text" name = "name" />
<input type = "submit" />
</form>
</body>
</html>
After submitting, I get this URL:
http://127.0.0.1/test.php?name=test
I can copy this link, send it to someone else, he or she can open it with a browser and will get the same result. It is simply done, by using the method GET in the form. Look at this:
http://www.utrace.de/?query=8.8.8.8
With Flask I cloud do this:
http://127.0.0.1/query/8.8.8.8
But what, if I would use more than one parameter? In PHP it would look like this:
http://127.0.0.1/?para1=8.8.8.8¶2=US
I've tried it, using this code (lent from Miguel Grinberg):
Program:
from flask import Flask, render_template
from flask.ext.wtf import Form
from wtforms import StringField, SubmitField
from wtforms.validators import Required, Length
app = Flask(__name__)
app.config['SECRET_KEY'] = 'top secret!'
class NameForm(Form):
name = StringField('What is your name?', validators=[Required(),
Length(1, 16)])
submit = SubmitField('Submit')
#app.route('/', methods=['GET', 'POST'])
def index():
name = None
form = NameForm()
if form.validate_on_submit():
name = form.name.data
form.name.data = ''
return render_template('index.html', form=form, name=name)
if __name__ == '__main__':
app.run(debug=True)
Template:
<form method="GET" action="">
{{ form.name.label }} {{ form.name(size=16) }}
{% for error in form.name.errors %}
{{ error }}
{% endfor %}
<br>
{{ form.submit() }}
{{ form.hidden_tag() }}
</form>
{% if name %}
<h1>Hello, {{ name }}!</h1>
{% endif %}
I would suggest factoring this out into its own view. You should use CSRF for forms, and you should try to separate GET and POST interfaces.
Forcing one piece of code or function to do multiple things sometimes might seem cleaner, but what happens is you add to maintainability costs and make things less clear down the road.
How you'd do this in Flask:
#app.route('/<name>/', methods=['GET'])
Now, this does not give you the validation. You can either do this yourself, or use a library such as Marshmallow: https://marshmallow.readthedocs.org/en/latest/
In your example Marshmallow would be overkill, but it is great if you plan on expanding your API. It will allow you to take more complicated JSON blobs and validate and serialize them into Python objects.
Also, I'd look into why you're using GET over POST. POST should be used to create new data on a server, GET should be used to get that information.
EDIT (After your edits):
With Flask I cloud do this:
http://127.0.0.1/query/8.8.8.8
But what, if I would use more than one
parameter? In PHP it would look like this:
You would make your Flask view like this:
#app.route('/<name>/<another_parameter>/<another>/', methods=['GET'])
def some_route_name(name, another_parameter, another):
However, if you want to accomplish what you want with ONE form, you would have to turn CSRF off. Your PHP example is not using CSRF.
I suggest this:
1) Creating a new view, as I originally suggested
2) On your view with the form, have it POST to itself, then redirect to the new view, like the below:
if form.validate_on_submit():
return redirect(url_for('name_of_new_view', name=form.name.data))
3) In this new view, put your form, but have that form POST to your OLD view. Make sure you're including the CSRF token when you POST! Like here:
{{ form.csrf_token }}
I am trying to pass some dynamic values from my form, but so far I am only getting empty values.
I have an autocomplete plugin, where I search for "names" from a JSON object, each time I select one they will pass into a list with value="the ID".
I couldn't find a unordered list in WTForms, so I am using SelectMultipleField instead, since it is able to pass many values as an array/list
my form class is looking like this:
class ClassForm(Form):
function_name = StringField('names')
studentid = SelectMultipleField('studentid')
submit = SubmitField('submit')
then in my template I am using it like this
<form id="function_search_form" method="post" action="">
{{ form.csrf_token }}
{{form.function_name.label()}}
{{form.function_name()}}
<!-- then I am not using studentid directly,
but just normal html, so each time you pass
in a name from the json object it will come in like this.
-->
<ol class='student-list'>
<li value="1" name="studentid" id="studentid">test</li>
</ol>
{{ form.submit()}}
</form>
My problem is that it wont get the value from the list, even if I hard code the values directly, instead from the jquery script.
Here is how my view looks like
#app.route('/index', methods=['GET', 'POST'])
def index():
form = ClassForm()
if request.method == 'POST' and form.validate_on_submit():
flash('valid form')
st = form.studentid.data
print(st)#debug
return render_template('index.html', form=form)
Everytime I submit I am printing form.studentid.data I am getting [], which is an empty list.
Even when I try to pass single values and make studentid to a StringField I am still getting an empty value.
I have also tried the request.form['studentid'] but then I am getting Bad Request What am I doing wrong and is there another way to pass "custom" values ?
The reason its not working is because <li> is not a form control, so its data is not sent back with the form request.
You are never rendering the studentid field from your form class, so the form control is never rendered. It would be like expecting the following to work:
<form>
<p name="foo" data="hello">This should be sent</p>
<input type="submit">
</form>
To get the data back to your view method, you need to use a form control - you can test it out like this:
<form id="function_search_form" method="post" action="">
{{ form.csrf_token }}
{{form.function_name.label()}}
{{form.function_name()}}
<select name="studentid" class='student-list'>
<option value="1">test</option>
</select>
{{ form.submit()}}
</form>
Or, simply render the field correctly:
<form id="function_search_form" method="post" action="">
{{ form.csrf_token }}
{{form.function_name.label()}}
{{form.function_name()}}
{{form.studentid.label()}}
{{form.studentid}}
{{ form.submit()}}
</form>
I have a form with several input fields about a meeting.
#app.route('/Alt_Reuniao/<WCodigoChaveP>', methods=['GET', 'POST'])
def Alt_Reuniao(WCodigoChaveP):
# This function returns a list with de data from the Database about a specific meeting
WReuniao = Executa_Query_Leitura("0005", WCodigoChaveP, "O")
if not WReuniao:
flash('Error.......')
return redirect(url_for('Cad_Reunioes'))
else:
if WReuniao[8]:
# this function returns a list with de data from the Database about the topic of the meeting
WAssunto = Executa_Query_Leitura("0002", WReuniao[8], "O")
Wform = Cad_Reunioes_Form()
if request.method == "POST":
if Wform.validate():
# save the data ......
return redirect(url_for('Cad_Reunioes'))
else:
for Werro in Wform.errors.values():
flash(Werro[0])
return render_template('Reuniao.html', Wformulario=Wform)
else:
Wform.WCPO_Reuniao.data = WReuniao[1]
Wform.WCPO_Desc_Reuniao.data = WReuniao[2]
Wform.WCPO_Nro_Part.data = WReuniao[3]
Wform.WCPO_Cod_Assunto.data = WReuniao[8]
if WReuniao[8]:
if WAssunto:
Wform.WCPO_Assunto.data = WAssunto[1]
return render_template('Reuniao.html', Wformulario=Wform)
This is my Reuniao.html template:
{% extends "Base_Cad_2.html" %}
{% block dados %}
{{ Wformulario.WCPO_Reuniao.label(id="WCPO_Reuniao", class="lab1") }} {{ Wformulario.WCPO_Reuniao(size = 100, maxlength=30, id="WCPO_Reuniao") }}
<p id="PL"> {{ Wformulario.WCPO_L_Desc_Reuniao(id="WCPO_L_Desc_Reuniao", class="lab1") }} </p>
{{ Wformulario.WCPO_Desc_Reuniao(rows=5, cols=100, id="WCPO_Desc_Reuniao") }}
{{ Wformulario.WCPO_Nro_Part.label(id="WCPO_Nro_Part", class="lab1") }} {{ Wformulario.WCPO_Nro_Part(size = 5, id="WCPO_Nro_Part") }}
{{ Wformulario.WCPO_Cod_Assunto.label(id="WCPO_Cod_Assunto", class="lab1") }} {{ Wformulario.WCPO_Cod_Assunto(size=10, readonly='readonly', id="WCPO_Cod_Assunto") }}
{{ Wformulario.WCPO_Assunto.label(id="WCPO_Assunto", class="lab1") }} {{ Wformulario.WCPO_Assunto(size=95, readonly='readonly', id="WCPO_Assunto") }}
<button id="Selec_Assunto" name="Selec_Assunto" value="Selec_Assunto" type="button"> <a class="botoes" href="{{ url_for('Selec_Assuntos_Inicio', WRotChama = "001", WCodorig = Wformulario.WCPO_Cod_Reuniao ) }}" hreflang="pt-br"> Seleciona </a> </button>
{% endblock %}
{% block botoes %}
<button id="gravar" name="gravar" value="Gravar" type="submit" class="botoes" > Gravar </button>
{% endblock %}
Basically, this view works fine.
When I select a meeting from a list in a previous template the view method is a GET and the data from the database is passed to the form and the template renders correctly.
When the method is a POST the data from the form is saved correctly in the Database,…
On the form there is a button Selec_Assunto. When the user click on that button I point to a view witch renders a template with a list of all possible topics for the meeting. These topics come from the database. There can be a lot of them so I can’t just use a combo. That’s why I use a template.
When the user select an topic from the list I have to render Alt_Reuniao view again and I have to pass to the view the selected topic.
This is working fine.
My problem is this: the method again is a GET. If before hitting the Selec_Assunto button the user alter or input data on the other fields in the form I loose these data from the user when the topic is selected. The view render the template with the data from the database again.
Everything seems to be working fine. I just want to maintain the data the user already change in the form before clicking on the Selec_Assunto button.
As you can see I’m new in Web developing, Python, Flask,…
Thanks a lot in advance for your help.
In this case you could update the "Selec_Assunto" button behavior to post the form data back to the server, but also include a redirect variable. When the redirect variable is included, the server would save the form changes and then redirect to the "Selec_Assuntos_Inicio" view, whereas it would follow the previous/normal form posting behavior if the redirect variable isn't present. For example:
if request.method == "POST":
if Wform.validate():
# save the data ……
if Wform.redirect.data:
return redirect(Wform.redirect.data)
else:
return redirect(url_for('Cad_Reunioes'))
else:
for Werro in Wform.errors.values():
flash(Werro[0])
return render_template('Reuniao.html', Wformulario=Wform)
It's worth noting that this approach requires you to use JavaScript to override the "Selec_Assunto" button behavior (as you'd be forcing it to perform a form submission essentially). Here's one way you could do that using jQuery:
$('button#Selec_Assunto').on('click', function() {
$('form#formId').append('<input type="hidden" name="redirect" value="{{ url_for('Selec_Assuntos_Inicio', WRotChama = "001", WCodorig = Wformulario.WCPO_Cod_Reuniao ) }}">');
$('form#formId').submit();
});
That said, a potentially better option from both a coding and usability perspective would be to asynchronously load the topic data into the existing page so that you don't have to reload the view at all. That would enable you to avoid needing to do an interim save of the form data.