flask wtforms dynamically show form fields based on select field - python

I have a form to kick off generation of a few different types of files, and each type has different input parameters. I'd like to use a generic form, and based on the type selected, show only the relevant parameters.
The python code is
from flask_wtf import FlaskForm
from wtforms import SelectField, DecimalField, IntegerField
from wtforms.validators import Optional, InputRequired
from flask import Blueprint, redirect, render_template, url_for
bp = Blueprint("routes", __name__)
class genericForm(FlaskForm):
# populate the file types, with default option empty
fileType = SelectField("File type",
choices=[(None, ""),
("file1", "file1"),
("file2", "file2"),
("file3", "file3")],
validators=[DataRequired()])
required1 = IntegerField("Required 1", validators=[InputRequired()])
required2 = IntegerField("Required 2", validators=[InputRequired()])
optional1 = DecimalField("Optional 1", validators=[Optional()])
optional2 = DecimalField("Optional 2", validators=[Optional()])
optional3 = DecimalField("Optional 3", validators=[Optional()])
optional4 = DecimalField("Optional 4", validators=[Optional()])
#bp.route("/index", methods=["GET", "POST"])
def index():
"""The index"""
form = genericForm()
if form.validate_on_submit():
identifier = createFile(form)
return redirect(url_for("routes.files", info=identifier))
# which file types require which form fields
fileFields = {"file1": "optional1", "optional3",
"file2": "optional2", "optional3",
"file4": "optional1", "optional4"}
return render_template("index.html", form=form, fileFields=fileFields)
and the jinja template I currently have is
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}File Creation{% endblock %}</h1>
{% endblock %}
{% block content %}
<form action="" method="post" novalidate>
{{ form.hidden_tag() }}
<p>
{% for field in form %}
{% if field.flags.required %}
{{ field.label }} {{ field }}<br />
{% endif %}
{% endfor %}
</p>
<p>{{ form.submit() }}</p>
</form>
{% endblock %}
But what I'm trying to do is determine whether to hide a field based on whether it's required or if it's in the list for the selected fileType, and update that when the selected fileType changes

Related

Flask-WTForms form.validate() fails on dynamic choices

I'm creating a survey application that displays the survey question and choices and allows the user to pick a choice through the Flask-WTForms package. The form uses a RadioField and seems to fail form.validate() when populating the choices attribute dynamically.
When I manually enter in the choices as such:
class SurveyAnswerForm(FlaskForm):
answers = RadioField('Answers',
coerce=str,
choices=[('18-25', '18-25'), ('26-35', '26-35')])
form.validate() returns True and there are no errors in form.error.
When I decide to populate the choices attribute dynamically (see below), form.validate() returns False and form.error returns:
{'answers': ['Not a valid choice']}.
I've been working at this for hours and am not sure why form.validate() returns False.
forms.py:
from flask_wtf import FlaskForm
from wtforms import RadioField
class SurveyAnswerForm(FlaskForm):
answers = RadioField('Answers',
coerce=str,
choices=[])
app.py:
#app.route('/survey/<int:survey_id>/questions', methods=['GET', 'POST'])
def survey_questions(survey_id):
survey = Survey.query.filter_by(id=survey_id).first()
page = request.args.get('page', 1, type=int)
questions = SurveyQuestion.query.filter_by(survey_id=survey_id)\
.order_by(SurveyQuestion.id)\
.paginate(page, 1, True)
for question in questions.items:
question_id = question.id
choices = QuestionChoices.query\
.join(SurveyQuestion,
and_(QuestionChoices.question_id==question_id,
SurveyQuestion.survey_id==survey_id)).all()
form = SurveyAnswerForm(request.form)
form.answers.choices = [(choice.choice, choice.choice)\
for choice in choices]
if request.method =='POST' and form.validate():
print('Successful POST')
next_url = url_for('survey_questions', survey_id=survey.id,
page=questions.next_num)\
if questions.has_next else None
prev_url = url_for('survey_questions', survey_id=survey.id,
page=questions.prev_num)\
if questions.has_prev else None
return render_template('survey_question.html',
survey=survey,
questions=questions.items,
choices=choices,
form=form,
next_url=next_url, prev_url=prev_url)
survey_question.html:
{% extends "layout.html" %}
{% block body %}
<h2>{{ survey.survey_title }}</h2>
{% for question in questions %}
<h3>{{ question.question }}</h3>
{% endfor %}
<form action="{{ next_url }}" method="POST">
{{ form.csrf_token }}
{{ form.answers(name='answer') }}
{% if prev_url %}
Back
{% endif %}
{% if next_url %}
<input type="submit" value="Continue">
{% else %}
Finish
{% endif %}
</form>
{% endblock %}
The problem was submitting a POST request with pagination. if the current link is /survey/1/question?page=2 the form will submit to /submit/1/question?page=3. To remedy this, I just created a separate route for submission and handled logic there.

Creating a Quote form in Django

i am creating a django powered website. Specifically, a courier website. I need to create an application that serves as a quoting app. The user will type in the dimensions of the package into a form and after submitting the form, a price/quote will be returned , based on the dimensions inputted.
I have done this so far
(views.py)
from django.shortcuts import render, redirect
from quote.forms import QuoteForm
def quoting(request):
if request.method == 'GET':
form = QuoteForm()
else:
form = QuoteForm(request.POST)
if form.is_valid():
Length = form.cleaned_data['Length']
Breadth = form.cleaned_data['Breadth']
Height = form.cleaned_data['Height']
return redirect('thanks')
return render(request, "quote/quote.html", {'form': form})
(forms.py)
from django import forms
class QuoteForm(forms.Form):
Length = forms.Integer()
Breadth = forms.Integer()
Height= forms.Integer()
(quote.html)
{% extends "shop/base.html" %}
{% block content %}
<form method="post">
{% csrf_token %}
{{ form }}
<div class="form-actions">
<button type="submit">Send</button>
</div>
</form>
{% endblock %}
Then i am aware i am lacking an html that would display the answer. I am not sure how to do this.
The price is determined by:
price= Shipping weight X distance
shipping weight= (length X breadth X height) / 5000
Thanks in advance :)
You have redirected to a 'thanks' page after the input is received. You should not have to return anything at this point.
After you have the input of length, breadth and height. You can calculate the price by doing: (Length * Breadth * Height )/ 5000.
This can be stored into a variable 'total_price'. Then, add 'total_price' to your context when rendering.
Finally, in the HTML, you can add a Django template tag like
{% if total_price %}
{{ total_price }}
{{ else }}
{{ form }}
Hope this helps!
When i submit the form it returns to just the form with my inputs
(views.py)
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render, redirect
from quote.forms import QuoteForm
def quoting(request):
if request.method == 'GET':
form = QuoteForm()
else:
form = QuoteForm(request.POST)
if form.is_valid():
Length = form.cleaned_data['Length']
Breadth = form.cleaned_data['Breadth']
Height = form.cleaned_data['Height']
totalprice=((Length*Breadth*Height)/5000)
return render(request, "quote/quote.html", {'form': form})
def answer(request):
return render(request,"quote/out.html")
(quote.html)
{% extends "shop/base.html" %}
{% block content %}
<form method="post">
{% csrf_token %}
{{ form }}
<div class="form-actions">
<button type="submit" action='/quote/out.html/'>Send</button>
</div>
</form>
{% endblock %}
(out.html)
{% extends "shop/base.html" %}
{% block content %}
{% if totalprice %}
{{ totalprice }}
{{ else }}
{{form}}
{% endblock %}
(forms.py)
from django import forms
class ContactForm(forms.Form):
Length = forms.IntegerField(required=True)
Breadth = forms.IntegerField(required=True)
Height = forms.IntegerField()
(urls.py ( the app))
from django.conf.urls import patterns, url
from django.views.generic import TemplateView
url(r'^quoting/$',
'quote.views.quoting',
name='quoting'
),
url(r'^answer/$',
'quote.views.answer',
name='answer'
)

Why validation not working on form request?

I am learning flask and made a small application. Now I am trying to learn form. I used a simple code to validate a name request and it should give error when the field remains empty. But it isn't giving one.
Main file :
from flask import Flask, render_template
from flask.ext.moment import Moment
from flask.ext.wtf import Form
from wtforms import StringField, SubmitField, validators
import requests
import json
from datetime import datetime
app = Flask(__name__)
app.config['SECRET_KEY'] = 'abcd'
moment = Moment(app)
class Nameform(Form):
name = StringField("whats your name?", [validators.Required()])
submit = SubmitField('submit')
#app.route('/')
#app.route('/index')
def index():
api_call = requests.get('https://api.stackexchange.com/2.2/users/moderators?order=desc&sort=reputation&site=stackoverflow') # api call to stack for user with highest scores in des order
var_1 = json.loads(api_call.text)
var_2 = [{'link': value['link'], 'name': value['display_name'], 'user_id': value['user_id']} for value in var_1['items']]
return render_template('index.html', posts=var_2, current_time=datetime.utcnow())
#app.route('/user/<id>/<user_name>')
def user(id, user_name):
print id
api_call = requests.get('https://api.stackexchange.com//2.2/users/'+id+'/reputation?site=stackoverflow') # api call for reputation of click user
var_1 = json.loads(api_call.text)
return render_template('reputation.html', result=var_1, user_name=user_name)
#app.route('/test', methods=['GET', 'POST'])
def user_form():
name = None
form = Nameform()
if form.validate_on_submit():
name = form.name.data
form.name.data = ''
return render_template('test_form.html', form=form, name=name)
if __name__ == '__main__':
app.run(debug=True)
Template for rendering:
<div class="page-header">
<h1>Hello, {% if name!= None %}{{ name }}{% else %}Stranger{% endif %}!</h1>
</div>
<form method=post>
{{ form.name.label }} {{ form.name() }}
{{ form.submit() }}
</form>
Why it is not throwing any error? when the field remains empty
You can render the error messages using form.errors. Note that you're also missing your CSRF token, which is required for validation since you didn't disable WTF_CSRF_ENABLED, so I've added {{ form.csrf_token }}. See CSRF Protection.
<div class="page-header">
<h1>Hello, {% if name!= None %}{{ name }}{% else %}Stranger{% endif %}!</h1>
</div>
{% for field in form.errors %}
{% for error in form.errors[field] %}
<div class="error">{{ error }}</div>
{% endfor %}
{% endfor %}
<form method=post>
{{ form.csrf_token }}
{{ form.name.label }} {{ form.name() }}
{{ form.submit() }}
</form>
I think you have not included the novalidate attribute with form.
The novalidate attribute is used to tell the web browser to not apply validation to the fields in this form, which effectively leaves this task to the Flask application running in the server.
For Sample code

Simple dynamic forms with Flask/Flask-WTF

I need to build a simple form with a number of checkboxes. The problem is that I need the checkboxes to come from a csv file which would look like this:
data_so.csv
Name
A. Blabla
U. Blublu
I. Blibli
O. Bloblo
The form, right now, with some hard coded checkboxes, looks like this:
Instead of "Mr. 1", I'd need to have "A. Blabla", instead of "Ms. 2", I'd want "U. Blublu", etc. and instead of 3 checkboxes, I'd need 4, the number of entries in my csv file.
Here are my Flask files:
route_so.py
from flask import Flask, render_template, request, flash
from forms_so import ContactForm
import csv
app = Flask(__name__)
app.secret_key = 'development key'
#app.route('/', methods=['GET', 'POST'])
def home():
form = ContactForm()
if request.method == 'POST':
if form.validate() == False:
flash('All fields are required.')
return render_template('home_so.html', form=form)
else:
print(form.node_1.data,form.node_2.data,form.node_3.data)
return render_template('home_so.html', success=True)
elif request.method == 'GET':
return render_template('home_so.html', form=form)
if __name__ == '__main__':
app.run(debug=True)
form_so.py
from flask.ext.wtf import Form
import csv
from wtforms import TextField, RadioField, TextAreaField, SubmitField, validators, BooleanField
class ContactForm(Form):
# my attempt to force the creation of dynamic global variables
with open('/data_so.csv', 'rb') as f:
reader = csv.reader(f)
r = list(reader)
nodes = {}
for i in range(1,len(r)):
globals()[''.join("node_"+str(i))] = BooleanField(r[i][0])
# end of my attempt to force the creation of dynamic global variables
node_1 = BooleanField("Mr. 1")
node_2 = BooleanField("Ms. 2")
node_3 = BooleanField("Dr. 3")
# this needs to be dynamically set instead
submit = SubmitField("Send")
So I tried and created dynamic variables (in a dirty, hacky way). The problem now is that I don't know how to make the home_so.html work with an undifined number of variables...
home_so.html
{% extends "layout_so.html" %}
{% block content %}
{% if success %}
<p>Thank you for filling up our survey. We'll get back to you shortly.</p>
{% else %}
<form action="{{ url_for('home') }}" method=post>
{{ form.hidden_tag() }}
<h2>List of check boxes dynamically built from local csv file</h2>
#this needs to be dynamically set
{{ form.node_1.label }}
{{ form.node_1 }}
{{ form.node_2.label }}
{{ form.node_2 }}
{{ form.node_3.label }}
{{ form.node_3 }}
{{ form.submit }}
</form>
{% endif %}
{% endblock %}
Is there a way to accomplish this sort of things with a simple csv file? If not, what's the usual way to go about dynamically producing a form as it loads client-side?
{% for node in node_list_from_app %}
<p class="field"><label><input type="checkbox" name="node" value="{{ node }}"> {{ node }}</label></p>
{% endfor %}

Flask-WTF form posts successfully but validation never occurs

I have been struggling around the WTF forms for quite a while now. But this error, never seems to go away. I When ever I try to run this code the form never validates
Views :
#bundle.route('/content/add/', methods=['GET', 'POST'])
#bundle.route('/content/add', methods=['GET', 'POST'])
#bundle.route('/content/edit/<posturl>/', methods=['GET', 'POST'])
#bundle.route('/content/edit/<posturl>', methods=['GET', 'POST'])
#fas_login_required
def addcontent(posturl=None):
form = CreateContent()
form_action = url_for('content.addcontent')
if posturl is not None:
content = Content.query.filter_by(slug=posturl).first_or_404()
form = CreateContent(obj=content)
if form.slug.data == posturl and request.method == 'POST' and form.validate():
form.populate_obj(content)
db.session.commit()
return redirect(url_for('content.addcontent',
posturl=posturl, updated="True"))
else:
if request.method == 'POST' and form.validate():
query = Content(form.title.data,
form.slug.data,
form.description.data,
form.media_added_ids.data,
form.active.data,
form.tags.data,
g.fas_user['username'],
form.type_content.data
)
try:
db.session.add(query)
db.session.commit()
# Duplicate entry
except Exception as e:
return str(e)
return redirect(url_for('content.addcontent',
posturl=form.slug.data, updated="True"))
else:
print "Please validate form"
return render_template('content/edit_content.html', form=form,
form_action=form_action, title="Create Content")
Form Class :
# -*- coding: utf-8 -*-
from flask.ext.wtf import Form
from wtforms import TextField, TextAreaField
from wtforms import BooleanField, SelectField, validators
from wtforms.validators import Required
__all__ = ['CreateContent']
class CreateContent(Form):
title = TextField(
'Title', [validators.Length(min=4, max=255)])
slug = TextField(
'Url-Slug', [validators.Length(min=4, max=255)])
description = TextAreaField('Content', [validators.Length(min=4)])
media_added_ids = TextField('media')
type_content = SelectField(u'Content Type',
[Required()],
choices=[('blog', 'Blog Post'),
('media', 'Lecture'),
('doc', 'Documentation')]
)
# Comma seprated media id's
active = BooleanField('Published')
tags = TextField('Tags', [Required()])
# Comma seprated tag id's
And my Template :
{% extends "base.html" %}
{% block title %}
{{ title }}
{% endblock %}
{% block content %}
{% from "_formhelpers.html" import render_field %}
<div id="Create Content">
<center><h3> {{ updated }} </h3></center>
<h2>{{ title }}</h2>
<form method="post" action="">
<fieldset>
<legend></legend>
{{ render_field(form.title) }}
{{ render_field(form.slug ) }}
{{ render_field(form.description ) }}
{{ render_field(form.media_added_ids)}}
{{ render_field(form.type_content) }}
{{ render_field(form.active) }}
{{ render_field(form.tags )}}
</fieldset>
<input type="submit" class="button" value="Save"/>
</form>
</div>
{% endblock %}
Any help will be highly apprieciated
If the CSFR tokens are activated in a flask application setting, a CSFR token is included in each form. If developer has activated the setting and not included it in the form template the flask WTF would automatically reject the request.
The solution to this problem was to the following tag :
{{form.hidden_tag()}}
Once added, a CSFR id is included in the request and sent to the views for validation by the WTForms.
If you haven't included this token no errors will appear in the form.errors dictionary. If you iterate over this dictonary no errors will be show, but the form.validate method will return false.
Flask-WTF adds the CSRF token automatically if it is activated in your Flask settings. If this setting is active and it isn't included in the form submission the submission will be rejected. The solution in this case is to add the hidden_tag field in the template so that it is included in the form submission.
{{form.hidden_tag()}}

Categories

Resources