Validation on dynamically sized FieldList in WTForms - python

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.

Related

Deleting FlaskForm Fields depending on userinput in other forms

I have 3 forms with Checkboxes to configure the desired form (Final_Form). After the user chooses the desired fields (in form1, form2 and form3), i want to delet all fields that are not required in the final form and render the final form. The reason for that is, that i have 3 Subkategories with around 12 possible values, in each form (form1-form3) the user can choose one ore more subkategories. The subcategories are standardized and are used to describe a clinical incident. The users wished to have the subcategories (1-3; form1-form3) seperated and always with an example (right-side of the screen in an anther bootstrap col).
The finalform is than a combination of the the subcategories that matches best to describe the clinical incident. All fields in the Final_Form are TextAreaFields. The Input for the TextAreaFields is stored in a sqlite-db.
Here is how i tried it:
app.py:
if request.method == 'POST' and form1.form1Submit.data:
OnePointOne = form1.OnePointOne.data
if not OnePointOne:
del Final_Form.OnePointOne
return render_template('Form2.html', form2 = form2)
if request.method == 'POST' and form2.form2Submit.data:
TwoPointTwo = form2.TwoPointTwo.data
if not TwoPointTwo:
del Final_Form.TwoPointTwo
return render_template('Form3.html', form3 = form3)
if request.method == 'POST' and form3.form3Submit.data:
ThreePointThree = form3.ThreePointThree.data
if not ThreePointThree:
del Final_Form.ThreePointThree
return render_template('Final.html', Final_Form = Final_Form)
forms.py:
class form1(FlaskForm):
OnePointOne = BooleanField('Effect')
form1Submit = SubmitField('Submit Category')
class form2(FlaskForm):
TwoPointTwo = BooleanField('Measure')
form2Submit = SubmitField('Submit Category')
class form3(FlaskForm):
ThreePointThree = BooleanField('Result')
form3Submit = SubmitField('Submit Category')
class Final_Form(FlaskForm):
OnePointOne = TextAreaField('Example Effect')
TwoPointTwo = TextAreaField('Example Measure')
ThreePointThree = TextAreaField('Example Result')
Final_FormSubmit = SubmitField('Submit incident')
The problem is, that the formfields of the Final_Form objects dont get deleted (only inside the if statements). I am very thankful for every hint or explanation.
As you are showing three separate pages, there are three separate requests.
You Final_Form object cannot be simply kept between these requests.
I don't fully understand why you configure your third form this way, it would be helpful to explain your use-case for better advice.
Without more information, I'm thinking of some ways to do this:
You make it one page/request, where you go from form to form using AJAX.
You make it one page with all forms, controlling visualisation with JS + CSS
You save your desired value somewhere
probably you can keep it in cookie (session object)
or in database, if that makes sense in your context
Also, please include whole code of this function - it's not clear how you create those forms you use.

Is it possible to use request.POST as a dictionary input?

In my django project, I collect membership data by HTML form and insert them into the database. There are the code samples:
models.py
class member(models.Model):
name = models.CharField(max_length=100,blank=True,null=True)
gender = models.CharField(max_length=10,blank=True,null=True)
profession = models.CharField(max_length=100,blank=True,null=True)
views.py:
def manage(request):
form_values = request.POST.copy()
form_values.pop('csrfmiddlewaretoken') # I don't need it.
add_member = member(**form_values)
add_member.save()
If HTML form input is: Rafi, Male, student
Database gets in list format: ['Rafi'], ['Male'], ['student']
How can I solve this?
You can make use of the .dict() [Django-doc] method here:
def manage(request):
form_values = request.POST.copy()
form_values.pop('csrfmiddlewaretoken')
add_member = member(**form_values.dict())
add_member.save()
If there are multiple values for the same key, it will take the last one.
That being said, it might be better to take a look at a ModelForm [Django-doc] to validate data and convert it to a model object. This basically does what you do here, except with proper validation, removing boilerplate code, and furthermore it will not use the other keys. If here a user would "forge" a POST request with extra key-value pairs, the server will raise a 500 error.

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

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.

flask-wtf editing a model using wtform Form constructor: pre-filling the form

I am reading the Flask Web Development book and came across this:
def edit_profile():
form = EditProfileForm()
if form.validate_on_submit():
current_user.name = form.name.data
current_user.location = form.location.data
current_user.about_me = form.about_me.data
db.session.add(user)
flash('Your profile has been updated.')
return redirect(url_for('.user', username=current_user.username))
form.name.data = current_user.name
form.location.data = current_user.location
form.about_me.data = current_user.about_me
return render_template('edit_profile.html', form=form)
Basically, when the form is not posted or doesn't validate, this copies over the data from the current user. Now reading up on wtforms, I read this about the init method on a form:
obj – If formdata is empty or not provided, this object is checked for attributes
matching form field names, which will be used for field values.
So I guess that means that we could write this (the sample below is my own):
def edit_post(post_id):
post = Post.query.get_or_404(post_id)
if current_user != post.author:
abort(403)
# Below is the line I am concerned about
form = PostForm(formdata=request.form, obj=post)
if form.validate_on_submit():
form.populate_obj(post)
db.session.commit()
return redirect(url_for('user', username=current_user.username))
return render_template('post_form.html', form=form)
I figure that this should fill the form instance from the database model on GET, and from POST data after post. Testing this, it seems to work..
Now my question: is this way of writing an edit view correct? Or should I copy everything over field by field, like in the book?
Loading in a POST MultiDict is certainly the accepted way to map key/value pairs to your WTForms instance. Even more so, if you are using the Flask-WTF extension, this is automatically done for you, it is one of the perks that this extension brings you.
If you would crack open the code of Flask-WTF you would see that it inherits the SecureForm class of WTForms and tries to load in the Werkzeug POST MultiDict (called formdata) by default (if it is present). So loading in your form in your view like this:
form = PostForm(obj=post)
Should be sufficient (if using Flask-WTF) to also fill the fields with POSTed data.
The way it is done in your book example is certainly not wrong, but creates a lot of unnecessary code and is error prone / redundant - one could forget to mention a field in the view that is declared in the WTForms instance.

Multiple Instances of a django app, does django support this

I have written a simple feedback application in django. It's not particulairly complex, basically it allows authenticated users to write a shot message with a subject line and submit that message via a form. I then allows who are in a selected group to view user submitted feedback. In the future I may add more functionality but for now it does what I want.
Here comes my question, the site I'm building has multiple places where I would like to use the feedback app, for example I have a "what do you think of the site?" kind of page at /dev/feedback/ I also have one for customer support feedback at "/support/feedback/" Currently I have just copied the code from my mysite.apps.dev.feedback over to mysite.apps.support.feedback.
The problem is that this has now created two separate copies of the same code. Despite having just written the app the two versions are already starting to diverge which is annoying. My question is simply how do I create multiple instances of the same app in a django site with distinct database models?
Some resources I've found related but not helpful are https://docs.djangoproject.com/en/dev/topics/http/urls/ and Reversing namespaced URLs in Django: multiple instances of the same app The first page does not offer much on the issue and the second page provides somewhat cludgey and impractical solutions that seem to be both unrelated and more work than their worth. Is there a proper way to implement multiple instances of the same django app?
Single model approach
I'd personally try to keep this as one app and have a view that can handle being posted from multiple locations / tag them appropriately.
As S.Lott says, this is the way to go. I am providing alternatives if you're curious about methods to keep your code in one place in other situations.
For example, you could add a category field to your model, set up a single url conf which accepts an argument in the URL such as /(?P<category>\w+/feedback/$ and have the view simply tag the feedback with the appropriate category.
class MyForm(forms.ModelForm):
class Meta:
model = Feedback
def my_view(request, category):
form = MyForm(request.POST or None)
if request.method == 'POST':
if form.is_valid():
feedback = form.save(commit=False)
feedback.category = category
feedback.save()
return http.HttpResponse("Thanks for posting!")
return render(request, "mytemplate.html", {'form': form})
# urls.py
(r'^(?P<category>\w+)/feedback/$', 'my_view')
# user can visit dev/feedback or support/feedback and the feedback will be tagged appropriately
Abstract base class
Another solution is to build an abstract base class, then create subclasses for your distinct tables. That should solve the issue with your code getting out of sync.
You'd have a single abstract model (which has no tables) from which your "real" models in your separate apps would be based on.
Dynamically generated views
If you must have separate models, you could potentially write a dynamically constructed view.
def view_generator(model_class):
class MyForm(forms.ModelForm):
class Meta:
model = model_class
def my_view(request):
form = MyForm(request.POST or None)
if request.method == 'POST':
if form.is_valid():
form.save()
return http.HttpResponse("Thanks for posting!")
return render(request, "mytemplate.html", {'form': form})
return my_view
# urls.py
from foo import view_generator
(r'^my_first_feedback_form', view_generator(Model1))
(r'^my_second_feedback_form', view_generator(Model2l))
how do I create multiple instances of the same app in a django site with distinct database models?
You shouldn't.
You simply use the feedback app model in the other two apps with a simple from feedback.models import Feedback.
Then your support app can create, retrieve, update and delete Feedback objects.
Your dev app, similarly, can create, retrieve, update and delete Feedback objects because it imported the model.
That's all that's required: import.
Thanks Yuji Tomita for a very thorough answer, my final solution is derived very closely from his suggestion, but is different enough that I thought I would post it as another option if someone else runs into the same situation that I am in.
Firstly in my mysite.apps.feedback.models file I put
class Feedback( models.Model ):
subject = models.TextField( max_length=100 )
body = models.TextField( max_length=100 )
# Some other stuff here...
# Finally I used the suggestion above and created a field which I
# use to label each entry as belonging to a specific instance of the app.
instance_name = models.TextField( max_length=20 )
In my mysite.apps.feedback.views file I put
def save_message( request, instance_name ):
if request.method == 'POST':
form = FeedbackFrom( request.POST )
if form.is_valid():
form.instance.instance_name = instance_name
form.save()
return render("feedback/thanks.html")
else:
return render("feedback/submit.html", {'form':form })
else:
return render("feedback/submit.html",{'form':FeedbackForm()})
#user_passes_test( is_staff )
def all_messages( request, instance_name ):
messages = Feedback.objects.filter( instance_name = instance_name )
return render("feedback/view_all.html",{'feedback':messages} )
In my mysite.apps.dev.urls file I put
url(r'^feedback/', include('mysite.apps.feedback.urls'),
{'instance_name':'dev'}),
In my mysite.apps.support.urls file I put
url(r'^feedback/', include('mysite.apps.feedback.urls'),
{'instance_name':'support'}),
This will separate feedback messages by app instance. Note that my actual code is more complex but this should be good enough for anyone with a similar problem to get a solution up and running pretty quickly. Hope this is useful to anyone in a similar situation. Thanks again to Yuji Tomita for the suggestions upon which this solution is based.

Categories