How to have radio buttons with Materialize and WTForms? - python

In Materialize you define the radio group of a radio button using the attribute name, but Flask-WTForms binds the input with an attribute name.
If I have the following in my template:
{{ form.radio1(type='radio', name='group1') }}
{{ form.radio2(type='radio', name='group1') }}
There will be an error:
TypeError: html_params() got multiple values for keyword argument 'name'
And if we don't add the name, the radios won't work as radios, just as checkboxes, as expected.
How can i get around this?
This is my form class:
class AbcForm(FlaskForm):
field1 = HiddenField('Field1')
field2 = HiddenField('Field2')
and then at runtime I'll dynamically add the radios, here's a simplification:
class F(AbcForm):
pass
setattr(F, radio1, BooleanField('Radio1')
setattr(F, radio2, BooleanField('Radio2')
form = F(field1=x, field2=y)

You can't set the name attribute in your field, wtforms already does that for you.
Use the RadioField instead of HiddenField:
from wtforms import RadioField
from flask_wtf import Form
class YourForm(Form):
radio_group = RadioField('label', choices=[('value','description'),('value_two','some other description')])
(...)
Then, in your endpoint:
#route('/your/route')
def your_endpoint():
your_form = YourForm()
(...)
return render_template('/your/template.html', form=your_form)
Finally, in your template:
<form action="#">
{% for subfield in form.radio_group %}
<p>
{{ subfield }}
{{ subfield.label }}
</p>
{% endfor %}
</form>
This generates the following code:
<form action="#">
<p>
<input id="radio_group-0" name="radio_group" type="radio" value="value">
<label for="radio_group-0">description</label>
</p>
<p>
<input id="radio_group-1" name="radio_group" type="radio" value="value_two">
<label for="radio_group-1">some other description</label>
</p>
</form>
Which, given materialize is included in the template, will render the radio buttons properly as shown in this plunker.

Related

Django AdminDateWidget apprearing as TextInput

I don't understand how widgets work.
I tried this minimum example :
in my forms.py
class PartialResetForm(forms.Form):
date = forms.DateField(
label="Starting date",
widget=AdminDateWidget()
)
in my admin/intermediary_reset_page.html
{% extends "admin/base_site.html" %}
<!--Loading necessary css and js -->
{{ form.media }}
{% block content %}
<form action="" method="post">{% csrf_token %}
<!-- The code of the form with all input fields will be automatically generated by Django -->
{{ form }}
<!-- Link the action name in hidden params -->
<input type="hidden" name="action" value="custom_action" />
<!-- Submit! Apply! -->
<input type="submit" name="apply" value="Submit" />
</form>
{% endblock %}
in my admin.py as the definition of an action
def custom_action(self, request, queryset):
form = PartialResetForm()
return render(request, "admin/intermediary_reset_page.html", {
"items": queryset, "form": form
})
For now I don't care about the queryset, it will be my next topic. With this simple example, I wanted to have a calendar in order to help pick a date, but only a TextInput appeared. I believe it is due to the fact that AdminDateWidget inheritates from TextInput.
My question is why isn't it appearing as a calendar ? I imported the media and declared my widget, I don't understand what else I'm supposed to do.
you should declare type
AdminDateWidget(attrs={'type': 'date'})
And it should be enough ;)

"Not a valid choice" in SelectField Only After Adding Bootstrap

I'm trying to build a very basic app in Flask: a single form that accepts a few inputs from the user (I'm brand new to web development). I was able to get it up and running initially, but when I tried to modify the HTML to include Bootstrap, I get a Not a valid choice error when clicking my submit button.
Main app:
from flask import Flask, render_template, flash, session, redirect, url_for, request
from forms import CustomAnalyticsMenu
app = Flask(__name__)
app.config['SECRET_KEY'] = 'mysecretkey'
#app.route('/')
#app.route('/home')
def home():
return render_template('home.html')
#app.route('/custom_analytics_menu', methods=['GET', 'POST'])
def custom_analytics_menu():
form = CustomAnalyticsMenu()
if form.validate_on_submit():
print("validated_on_submit")
session['menu_item'] = form.menu_item.data
return redirect(url_for('submission'))
else:
print(form.errors)
return render_template('custom_analytics_menu.html', form=form)
#app.route('/submission', methods=['GET', 'POST'])
def submission():
return render_template('submission.html')
if __name__ == '__main__':
app.run(debug=True)
Form:
from flask_wtf import FlaskForm
from wtforms import StringField, RadioField, SelectField, SubmitField
from wtforms.validators import DataRequired
class CustomAnalyticsMenu(FlaskForm):
menu_item = SelectField('Menu Item:', choices=[('option_one', 'Option One'),
('option_two', 'Option Two'),
('option_three', 'Option Three')])
generate_deck = SubmitField('Generate deck')
HTML:
This is the HTML code that works:
{% extends "layout.html" %}
{% block content %}
<div>
<form method="POST" action="">
{{ form.hidden_tag() }}
{{ form.menu_item.label }} {{ form.menu_item }}
{{ form.generate_deck() }}
</form>
</div>
{% endblock %}
However, when I change this portion:
{{ form.menu_item.label }} {{ form.menu_item }}
To this: (which I got from here: https://getbootstrap.com/docs/4.0/components/forms/#form-controls)
<div class="form-group">
<label for="menu_item">{{ form.menu_item.label }}</label>
<select class="form-control" id="menu_item">
{% for item in form.menu_item.choices %}
<option>{{ item[-1] }}</option>
{% endfor %}
</select>
</div>
My SelectField functionally looks the same (except obviously it has the Bootstrap styling), but now when I click the submit button, nothing happens and I get the error Not a valid choice. This is a snippet of what I'm trying to do, I also have a RadioField and a StringField but I get the same errors for those as well when trying to apply similar styling. Can someone help me understand where I'm going wrong?
I think I figured it out! I saw another StackedOverflow question (Why is my flask form validation returning Not a valid choice?) and they had a name field in their <select>. I updated my (value, label) pairs so they matched (i.e. ('Option One', 'Option One') rather than ('option_one', 'Option One')) and updated the code to this:
<div class="form-group">
<label for="menu_item">{{ form.menu_item.label }}</label>
<select class="form-control" name="menu_item">
{% for item in form.menu_item.choices %}
<option>{{ item[-1] }}</option>
{% endfor %}
</select>
</div>
It worked! Alternatively, I could have left the (value, label) pairs the same and used the code from SuperShoot's answer above. I can't say I fully understand why the name tag was needed, so if anyone has an explanation for that, I'd love to hear it.
Edited to include name attrib on <select> field as resolution of additional issue and further explained in #RaghavPaliwal's answer.
The choices that you pass to the SelectField are a a sequence of (value, label) pairs.
In your example these are:
[('option_one', 'Option One'),
('option_two', 'Option Two'),
('option_three', 'Option Three')]
The first element of those tuples is the actual value that the option represents and is meaningful in terms of your application. It needs to be set as the value attribute of the <option> tag. The second element in each tuple is just a nicely formatted representation of the value that the option represents for display to the user.
So, you need to provide a value attribute to each of your <option> tags:
<div class="form-group">
<label for="menu_item">{{ form.menu_item.label }}</label>
<select class="form-control" id="menu_item" name="menu_item">
{% for val, text in form.menu_item.choices %}
<option value="{{ val }}">{{ text }}</option>
{% endfor %}
</select>
</div>
If you don't provide a value attribute to your <option> tags, the content of the tag will be passed as the value of the form field instead. In your case this means that if 'Option One' was selected, the string 'Option One' would be passed as the value of the field. The form validation then tests that it is included in one of the choices values, 'option_one', 'option_two', or 'option_three', so the validation fails.

Flask dynamic input form, FormField returns blank POST data

I'm working on a Flask webapp that asks (internal) users to select options for an arbitrary number of files that will be in a directory on our network so that some other scripts can process the files according to those options (not relevant to this issue, but if you're curious they are A/V files we need to process).
The problem I'm having is that I can't seem to both dynamically generate the form fields needed (there could be 0 - dozens of files in the server directory the app is looking in) and collect the form data for each instance of the form class I created for the input objects. How do you shoehorn n form instances into an instance of another form class??
I have a base IngestForm class and an ObjectForm class that describes fields pertaining to each individual object. My basic suspicion is that wtforms can't have a subclass that includes other forms... but I can print everything out at various steps before the form gets POSTed and see all the data that I expect as a dict, and I can see that the sub-forms are there as wtforms objects. From my index.html template I can see all the expected data from the ObjectForm instances. But once the super-form is posted, all that is returned is a blank choicesDict (see below) and the submit value. Does the IngestForm instance get reinitialized or something weird when I hit Submit?
Here's what I have now. I have set up a dict for choices where each key is the path of the file in question and the value is an instance of the ObjectForm class:
forms.py
class ObjectForm(FlaskForm):
"""
Fields for an individual object
"""
targetFilePath = wtforms.HiddenField('targetObjectPath')
option1 = wtforms.BooleanField('Option1?')
option2 = wtforms.BooleanField("Option2?")
# etc.
class IngestForm(FlaskForm):
'''
General input form
'''
choicesDict = {}
# also tried just targetObject = wtforms.FormField(ObjectForm)
submit = wtforms.SubmitField('Submit')
routes.py:
[import relevant stuff]
#app.route('/index',methods=['GET','POST'])
def index():
# GET A DICT OF PATHS AND BASENAMES TO PROCESS {'fullPath':'basename'}
objects = listObjects.list_objects()
class OneObject(forms.ObjectForm):
pass
choices = {}
for path,_object in objects.items():
choices[path] = OneObject(targetPath=path,targetBase=_object)
# also tried setattr(forms.IngestForm,'choicesDict',choices)
form = forms.IngestForm()
form.choicesDict = choices
if form.is_submitted():
return redirect(url_for('status'))
return render_template(
'index.html',title='Index',objects=objects,form=form
)
#app.route('/status',methods=['GET','POST'])
def status():
# DO THE STUFF
ingest.html template:
{% block content %}
<h1>Here are files to ingest:</h1>
<form action="{{url_for('status')}}" method='POST'>
<div>
{% for item,vals in form.choicesDict.items() %}
<p>{{vals.targetBase.data}}</p>
<p>{{vals.option1.label}}{{vals.option1()}}</p>
<p>{{vals.option2.label}} {{vals.option3()}}</p>
<p>{{vals.etc.label}}{{vals.etc()}}</p>
{%endfor%}
</div>
{{form.submit()}}
</form>
{% endblock %}
status.html template just takes the POST data. Not really relevant here except to say I can see it is getting none of the choicesDict
OK so I solved this in a really hack-y way but whatever. I used a jinja2 macro following this example and in my form template constructed field names/ids that are unique to the files I'm interested in.
So for each file ['a.mov','b.mov','c.mp4'] in my network directory, I create a dict like so: {'a.mov': SubclassObjectForm, 'b.mov': SubclassObjectForm } and I have a MainForm instance field that includes this dict. When I render the form, the jinja macro creates name and id attributes for the <label> and <input> fields as needed that include a prefix for the file in question.
For example <input name='targetObjectFilePath-movieA.mov' value='/full/path/to/file' type='hidden>.
When the form gets POSTed, it's just a matter of pulling the relevant bits of data in my view.
I hope this helps someone! It might not be elegant or 'pro' but it gets my task done. Next step... styling!
forms.py
class ObjectForm(FlaskForm):
"""
Fields for an individual object
"""
targetFilePath = wtforms.HiddenField('targetObjectPath')
targetBase = wtforms.HiddenField('targetObjectBasename')
option1 = wtforms.BooleanField('Option1?')
option2 = wtforms.BooleanField("Option2?")
# etc.
class IngestForm(FlaskForm):
'''
General input form
'''
choicesDict = wtforms.HiddenField(default='no choices')
submit = wtforms.SubmitField('Submit')
routes.py
[import relevant stuff]
#app.route('/index',methods=['GET','POST'])
def index():
# GET A DICT OF PATHS AND BASENAMES TO PROCESS {'fullPath':'basename'}
objects = listObjects.list_objects()
class OneObject(forms.ObjectForm):
pass
choices = {}
for path,_object in objects.items():
choices[path] = OneObject(targetPath=path,targetBase=_object)
form = forms.IngestForm()
form.choicesDict = choices
return render_template(
'index.html',title='Index',form=form
)
#app.route('/status',methods=['GET','POST'])
def status():
data = request.form.to_dict(flat=False)
# DO THE STUFF
index.html
{% import "macros.html" as macros %}
{% extends "base.html" %}
{% block content %}
<h1>Here are files to ingest:</h1>
<form action="{{ url_for('status') }}" method='POST'>
{{ form.hidden_tag() }}
{{ form.csrf_token }}
{# iterate over the form dict with subforms included: #}
{% for item,vals in form.choicesDict.items() %}
<div>
{# iterate over subform fields and include target file basename #}
{% for field in vals %}
{{macros.render_field(field,vals.targetBase.data)}}
{% endfor %}
</div>
{%endfor%}
{{form.submit()}}
</form>
{% endblock %}
macros.html
{% macro render_field(field, uniqueName) %}
<p>
{# I only care about 2 kinds of data: filenames/paths and boolean options. #}
{% if field.type == 'BooleanField' %}
<label for="{{ field.id }}-{{ uniqueName }}">{{ field.label.text }}</label>
<input name="{{ field.id }}-{{ uniqueName }}" id="{{ field.id }}-{{ uniqueName }}" type="checkbox" default=""></input>
{# render hidden input for full path for later processing #}
{% elif field.name == 'targetPath' %}
<input name="{{ field.id }}-{{ uniqueName }}" id="{{ field.id }}-{{ uniqueName }}" type="hidden" value="{{ field.data }}"/>
{# use basename for local id purposes and display value as label for users #}
{% elif field.name == 'targetBase' %}
<label for="{{ field.id }}-{{ uniqueName }}">{{ uniqueName }}</label>
<input name="{{ field.id }}-{{ uniqueName }}" id="{{ field.id }}-{{ uniqueName }}" type="hidden" value="{{ field.data }}"/>
{% endif %}
</p>
{% endmacro %}

Display form input with Django

So basically I want to make a simple form I can enter text and the after I hit submit, see the text.
Here is my forms.py:
class Search(forms.Form):
search = forms.CharField()
Here is my views.py:
def search(request):
context = RequestContext(request)
if request.method == 'POST':
search = Search(data=request.POST)
if search.is_valid():
ticker = search.save()
ticker.save()
success = True
else:
print search.errors
else:
search = Search()
return render_to_response('ui/search.html', {"search":search}, context)
Here is the html form that you use to type in (I'm using bootstrap for styling purposes):
<form class="navbar-form navbar-right" role="search" action="/search/" method="post" name="tick">
{% csrf_token %}
<div class="form-group">
<input type="text" class="form-control" placeholder="Enter stock symbol">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
And finally, I want the text entered in the form to be displayed on "search.html" which looks like this currently:
{% extends 'ui/base.html' %}
{% block title %} search {% endblock %}
{% block body_block %}
<br>
<p>test</p>
{{ form.search.data }} <!--I'm pretty sure this is not correct -->
{% endblock %}
Anyone know how I can do this? Thanks.
Your form name is search.
To render the value with modern django, you need to call the value method of the field, therefore your template should look like the following:
{{ search.search.value }}
Your template is wrong, as you suspect.
It is looking for a context variable named "form", but you have given it a context dictionary with a key named "search".
Also, "data" is the argument that you use to build up your Search object (correctly), but when you want to extract the user's input from it, you should use the field names instead, and you need to call value() on them in order to get the bound value. So, to get the contents of the text field called search, you should use search.search.value.
Try changing the line
{{ form.search.data }}
to
{{ search.search.value }}

How to render my TextArea with WTForms?

To render my textareafield with a specified number of columns and rows with WTForms, how do I set the number of columns and rows? I followed the instructions from this question but it didn't work:
How to specify rows and columns of a <textarea > tag using wtforms
I tried adding a widget but it didn't work:
class AForm(Form):
name = TextField('Name', [validators.Length(min=4)])
title = TextField('Title', [validators.Length(min=4)])
text = TextAreaField('Text', widget=TextArea(row=70, cols=11))
phonenumber = TextField('Phone number')
phonenumberhide = BooleanField('Display phone number on site')
price = TextField('Price')
password = PasswordField('Password')
email = TextField('Email', [
validators.Length(min=6, message=_('Little short for an email address?')),
validators.Email(message=_('That\'s not a valid email address.'))
])
TypeError: object.new() takes no parameters
Very old question, but since the WTF-Form documentation isn't clear I'm posting my working example. OP, hope you are not still working on this. :-)
form
from flask_wtf import Form
from wtforms.fields import StringField
from wtforms.widgets import TextArea
class PostForm(Form):
title = StringField(u'title', validators=[DataRequired()])
body = StringField(u'Text', widget=TextArea())
template
{% extends "base.html" %}
{% block title %}Create Post{% endblock %}
{% block content %}
<H3>Create/Edit Post</H3>
<form action="" method=post>
{{form.hidden_tag()}}
<dl>
<dt>Title:
<dd>{{ form.title }}
<dt>Post:
<dd>{{ form.body(cols="35", rows="20") }}}
</dl>
<p>
<input type=submit value="Publish">
</form>
{% endblock %}
There is no need to update the template for this issue. You can set the rows and cols in the definition of TextAreaField. Here is the sample: \
class AForm(Form):
text = TextAreaField('Text', render_kw={"rows": 70, "cols": 11})
For render_kw, if provided, a dictionary which provides default keywords will be given to the widget at render time.
TextArea field can be also implemented without any widgets:
forms.py
from wtforms import Form, TextField, TextAreaField
class ContactForm(Form):
name = TextField('Name')
email = TextField('Email Address')
body = TextAreaField('Message Body')
template.html
...
<form method="POST" action="">
{{ form.csrf_token }}
{{ form.name.label }} {{ form.name(size=30) }} <br/>
{{ form.email.label }} {{ form.email(size=30) }} <br/>
{{ form.body.label }} {{ form.body(cols="35", rows="20") }} <br/>
<input type="submit" value="Submit"/>
</form>
...
I want to add here that the solutions above which suggest to use render_kw indeed works UNDER THE CONDITION that height for the text area IS NOT set.
so if you have a field:
temp = TextAreaField('temp', render_kw={'rows':20})
and in your HTML file you write:
{{ form.temp(class_='someclass' )}}
then in the CSS definition of someclass, height should not be set as this will conflict with your rows settings and apparently height has precedence above rows.

Categories