How to get WTForms to require entry into a RadioButton field? - python

I'm creating a web form using Flask and Flask-WTF. The form is dynamic, with the fields determined by the database. Some questions in the database specify a radio button field, while others specify a single word.
I need to require that the radio button fields have a selection. Using wtforms.validators.DataRequired works perfectly for the string fields, but not the radio fields.
...
if question.category == 'word':
field = StringField(question.question, validators=[DataRequired()])
elif question.category == 'likert':
choices = [('1', 'Strongly Agree'), ('2', ...
field = RadioField(question.question, choices=choices,
validators=[DataRequired()])
setattr(FlaskForm, str(question.id), field)
stator(FlaskForm, 'submit', SubmitField('Submit))
form = FlaskForm()
....
Leaving any string field blank results in an error message when submitted. But nothing happens if a radio field is left without a selection.
How can I correct this?

I still don't know why DataRequired (and InputRequired) don't properly work with radio fields, but here's how I solved it.
I subclassed FlaskForm as follows:
class SurveyForm(FlaskForm):
class Meta:
def render_field(self, field, render_kw):
render_kw.setdefault('required', True)
return super().render_field(field, render_kw)
Using adding the dynamic fields to an instance of this subclass then made even the radio fields require an input.

Related

Validation on dynamically sized FieldList in WTForms

I am creating a quiz webapp using Flask and WTForms in which the user is shown a quiz which they can answer. The questions for the quiz come from a DB and hence the form to let the users answer the quiz is dynamic. Here is how the form class looks like:
class QuizForm(FlaskForm):
answers = FieldList(RadioField('Correct Answer', choices=CHOICES,
validators=[DataRequired()]),
min_entries=0)
submit = SubmitField('Submit your answers')
Before I serve the form, I append entries to the field list as follows:
questions = Question.query.all()
form = QuizForm()
for i, question in enumerate(questions):
form.answers.append_entry()
choices = [('1', question.option_a),
('2', question.option_b),
('3', question.option_c),
('4', question.option_d)]
form.answers.entries[i].choices = choices
This works well and creates the form as I would expect. But, I am not able to get validation to work where I want to make sure the user has answered all questions. This is how I am currently validating the form:
questions = Question.query.all()
form = QuizForm(request.form)
if request.method == 'POST':
if form.validate() and len(form.answers.entries) == len(questions):
# use form data
The problems I have are:
I would ideally want the validation to work just using form.validate().
In the current approach, the values for field.errors are not set, and thus I can't show the user a message that a field is required right below the radio field containing the options for the question (using WTForms validation).
The full code can be seen at github.
What is a good way to implement validation for this use case?
As commented further info in questions: stackoverflow.com/questions/50882720 and stackoverflow.com/questions/51048153
Personally I would use the dynamic form method to assign a unique name to each RadioField required so that the result would be similar to, for example if the form was statically created:
class WebForm(FlaskForm):
q_1 = RadioField('Q 1', validators=[InputRequired()], ...)
q_2 = RadioField('Q 2', validators=[InputRequired()], ...)
Then the validators will definitely work as requested.
Failing that I suppose you could just write your own validator over your FieldList named answers. Here is an example of a custom validator catching an input field supposed to capture a full name and it breaks if it doesn't detect a space between names:
from wtforms.validators import ValidationError
class WebForm(FlaskForm):
fullname = StringField('Input full name', validators=[])
def validate_fullname(self, field):
if ' ' not in field.data:
raise ValidationError('Please give first and last name')
If you write your own validator on the FieldList you can perhaps loop through and check each has some data, but the more painful approach (in my opinion) is why I stated my preferred way of doing this.

Invalid SQLFORM submit generates ticket instead of showing errors on form

Here is my db.py:
db.define_table('antenna_details',
Field('antenna_name',required=True),
Field('model_name',required=True),
Field('project_name',required=True),
Field('frequency_band',required=True),
Field('polarization',required=True),
Field('aperture_size',required=True),
Field('fixer_availability',required=True),
Field('weight',required=True),
Field('material',required=True),
Field('email_id',required=True,unique=True,requires=[IS_NOT_IN_DB]),
Field('subject',type='text',required=True),
Field('attached',type='upload', label="""
Antenna/feed Geometry
Electrical specification
Attach Simulated data in predicted form
""")
)
db.antenna_details.email_id.requires=[IS_EMAIL(),IS_NOT_EMPTY()]
db.antenna_details.attached.requires=IS_NOT_EMPTY()
db.antenna_details.subject.rquires=IS_NOT_EMPTY()
db.antenna_details.material.requires=IS_NOT_EMPTY()
db.antenna_details.weight.requires=IS_NOT_EMPTY()
db.antenna_details.fixer_availability.requires=IS_NOT_EMPTY()
db.antenna_details.aperture_size.requires=IS_NOT_EMPTY()
db.antenna_details.polarization.requires=IS_NOT_EMPTY()
db.antenna_details.frequency_band.requires=IS_NOT_EMPTY()
db.antenna_details.project_name.requires=IS_NOT_EMPTY()
db.antenna_details.model_name.requires=IS_NOT_EMPTY()
And Here is my default.py index action:
def index():
"""
example action using the internationalization operator T and flash
rendered by views/default/index.html or views/generic.html
if you need a simple wiki simply replace the two lines below with:
return auth.wiki()
"""
# response.flash = T("Hello World")
# return dict(message=T('Welcome to web2py!'))
form = SQLFORM(db.antenna_details).process()
if form.process().accepted:
response.flash = 'your data is posted'
return dict(form=form)
Now when I hit http://127.0.0.1:8000/Test/default, I get the form and when I submit it with an email id that already exists in the database, I get a ticket that says this:
<class 'sqlite3.IntegrityError'> column email_id is not unique
There are other constraints as well in the model(specifically IS_NOT_EMPTY()) but when this constraint is violated, the app simply redirects user back to the form and shows error in red. But for the duplicate email, it throws a ticket.
Why is it happening and what needs to be done so that on violation of IS_NOT_IN_DB constraint on email, it should redirect back to the form and show error in red near the email field similar to what happens with violation of IS_NOT_EMPTY constraint?
Field('email_id',required=True,unique=True,requires=[IS_NOT_IN_DB]),
...
db.antenna_details.email_id.requires=[IS_EMAIL(),IS_NOT_EMPTY()]
There are two problems. First, your IS_NOT_IN_DB validator is incorrect -- it should be something like IS_NOT_IN_DB(db, 'antenna_details.email_id').
Second, after you set the requires attribute of the email_id field to IS_NOT_IN_DB in the field definition, you then completely overwrite it with new validators, which do not include IS_NOT_IN_DB. You should either append the additional validators or define them all in once place. (For that matter, you can move all of your validator assignments into the field definitions rather than putting them all after the table definition.)

Disable all the options except the default - ChoiceField Django Forms

So basically here is what I am trying to do:
I would like to have a way to be able to make some of the fields readonly depending on the user.
So far it's easy but here comes the problem, I need to make some select input read only and well...they don't work well with read only. The thing is the user is still able to play with it even though the new value is not submitted. I want the select field to be disabled but if I do that, the value is not sent via the POST and there is a problem with the form valid method.
So after reading around, I understand that the only way is to disable all the fields except the one that is selected by default?
How is it possible
Here is what I have so far:
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
super(TeamForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance and instance.pk is None:
self.fields['division'].initial = 1
user_role = self.user.memberaccount.get_role()
if user_role != 'admin':
And here is whhere I want to disable the choices....
Thanks a lot for your help,
Ara
I see two possible choices here:
create a custom widget which will show the disabled select alongside a hidden input containing the value to submit https://docs.djangoproject.com/en/dev/ref/forms/widgets/#customizing-widget-instances
set the field as required=False and create a clean_division() method inside your form to populate the field if it hasn't been submitted: https://docs.djangoproject.com/en/dev/ref/forms/validation/#cleaning-a-specific-field-attribute

Django autocomplete_light and cities_light - invalid choice

I have a model with a location field that is mapped to cities_light.city and I'm using an autocomplete field that allows users to type in their city and have it autocompleted to the correct/valid location model instance.
class Profile(models.Model):
location = models.ForeignKey(City, blank=True, null=True)
class ProfileForm(ModelForm):
class Meta:
model = Profile
fields = ('location')
widgets = {
'location': autocomplete_light.TextWidget(CityAutocomplete, autocomplete_js_attributes={'placeholder':'City, Country', 'minimum_characters':4})
}
The form field works exactly as advertised and a list of autocomplete options are shown. However when I save the form/model I get a validation error which seems to be caused by the field not being translated into the primary key of the City model instance.
Select a valid choice. That choice is not one of the available choices.
I'm guessing I need to extend the AutocompleteModelBase like the CityAutocomplete implemented below but I'm not sure how and I've been unable to find a working example.
class CityAutocomplete(autocomplete_light.AutocompleteModelBase):
search_fields = ('search_names',)
https://github.com/yourlabs/django-cities-light/blob/master/cities_light/contrib/autocompletes.py
Thank for any assistance and I'm sorry if my question is poorly formatted.
Your problem is not specific to django-autocomplete-light. What you're doing has no chance to work and here's why:
the form field for a ForeignKey like location is a ModelChoiceField by default,
ModelChoiceField accepts values which are pks of models in ModelChoiceField.queryset, which is TheModel.objects.all() by default,
the TextWidget widget is a TextInput,
a TextInput widget is just an <input type="text" />,
the value of a <input type="text" /> is directly sent to the server on form submission.
As a result, selecting a couple of cities like "Lisboa" and "Madrid" with a text widget will look like::
<input type="text" value="Lisboa, Madrid" name="location" />
Which means that the form will post {'location': 'Lisboa, Madrid'}. While this is good for a CharField, it won't work for a ModelMultipleChoiceField which would expect something like {'location': [3,5]} where 3 would be the pk of Lisboa and 5 the pk of Madrid.
In the same fashion, a ModelChoiceField would expect {'location': 3} which autocomplete_light.ChoiceWidget is able to do.
To fix this, use a ChoiceWidget instead of a TextWidget. I have clarified this in the tutorial I hope it is better now.

Django add single fields to Model form using AJAX call

I have a Model form. The form contains a button "Add more Field". Clicking this button sends an AJAX call which should add a textfield to the form. Any number of "textfields" can be added.
I am not sure if Django-Formsets is the correct way to do it as I need to store the data of "extra added fields" in the same Model Form's Table in the database.
How can I achieve this ?
I did something similar to this recently, and my solution was to subclass the form dynamically, providing only the field needed, and rendering just that field:
from forms import SomeModelForm
from models import SomeModel
def view_name(request,pk,field):
SomeModelFieldForm(SomeModelForm):
class Meta(SomeModelForm.Meta):
fields = (field,)
inst = SomeModel.objects.get(pk=pk)
form = SomeModelFieldForm(instance=inst)
#the rest of your view goes here...
#...send only the form's field:
ctx = {'field': form[field]}
return render_to_response("template",ctx)
This takes advantage of your original form's specifics - i.e., if you have specially defined widgets, or other restrictions, or something. It then restricts the entire form to a single field. This is to allow the validation of a single field on an existing model.
However, you don't want to send the entire form, you only want to send the single field. form[field] is a django.forms.forms.BoundField that represents the specific field you pass in.
For example, if you were working with the User model from django.contrib.auth.models, and you created a form for a specifi user, in idle calling form["username"] would return:
<django.forms.forms.BoundField object at 0x01354750>
and calling print form["username"] would print:
<input id="id_username" type="text" name="username" value="name_of_user" maxlength="30" />

Categories