I have a jinja2 template with a simple user preferences form. The preferences are passed as a python dictionary into the render_template() function. Depending on a setting, I need to mark one of the radio buttons in a group as checked.
Q: How to do it cleanly, without much code repetition?
Below is my current solution. It works, but it's ugly, and with more than 2 radio buttons, it would soon become hard to manage. There is probably a better way.
I use two string variables (for the 2 radio btns). One of them will be empty, and the other will be set to 'checked', according to the preference setting:
{% if user_prefs['when_done'] == 'index' %}
{% set indexchecked = 'checked' %}
{% set backchecked = '' %}
{% else %}
{% set indexchecked = '' %}
{% set backchecked = 'checked' %}
{% endif %}
<!-- With more radio buttons here, this would be a mess! -->
And then I use these strings in the template:
<form action="{{ url_for('prefs') }}" method="post">
<fieldset>
<div class="radio text-left">
<p><strong>After completing a task:</strong></p>
<label>
<input type="radio" name="when_done" value="index" {{ indexchecked }}>
Return to homepage
</label>
<br/>
<label>
<input type="radio" name="when_done" value="task" {{ taskchecked }}>
Go to next task
</label>
</div>
<div class="form-group">
<button class="btn btn-default" type="submit">Update preferences</button>
</div>
</fieldset>
</form>
I would move the computation of indexchecked and backchecked from the template to the code. You can also use dictionary unpacking in order to pass less parameters to the render_template method.
do_index = user_prefs['when_done'] == 'index'
index_checked = 'checked' if do_index else ''
back_checked = '' if do_index else 'checked'
render_template('pages/something.html', form=some_form, index_checked=index_checked, back_checked=back_checked)
In order to reduce the number of passed parameters you can use a dict with unpacking:
template_parameters = dict(form=some_form, index_checked=index_checked, back_checked=back_checked)
render_template('pages/something.html', **template_parameters)
Related
My problem is not that serious, just a little bit annoying. I have a dropdown menu and a list of values; however, my values resets themselves to the first option, and I would like for them to remain as the user selected them.
I have read from other sources that the solution is using getlist instead of get, but when I attempt to do it, I get the following error:
TypeError: int() argument must be a string, a bytes-like object or a number, not 'list'
I have little experience working with flask and Jinga. I guess this must be a problem with the type of values, or some type of name or value that I need to fetch... I honestly have no idea. Any help will be appreciated.
Here is the video of how the flask is working with request.form.get, and here is the code that I have for that specific html view and the fragment of the app where I am requesting the data.
#app.route('/apuntual', methods = ['GET','POST'])
def apunt():
if request.method == 'POST':
# This is the button that I click on the video
if request.form.get('capturar') == 'capturar':
sample_rate_unf = request.form.get('f_muestreo')
samples_to_acq_unf = request.form.get('m_canal')
#Changing the values to int so I can work with them
sample_rate = int(sample_rate_unf)
samples_to_acq = int(samples_to_acq_unf)
#lots of more code in here
#
# Sending the output values to make the graph, and some other values to
# display in the html
return render_template('apuntual.html', fmax = fmax, tad = tad, imagen = datos)
<div class="col-md-2">
<form method="POST">
<h5 class="mb-1">Frecuencia de muestreo</h5>
<select name="f_muestreo">
<option value="2048">2048</option>
<option value="2560">2560</option>
<option value="3200">3200</option>
<option value="5120">5120</option>
</select>
<h5 class="mb-1">Muestras por canal</h5>
<select name="m_canal">
<option value="2048">2048</option>
<option value="4096">4096</option>
<option value="8192">8192</option>
</select>
<h5 class="mb-1">Captura instantánea</h5>
<p class="bs-component">
<input type="submit" class="btn btn-primary" name="capturar" value="capturar">
<input type="submit" class="btn btn-primary" name="borrar" value="borrar">
</p>
<p class=""bs-component>Frecuencia máxima de: {{ fmax }} Hz con TAD: {{ tad }} ms.</p>
</form>
</div>
One solution I can think of is to pass the selected option as a variable to the template and mark the selected option in the template. Here is a demo:
#app.route("/", methods=("GET", "POST"))
def demo():
options = (1, 2, 3, 4)
selected = None
if request.method == "POST":
selected = int(request.form.get("something"))
# Rest of the code goes here
return render_template("demo.html", options=options, selected=selected)
<form method="POST">
<select name="something">
{% for op in options %}
{% if selected == op %}
<option value="{{ op }}" selected>{{ op }}</option>
{% else %}
<option value="{{ op }}">{{ op }}</option>
{% endif %}
{% endfor $}
</select>
<input type="submit">
</form>
Notice that I put all the options as a tuple in the server code. One of reason is to avoid repetitions in the template code. It is also generally considered a bad practice to store data directly to the frontend code like what you are doing here. My demo is not perfect either. A better solution is to put all these options into a configuration file.
As I understand it, if you want to populate a textarea you place the text between the textarea tags. However I am using WTForms. How can I pre-populate the form from views or in my template?
FORM
class ModuleSectionForm(FlaskForm):
title = StringField('Section Title', validators=[DataRequired()])
description = TextAreaField('Description', validators=[DataRequired()])
submit = SubmitField('Add Section')
VIEW
#modules.route('/update_section/<name>/<title>', methods=['GET', 'POST'])
def update_section(name, title):
form = ModuleSectionForm()
module = Module.objects(title=name).first()
section = None
for sect in module.sections:
if sect.title == title:
section = sect
#if form.validate_on_submit():
#save data
return render_template('modules/update_section.html', section=section, form=form)
TEMPLATE
<form method="post" action="{{ url_for('modules.update_section', name=name) }}">
{{ form.hidden_tag() }}
<div class="form-group">
{{ form.title.label(class="form-control-label") }}
{{ form.title(class="form-control", value=section.title) }}
</div>
<div class="form-group">
{{ form.description.label(class="form-control-label") }}
{{ form.description(class="form-control", default=section.description) }}
</div>
<div class="form-group">
{{ form.submit(class="btn btn-secondary shadow") }}
</div>
</form>
This can be done by just assigning the text to display beforehand (in the view, for instance).
Try edit your view (update_section) this way:
+ form.description.data = 'text you want to display'
And your template as follows:
- {{ form.description(class="form-control", default=section.description) }}
+ {{ form.description(class="form-control") }}
Mind you, there's an alternative way, which is to specify the placeholder attribute (but I guess is not what you want to do here).
Typically with WTForms (let's assume you're using Flask and Bootstrap here), you'll use the value attribute of the input to pre-populate a form field. Note that pre-populating a field is distinct from providing a 'placeholder', which is just an ephemeral hint. So usually we pre-populate like this:
<div class="form-group row">
<label for="form_subject" class="col-sm-2 col-form-label">Subject</label>
<div class="col-sm-7">
<input class="form-control" id="form_framework" name="form_framework"
value="{{ instruct.Subject }}">
</div>
</div>
With Flask and Bootstrap, the name attribute is required to pass the value back to the Controller upon submission, the value attribute is used to pre-populate the field from the object - in this case, our controller has passed in an object called instruct, which has an attribute Subject.
But you have to be aware that different kinds of inputs in WTForms have different attributes, and this is left as a fun challenge for the developer to figure out.
TextArea doesn't have a value attribute, so in order to pre-populate the field, you have to provide the value between the tags, like so (again, using Bootstrap here in case any of these tags are unfamiliar):
<div class="form-group row">
<label for="form_longish_text"
class="col-sm-2 col-form-label">Longish Text</label>
<div class="col-sm-7">
<textarea class="form-control" rows="3"
id="form_longish_text"
name="form_longish_text">{{ instruct.LongishText }}
</textarea>
</div>
</div>
I'm trying to dynamically set the value of a radio field within the HTML template but I'm not sure how to set the default selection. I want to do this because the forms I want to use are forms that can be saved and re-edited if need be, so I want the default values to be values set when previously saved/submitted.
the wtform fields
field_1 = StringField('field_1')
radio_1 = RadioField('radio_1', choices=[(1,'Yes'),(2,'No')])
what i want to be able to do is something like this:
data is database data
{% if data.field_1 = 'X' %}
{{ form.radio_1(id="radio_1",class="ff-style-radio",default=1) }}
{% else %}
{{ form.radio_1(id="radio_1",class="ff-style-radio",default=2) }}
{% endif %}
I haven't had any success trying this method, swapping default for value, etc. Is something like this possible? If not how would I separate the wtform radio field choices so I can just manually mark which selection is checked? Or should I just use the base HTML method and do something like this:
<ul class="ff-style-radio" id="radio_1">
<li>
{% if data.field_1 = 'X' %}
<input id="radio_1-0" name="radio_1" type="radio" value="Yes" checked>
{% else %}
<input id="radio_1-0" name="radio_1" type="radio" value="Yes">
{% endif %}
<label for="radio_1-0">Yes</label>
</li>
<li>
{% if data.field_1 = 'X' %}
<input id="radio_1-1" name="radio_1" type="radio" value="No">
{% else %}
<input id="radio_1-1" name="radio_1" type="radio" value="No" checked>
{% endif %}
<label for="radio_1-1">No</label>
</li>
</ul>
Thanks for any help
If you want to use the form to edit data already in your database, you should supply this data to the form at creation time in your view function. This will allow you to keep your templates simple and reusable. Data from the database can be passed to the form either as an object or as a dictionary https://wtforms.readthedocs.io/en/stable/forms.html. If your form maps directly to a database table you can just pass this unmodified from your query. In the following code I've followed your example and created a new dictionary to set the value of radio_1 based on the value of field_1.
#app.route("/myurl")
def myview():
# do database lookup here
olddata = {"radio_1": 1 if data.field_1 == "X" else 2}
form = Myform(formdata=request.form, data=olddata)
if request.method == "POST" and form.validate():
# .....
Im using wtf-form for my password reset page but im having trouble with the generated error messages. I want to be able to either disable the error that wtf-form generates and use my own or be able to override them.
{% for e in form.password.errors %}
<span class="label label-danger">{{ e }}</span>
{% endfor %}
<form action="http://127.0.0.1:5000/setpassword" method=post>
<div class="form-group">
{{ form.csrf_token }}
{{ wtf.form_field(form.password, class='form-control', placeholder='Enter Password') }}
</div>
class PasswordForm(FlaskForm):
password = PasswordField('',validators=[pass_num, pass_small_lett, pass_big_lett, pass_special, pass_min])
confirm = PasswordField('',[validators.EqualTo('password', message='Not matching')])
submit = SubmitField('Send')
Don't use form_field() if you don't want its extra functionality. This will render just the input field:
{{ form.password(class='form-control', placeholder='Enter Password') }}
In your example the field's label is empty, but in case the field has a label, it must be printed as well. Bootstrap rules are:
Wrap labels and form controls in <div class="form-group"> (needed for optimum spacing)
Add class .form-control to all textual <input>, <textarea>, and <select elements
(source: https://www.w3schools.com/bootstrap/bootstrap_forms.asp)
I was trying to get the item selected in the Dropdown menu of the form. But i can't access the selected item. So to check, whether the data is available in views.py i used messages.error().But it shows None like
Here is the form:
<form method="post" name="deleteitemform" id="deleteitemform" style="padding-bottom:50px; padding-top:10px;">
{% csrf_token %}
<div class="input-group">
<span class="input-group-addon" id='prepandID'>Item Name :</span>
<select class="form-control" id="delete-item-select" name='delete_select'>
{% for item in items %}
<option value="{{item.item_name}}">{{item.item_name}}</option>
{% endfor %}
</select>
</div>
<button class="btn btn-primary col col-md-2 col-md-offset-5" style="margin-top:10px;" name='deletebutton' type="submit">Delete</button>
</form>
And in views.py:
if 'deletebutton' in request.POST:
selected_item = request.POST.get("detele_select", None)
# to_be_deleted = Item.objects.filter(item_name=selected_item)
# to_be_deleted.delete()
messages.error(request, str(selected_item))
return redirect('/restaurant/updateitems')
else:
return redirect("/")
I am not sure what I am doing wrong. Can anyone help on this regard?
In views you use detele_select instead of delete_select specified in your form.
By the way, it is easier and more convenient to use Django forms. It does a lot of work instead of you.