Flask and WTForms with custom validation - python

I am creating an online form with Python, to send an online form. The form consists of a mixture of free input fields, and standard options. What it does now is convert the input to a mail, and send it. That's great, but I would like to build in a functionality that checks the input first. I need the length of two different inputfields to be of the size. So if someone enters 4 products and only 3 quantities, I want it to return a warning that these amounts differ.
base.py:
from flask import *
from wtforms import *
import yagmail
yag = yagmail.SMTP('email', 'pass')
# App config.
DEBUG = True
app = Flask(__name__)
app.config.from_object(__name__)
app.config['SECRET_KEY'] = 'key'
class ReusableForm(Form):
naam = TextField('Name:', validators=[validators.required()])
#app.route("/", methods=['GET', 'POST'])
def index():
form = ReusableForm(request.form)
print form.errors
if request.method == 'POST':
# Process all input
naam=request.form['naam']
productcodes=request.form['productcodes']
productquantity=request.form['productquantity']
# Convert SKU & EAN input to list of entries
productcodes = [int(i) for i in productcodes.strip('{}').split('\n')]
productquantity = [int(i) for i in productquantity.strip('{}').split('\n')]
# tried this; didn't work
# if len(productcodes) != len(productquantity):
# flash('Unequal inputs')
if form.validate():
# Comment when form is validates
flash('Order succesvol: ' + naam)
(send mail)
else:
flash('Error: All the form fields are required. ')
return render_template('hello.html', form=form)
if __name__ == "__main__":
app.run()
hello.html:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>Form</title>
<link rel="stylesheet" media="screen" href ="static/css/bootstrap.min.css">
<link rel="stylesheet" href="static/css/bootstrap-theme.min.css">
<meta name="viewport" content = "width=device-width, initial-scale=1.0">
</head>
<body>
<div class="container">
<h2>Form</h2>
<form action="" method="post" role="form">
{{ form.csrf }}
<div class="form-group">
<label for="naam">Naam:</label>
<select name="naam" class="selectpicker form-control">
<option value="Jack">Jack</option>
<option value="John">John</option>
</select>
<br>
<label for="productcodes">SKU-codes:</label>
<textarea class="form-control" id="productcodes" name="productcodes"></textarea>
<br>
<textarea class="form-control" id="productquantity" name="productquantity"></textarea>
<br>
</div>
<button type="submit" class="btn btn-success">Send</button>
</form>
<br>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for message in messages %}
{% if "Error" not in message[1]: %}
<div class="alert alert-info">
<strong>Success! </strong> {{ message[1] }}
</div>
{% endif %}
{% if "Error" in message[1]: %}
<div class="alert alert-warning">
{{ message[1] }}
</div>
{% endif %}
{% endfor %}
{% endif %}
{% endwith %}
</div>
<br>
</div>
</div>
</body>
</html>
So what i would like to have is that upon the first click, the check is executed. If the size of the input of productcodes is smaller than the size of productquantity, the user has to edit the inputs. If not, the user has to confirm and is able to send the mail.
The lines that are commented out in base.py didn't work.
Thanks in advance!

Quick and Dirty
Based on the commented-out code, in your original attempt there was nothing stopping the form from validating and submitting. Your if len(productcodes) != len(productquantity) just flashed a message, which only shows on the next page load, but doesn't stop validation of the form. form.validate() can only validate the fields you tell it about.
Change the check to be something like:
if len(productcodes) != len(productquantity) and form.validate():
That way your basic check will bounce to the failed validation code path.
The "Right" Way
Keep in mind, even if that works, you're still basing your check on parsing text from a <textarea>, with little to enforce the format. Any unexpected input is likely to break it.
I'd strongly recommend you rethink your form to be a bit more in line with how WTForms and Flask work. Each form field should map directly to an attribute of your Form subclass. That way you can use the built-in validators, and not have to create your own. You should probably look at using something other than a raw <textarea> as well. It may be a bit more work up-front, but it will make it a lot easier for you and your users down the road.

Related

How use a conditional statement in a Flask app?

What I am trying to do is to trigger the function get_lyrics() when the Submit button is clicked. That action might run successfully which then triggers another action, or it might fail showing instead 'API calls reached. Try again later.'
However, as soon as I load the page, the text 'API calls reached. Try again later.' is already there whereas it should only appear when the function that is triggered fails.
This is my app.py:
import os
import json
from flask import Flask, render_template, request, session
from src.get_lyrics import get_lyrics
app = Flask(__name__)
#app.route('/')
def web_page():
return render_template('simple.html')
#app.route('/artist', methods=['POST'])
def insert_text():
if request.method=='POST':
artist_name = request.form.get("artist")
number_songs = request.form.get("number")
try:
titles, lyrics, no_of_songs = get_lyrics(artist = artist_name,
max_no_songs = number_songs,
path_to_txt="C:/Users/test_new.txt")
train = True
except:
train = False
titles= None
no_of_songs = None
return render_template('simple.html', titles=titles, no_songs=no_of_songs, train=train)
and this is the html bit:
<!DOCTYPE html>
<link href="static/simple.css" rel="stylesheet" type="text/css"</link>
<html>
<div class="image"></div>
<body>
<div class="gray-block-1">
<div class="block-1">
<h2>Instructions</h2>
</div>
<div class="block-2">
<form action = "./artist" method = "POST">
<input type="text" placeholder="Artist name" name="artist" />
<input type="text" placeholder="Number of songs" name="number" />
<div class="submit-button">
<button type = "submit"> Submit</button>
</div>
</form>
</div>
</div>
<div class="gray-block-2">
{% if train %}
{% for title in titles %}
<p>{{title}}</p
{% endfor %}
<form action = "./train" method = "POST">
<input type = "text" placeholder="Number of lines" name = "lines" />
<div class="submit-button-2">
<button type = "predict"> Predict</button>
</div>
</form>
{% else %}
API calls reached. Try again later.
{% endif %}
</div>
</body>
</html>
When simple.html is loaded for the first time with a GET request, the variable train is not defined. if train will therefore return False and the text in the else statement will be shown: “API calls reached. Try again later.”
You could set another variable in the backend.
#app.route('/artist', methods=['POST'])
def insert_text():
…
return render_template('simple.html', is_post=True, …)
And update the front end accordingly.
<div class="gray-block-2">
{% if is_post %}
{% if train %}
…
{% else %}
API calls reached. Try again later.
{% endif %}
{% endif %}
</div>
There is probably a better way. I'm just trying to point you in the right direction.

How to convert user form input to integer or float without receiving an error?

I am creating a foreign exchange currency converter using using Python, Flask, and forex_python.converter. Right now, when the user submits the currencies and amount to be converted on the home page, it directs them to a separate webpage just showing the values of their form inputs. Eventually this will show the converted Forex amount.
If the user inputs an incorrect forex code or a string as the amount, they would be directed back to the same page and error banners would appear using Flasks's flash messaging. I have been able to successfully create error banners for incorrect Foreign exchange code inputs, however I am struggling with how to create one for an invalid amount. Ideally if the "amount" that the user entered in were letters, blank, or symbols instead of a number, the banner would appear "Not a valid amount." Right now, the banner will always appear, but that the user amount is never converted to a float.
I tried this by converting the user entered amount into a float using float(), which worked successfully when the amount was an integer (or float), however if the input was anything else, I receive an error and my code stops. I've been stumped on this for a few hours now, so if anyone has any strategies on how to approach this, I would appreciate it.
My python code and 3 HTML pages are below:
from flask import Flask, request, render_template, flash, session, redirect, url_for
from flask_debugtoolbar import DebugToolbarExtension
from forex_python.converter import CurrencyRates
app = Flask(__name__)
app.config['SECRET_KEY'] = "secretkey"
# store all currency rates into variable as a dictionary
c = CurrencyRates()
fx_rates = c.get_rates('USD')
# home page
#app.route('/', methods=['POST', 'GET'])
def home():
return render_template('home.html')
# result page. User only arrives to result.html if inputs info correctly
#app.route('/result', methods=['POST', 'GET'])
def result():
# grab form information from user and change characters to uppercase
forex_from = (request.form.get('forex_from').upper())
forex_to = (request.form.get('forex_to').upper())
# Where I am running into issues.
# I have tried:
# before_amount = (request.form.get('amount').upper())
# amount = float(before_amount)
amount = request.form.get('amount')
print(amount)
# if input is invalid bring up banner error
if forex_from not in fx_rates :
flash(f"Not a valid code: {forex_from}")
if forex_to not in fx_rates :
flash(f"Not a valid code: {forex_to}")
if not isinstance(amount, float) :
flash("Not a valid amount.")
# if any of above errors occur, direct to home, else direct to result.html
if forex_to not in fx_rates or forex_from not in fx_rates or not isinstance(amount, float):
return redirect(url_for('home'))
else :
return render_template('result.html', forex_from=forex_from, forex_to=forex_to, amount=amount)
<!-- Base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha2/css/bootstrap.min.css" integrity="sha384-DhY6onE6f3zzKbjUPRc2hOzGAdEf4/Dz+WJwBvEYL/lkkIsI3ihufq9hk9K4lVoK" crossorigin="anonymous">
<title>Forex Converter!</title>
</head>
<body>
{% block content %}
{% endblock %}
</body>
</html>
<!-- home.html -->
{% extends "base.html" %}
{% block content %}
<h1>Forex Converter!</h1>
{% for msg in get_flashed_messages() %}
<div class="alert alert-danger" role="alert">
<h3>{{msg}}</h3>
</div>
{% endfor %}
<div class="alert alert-danger d-none" role="alert">
Not a valid amount.
</div>
<form action="/result" method="POST">
<div class="form-group">
<label for="forex_from">Converting from</label>
<input name="forex_from" type="text"><br>
</div>
<div class="form-group">
<label for="forex_to">Converting to</label>
<input name="forex_to" type="text">
<br>
</div>
<div class="form-group">
<label for="amount">Amount: </label>
<input name="amount" type="text"><br>
</div>
<button type="submit" class="btn btn-primary">Convert</button>
</form>
{% endblock %}
<!-- result.html -->
{% extends "base.html" %}
{% block content %}
<h1>Forex Converter!</h1>
<h3>forex_from: {{forex_from}}</h3>
<h3>forex_to: {{forex_to}}</h3>
<h3>amount: {{amount}}</h3>
<form action="/">
<button type="submit" class="btn btn-primary">Home</button>
</form>
{% endblock %}
You can use try and except
ask_again = True
while ask_again == True:
amount = request.form.get('amount')
try:
amount = float(amount)
ask_again = False
except:
print('Enter a number')
You can use try catch method to do this.
try:
val = int(input())
except valueError:
try:
val = float(input())
except valueError:
#show error message

How to handle errors in Django

I want to make my django app as user friendly as possible and I want to handle appropriate errors and have it push out an error message sort of like an alert in javascript. I want to do this when there's no file uploaded. So when the upload button is pressed and nothing have been uploaded there would be an alert message sent out.
My view, views.py:
def upload(request):
if "GET" == request.method:
return render(request, 'uploadpage/upload.html', {})
else:
excel_file = request.FILES["excel_file"]
# you may put validations here to check extension or file size
wb = openpyxl.load_workbook(excel_file)
# getting a particular sheet by name out of many sheets
worksheet = wb['Summary']
# iterating over the rows and
# getting value from each cell in row
seller_info = []
for cells in worksheet.iter_rows(min_col=2, max_col=2, min_row=1, max_row=5):
for cell in cells:
seller_info.append(str(cell.value))
return render(request, 'uploadpage/upload.html', {"excel_data": seller_info})
My template, uploadpage/upload.html:
<!DOCTYPE html>
<html>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<link rel="stylesheet" href="{% static 'css/upload.css' %}">
<head>
<div id='banner-container'>
<div id='banner'>
<h1 id='header'>MY APP</h1>
<i class="glyphicon glyphicon-cloud" style="font-size:60px;color:lightblue;text-shadow:2px 2px 4px #000000;"></i>
</div>
<div>
<body>
<div id='upload-container' >
<span><h1>Upload File !</h1></span>
<span><h2>Upload Here</h2></span>
<form method="post" enctype="multipart/form-data">
<div id='input'>
{% csrf_token %}
<input type="file" name="excel_file">
<div id='btn'><button type="submit">Upload File</button> </div>
</form>
<div>
</div>
</body>
{{ excel_data }}
</head>
</html>
Django provided us a message framework which allow you to attach messages and then you can render it on your template by using JavaScript or simply just use django template.
My favorite library to show message on my web application is toastr. You can go to the document page to see how you will integrate into your project.
On your views:
from django.contrib import messages
# ...
def upload(request):
if "GET" == request.method:
messages.error(request, "There's no file uploaded")
return render(request, 'uploadpage/upload.html', {})
# ...
Then on your template you can use it like so:
...
<head>
...
<link href="toastr.min.css" rel="stylesheet" />
</head>
<body>
...
<script src="toastr.min.js"></script>
{% if messages %}
<script>
toastr.options = {
"showDuration": "300",
"hideDuration": "1000",
"timeOut": "5000"
}
{% for message in messages %}
toastr.{{ message.tags }}("{{ message }}");
{% endfor %}
</script>
{% endif %}
</body>
message.tags: Using to match with the function of toastr, for example if you want to show an error by using messages.error(...) then the message.tags will be error, when your template rendered it turned to toastr.error("Your message here") and then you'll see the toast message on your browser.
Hope that helps!
There's two ways to go about it:
You can create a client-side check that prevents the form from being sent with Javascript. That is not Django-specific and you'll have no trouble finding examples.
You catch the fact that no file was sent and set an extra flag for the upload.html template. Untested example code:
message = None
data = None
if request.FILES:
data = # process file
message = "Upload successful!"
else:
message = "Please upload a file!"
return render(request, 'upload.html', {"data": data, "message": message})
Then you can show the message in the template:
{% if message %}
<div class="message">{{message}}</div>
{% endif %}

How to use a WTForms FieldList of FormFields?

I'm building a website using Flask in which I use WTForms. In a Form I now want to use a FieldList of FormFields as follows:
class LocationForm(Form):
location_id = StringField('location_id')
city = StringField('city')
class CompanyForm(Form):
company_name = StringField('company_name')
locations = FieldList(FormField(LocationForm))
so to give people the ability to enter a company with two locations (dynamic adding of locations comes later) I do this on the front side:
<form action="" method="post" role="form">
{{ companyForm.hidden_tag() }}
{{ companyForm.company_name() }}
{{ locationForm.location_id() }}
{{ locationForm.city() }}
{{ locationForm.location_id() }}
{{ locationForm.city() }}
<input type="submit" value="Submit!" />
</form>
So on submit I print the locations:
print companyForm.locations.data
but I get
[{'location_id': u'', 'city': u''}]
I can print the values of the first location using the locationForm (see below), but I still don't know how to get the data of the second location.
print locationForm.location_id.data
print locationForm.city.data
So the list of locations does have one dict with empty values, but:
Why does the list of locations have only one, and not two dicts?
And why are the values in the location dict empty?
Does anybody know what I'm doing wrong here? All tips are welcome!
For starters, there's an argument for the FieldList called min_entries, that will make space for your data:
class CompanyForm(Form):
company_name = StringField('company_name')
locations = FieldList(FormField(LocationForm), min_entries=2)
This will setup the list the way you need. Next you should render the fields directly from the locations property, so names are generated correctly:
<form action="" method="post" role="form">
{{ companyForm.hidden_tag() }}
{{ companyForm.company_name() }}
{{ companyForm.locations() }}
<input type="submit" value="Submit!" />
</form>
Look at the rendered html, the inputs should have names like locations-0-city, this way WTForms will know which is which.
Alternatively, for custom rendering of elements do
{% for l in companyForms.locations %}
{{ l.form.city }}
{% endfor %}
(in wtforms alone l.city is shorthand for l.form.city. However, that syntax seems to clash with Jinja, and there it is necessary to use the explicit l.form.city in the template.)
Now to ready the submitted data, just create the CompanyForm and iterate over the locations:
for entry in form.locations.entries:
print entry.data['location_id']
print entry.data['city']
This is an old question, but still a good one.
I'd like to add a working Flask based example of a toy database (just a list of strings) with focus on the Python part - how to initialize the form with variable number of subforms and how to process the posted data.
This is the example.py file:
import flask
import wtforms
import flask_wtf
app = flask.Flask(__name__)
app.secret_key = 'fixme!'
# not subclassing from flask_wtf.FlaskForm
# in order to avoid CSRF on subforms
class EntryForm(wtforms.Form):
city = wtforms.fields.StringField('city name:')
delete = wtforms.fields.BooleanField('delete?')
class MainForm(flask_wtf.FlaskForm):
entries = wtforms.fields.FieldList(wtforms.fields.FormField(EntryForm))
submit = wtforms.fields.SubmitField('SUBMIT')
city_db = "Graz Poprad Brno Basel Rosenheim Torino".split() # initial value
#app.route("/", methods=['POST'])
def demo_view_function_post():
global city_db
form = MainForm()
if form.validate_on_submit():
city_db = [
entry['city'] for entry in form.entries.data
if entry['city'] and not entry['delete']]
return flask.redirect(flask.url_for('demo_view_function_get'))
# handle the validation error, i.e. flash a warning
return flask.render_template('demo.html', form=form)
#app.route("/")
def demo_view_function_get():
form = MainForm()
entries_data = [{'city': city, 'delete': False} for city in city_db]
entries_data.append({'city': '', 'delete': False}) # "add new" placeholder
form.process(data={'entries': entries_data})
return flask.render_template('demo.html', form=form)
This is the demo.html file:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Demo</title>
</head>
<body>
<h1>Subform demo</h1>
<p>Edit names / mark for deletion / add new</p>
<form method="post">
{{ form.csrf_token() }}
{% for entry in form.entries %}
{% if loop.last %}
<div>Add new:</div>
{% endif %}
<div>
{{ entry.city.label }} {{ entry.city() }}
{{ entry.delete() }} {{ entry.delete.label }}
</div>
{% endfor %}
{{ form.submit() }}
</form>
</body>
Run with: FLASK_APP=example flask run

Django Development Server, blank HttpResponse on Post Method

I am developing an application in Django using Heroku's tools and guides for doing so, and have ran into an issue. On my local dev environment I cannot get a response from my views if it I use the post method. Currently i'm using a simple form to post a collection of ids to a view.
def webinarToHS(request):
errors = []
if request.method == 'GET':
webinars = get_upcoming_webinars()
return render_to_response('webinarToHS.html', {'webinars': webinars.json(), 'length': len(webinars.json())/2}, RequestContext(request))
elif request.method == 'POST':
method = request.method
return HttpResponse("test")
In the console it comes back with a 200 response ok. However the browser displays a blank html page (empty body tags).
On the production/heroku server, i get back a response, so I don't believe there is an issue with the code itself but rather with my settings file. I went back through the heroku django setup guide and used an environment variable on my local machine to switch those settings off if i'm in local dev but I am still having this issue.
Can anyone give me a clue as to where to start looking for a fix? I'm running windows 7 with a virtualenv wrapper, python 2.7.5 and django 1.5
Thanks.
As per requested in the comments, the WebinarToHS template file is as below:
<html>
<head>
<title>Add Webinars to Hubspot</title>
<style>
html, body {background-color: #eee;}
#wrapper {background-color: #fefefe; width:60%; margin:0 auto; position:relative; margin-top:50px; padding:25px;}
form {text-align:center;}
label {font-weight:bold;}
.submit {float:right;}
.check {float:left; text-align:left; max-width:48%; margin-right:2%;}
</style>
</head>
<body>
<div id="wrapper">
<form name="form" action="{%url 'G2WApi.views.webinarToHS' %}" method="post">
{% csrf_token %}
<label for="webinarKey">Choose the Webinar(s) you would like to add to hubspot:</label><br/><br/>
<div class="check">
{% for webinar in webinars %}
<input type="checkbox" name="webinars[]" value="{{ webinar.webinarKey }}" />{{ webinar.subject }}<br/>
{% if forloop.counter|divisibleby:length %}
</div><div class="check">
{% endif %}
{% endfor %}
</div>
<div style="clear:both; height:10px;"></div>
<input class="submit" type="submit" value="Add to Hubspot" />
</form>
</div>
</body>
</html>
Although it doesn't actually solve the question at hand, I found a way that was much better for what I was trying to do. I discovered that everything in my code for the POST Method worked except the output or rendering of a template. HttpRedirection worked, and also the Django Messages System. I found a tutorial on how to use that and it works perfectly for posting responses back to the originating template.
The Django Messaging system, for those who aren't aware, is a way to add details back to the originating request object. Here's a snipper
messages.success(request, "success message")
return HttpResponseRedirect(request.path)
The first line of code is attaching the message to the request object (in this case the same one that was passed in to the view) and then in the second line of code we are just redirecting back to the requesting path. Django handles all the rest you just have to add some code to your template like follows:
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
to loop through and display your messages however you wish. I hope this helps someone.

Categories

Resources