How use a conditional statement in a Flask app? - python

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.

Related

Failed to get value from html page in Django

I have a problem with trying to get a response from my HTML page using Django (admin).
I have a pretty simple div = contenteditable and need to pass data from this div back after the submit button was clicked.
Everything, including choosing selection and opening the intermediate page works fine. But when I tapped submit button, the condition if "apply" in request.POST failed to work.
Please, tell me, what I'm doing wrong?
This is my Django admin:
class QuestionAdmin(AnnotatesDisplayAdminMixin, admin.ModelAdmin):
def matched_skills(self, question):
return ', '.join(s.name for s in question.skills.all())
def update_skills(self, request, queryset):
if 'apply' in request.POST:
print("something")
skills = []
for question in queryset:
skills.append(self.matched_skills(question))
return render(request,
'admin/order_intermediate.html',
context={'skills': skills})
update_skills.short_description = "Update skills"
This is my order_intermediate.html page:
{% extends "admin/base_site.html" %}
{% block content %}
<form method="post">
{% csrf_token %}
<h1>Adjust skills. </h1>
{% for skill in skills %}
<div>
<div id="title" style="margin-left: 5px" contenteditable="true" > {{ skill }} </div>
</div>
{% endfor %}
<input type="hidden" name="action" value="update_status" />
<input type="submit" name="apply" value="Update skills"/>
</form>
{% endblock %}
Actually, request.POST is an HttpRequest object. For getting available keys in the body of the request, you need to use "request.POST.keys()" method. So, you can simply change your condition to:
if 'apply' in request.POST.keys():
print("something")
In my knowledge, you can not send div content with form submit. However you can use input tag with array in name attribute for this. This will send an array as post variable when submit
First, send skills as a enumerate object from your views
return render(request, 'admin/order_intermediate.html', context={'skills': enumerate(skills)})
Then edit your html to this (Note: if you have css in title id, change it to title class)
{% for i,skill in skills %}
<div>
<input class="title" name="skill[{{ i }}]" value="{{ skill }}" style="margin-left: 5px">
</div>
{% endfor %}
and handle array with any action you want to perform in update_skills()
for skill in request.POST.getlist('skill[]'):
# your code

POST Method not allowed for a simple lookup webpage

I have a simple page with a data entry field and a click button, this will run the API to retrieve the coin data
running the code in a python terminal return with success, but when I try to add it to flask and use the webpage, I get the error 405 method not allowed for the POST.
This is the main python/flask file:
crypto.py
# template libraries
from flask import render_template,url_for,flash,request,redirect,Blueprint
# Coingecko API library
from pycoingecko import CoinGeckoAPI
crypto_simulator = Blueprint('crypto_simulator',__name__)
#crypto_simulator.route('/crypto_simulator', methods=['GET','POST'])
#login_required
def crypto_insert():
if request.form.get("ident") == "formCrypto":
print('Hello')
cg = CoinGeckoAPI()
#crypto_token = request.form.get('crypto_name_html', '')
crypto_token = 'bitcoin'
crypto_currency = 'usd'
response = cg.get_price(ids=crypto_token,
vs_currencies='usd',
include_market_cap='true',
include_24hr_vol='true',
include_24hr_change='true',
include_last_updated_at='true')
crypto_result = response.get(crypto_token,'')
print(crypto_result[crypto_currency])
return render_template('crypto_simulator.html',
formCryptoSimulation=form,
crypto_token=crypto_token,
crypto_currency=crypto_currency,
crypto_result=crypto_result
)
This is the Blueprint routing file:
core.py
# crypto section
#core.route('/crypto_simulator')
def crypto_simulator():
return render_template('crypto_simulator.html')
This is the Flask/Bootstrap front-end:
crypto_simulator.html
{% extends "base.html" %}
{% block content %}
<!-- Simulation Code Start -->
<div class="forms">
<div class="formCrypto">
<form method="post" action="{{ url_for('core.crypto_simulator') }}">
<div class="container">
<div class="row g-3">
<div class="col-sm-3">
<label class="form-label"><b>Crypto Name:</b></label>
<input type="text" class="form-control" name="crypto_name_html" placeholder="Enter Crypto Name" required>
</div>
</div>
</div>
<br>
<div class="d-grid gap-2 d-md-flex justify-content-md-start">
<button id="btn" type="submit" class="btn btn-info">Check Token!</button>
</div>
<input type=hidden name="ident" value="formCrypto">
</form>
<br>
<p>Token: <b>{{crypto_token}}</b>
<p>Price: <b>{{crypto_result}}</b>
</div>
</div>
{% endblock %}
I checked for misspelled lines and anything related but still stuck into how to fix it...
Your form has this:
<form method="post" action="{{ url_for('core.crypto_simulator') }}">
So you are calling the function crypto_simulator in blueprint core:
#core.route('/crypto_simulator')
def crypto_simulator():
return render_template('crypto_simulator.html')
Note that your form does a POST request, so you very logically have to enable the POST method on the function being called like this:
#core.route('/crypto_simulator', methods=['GET','POST'])

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

I am trying to retrieve the current page number from the html page for url routing on update_table function with respect to pagination

I am trying to retrieve the current page number from the html page for url routing on update_table function with respect to pagination. When I get/print the 'page' variable I keep getting None. Could someone please help me with this?
views.py
#app.route('/')
#app.route('/<int:page_num>')
def index(page_num=1):
historical_data = HR_historical.query.paginate(per_page=100, page=page_num, error_out=True)
return render_template('index.html',historical_data = historical_data)
#app.route('/update-table/<int:id>', methods=['GET','POST'])
def update_table(id):
updated = HR_historical.query.filter_by(id=id).first()
form = Historical_Data_Form(obj=updated)
if form.validate_on_submit():
form.populate_obj(updated)
db.session.add(updated)
db.session.commit()
**page = request.form.get('page')
print(page)
return redirect(url_for('index', page_num=page))**
return render_template('edit_historical_table.html',form=form)
index.html
<div class="pagination">
«
{% for page in historical_data.iter_pages(left_edge=1, left_current=1, right_current=2, right_edge=1) %}
<form class="" method="post">
<input type="hidden" name="page" value="{{page}}">
</form>
{% if page %}
{{page}}
{% else %}
<a href="#"<«</a>
...
{% endif %}
{% endfor %}
»
</div>

Flask and WTForms with custom validation

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.

Categories

Resources