I have a Flask web form that "searches for members" using a free-text input field. I want to programmatically load-test this website using some command-line tool like curl, instead of mocking user behavior with something like Selenium.
Here is the Python backend:
# test.py
from time import sleep
from flask import Flask, render_template
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
class Config:
SECRET_KEY = 'SUPER_SECRET_VALUE'
app = Flask(__name__)
app.config.from_object(Config)
class TestForm(FlaskForm):
member_id = StringField('Member ID', validators=[DataRequired()])
submit = SubmitField('Submit')
#app.route('/test', methods=['GET', 'POST'])
def test():
form = TestForm()
if form.validate_on_submit():
member_id_field = form.member_id
member_id = member_id_field.data
# simulate doing something with `member_id`
sleep(2)
result = 'some result'
return render_template(
'test.html',
title='test',
form=form,
result=result
)
else:
return render_template(
'test.html',
title='test',
form=form
)
if __name__ == '__main__':
app.run()
And here is the HTML form:
<h1>Search for a member</h1>
<form action="" method="post" novalidate>
{{ form.hidden_tag() }}
<p>
{{ form.member_id.label }}
<br>
{{ form.member_id(size=32) }}
</p>
<p>{{ form.submit() }}</p>
{% if result is defined %}
<h1>Result is: {{ result }}</h1>
{% endif %}
</form>
First, I start the server with python3 test.py and then use curl -X POST -H "Content-Type: application/json" -d '{"member_id":"123"}' http://127.0.0.1:5000/test/ to try to send a member ID to the form to trigger the searching logic.
If I go to the website in a browser, I'm able to type in an ID and the Result is: section populates. However, using curl, I don't get anything back:
<h1>Search for a member</h1>
<form action="" method="post" novalidate>
<input id="csrf_token" name="csrf_token" type="hidden" value="BASDHSAHSADHASHDASHDAHSDH">
<p>
<label for="member_id">Member ID</label>
<br>
<input id="member_id" name="member_id" required size="32" type="text" value="123">
</p>
<p><input id="submit" name="submit" type="submit" value="Submit"></p>
If Result is: populates, I know my form is working. So, the using-a-website approach works, but the command line does not. What could the issue be?
This isn't the most optimal solution as it doesn't account for the jinja rendering time, but you could add a layer of flask_restful, developing a REST API for the core data processing piece:
from time import sleep
from flask import Flask, render_template, request
from flask_wtf import FlaskForm
from flask_restful import Api, Resource
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
class Config:
SECRET_KEY = 'SUPER_SECRET_VALUE'
def get_data(member_id):
sleep(2)
return f'getting data for {member_id}'
class TestForm(FlaskForm):
member_id = StringField('Member ID', validators=[DataRequired()])
submit = SubmitField('Submit')
class TestApi(Resource):
def get(self):
data = request.get_json(force=True)
get_data(data)
return data, 200
def post(self):
data = request.get_json(force=True)
get_data(data)
return data, 200
app = Flask(__name__)
app.config.from_object(Config)
api_manager = Api(app)
api_manager.add_resource(TestApi, '/api/test')
#app.route('/test', methods=['GET', 'POST'])
def test():
form = TestForm()
if form.validate_on_submit():
member_id_field = form.member_id
member_id = member_id_field.data
# simulate doing something with `member_id`
data = get_data(member_id)
return render_template(
'test.html',
form=form,
result=data
)
else:
return render_template(
'test.html',
form=form
)
if __name__ == '__main__':
app.run()
And then use:
curl -X POST -H "Content-Type: application/json" -d '{"member_id":"123"}' http://127.0.0.1:5000/api/test
Related
I am working with a flask app and writing unit tests in pytest. I have a LoginForm (flaskwtf) that just takes a username and password string. When I run the app and log in through the form, everything works fine. But when I try to simulate a login with pytest, I get a CSRF is Missing error even thought the csrf is shown by {{ form.hidden_tag() }} in my template. Why would this be happening?
I am assuming you have a flask app structured more or less like this:
from flask import Flask
from flask import redirect
from flask import render_template_string
from flask import url_for
from flask_wtf import FlaskForm
from flask_wtf.csrf import CSRFProtect
from wtforms import PasswordField
from wtforms import StringField
from wtforms import SubmitField
app = Flask(__name__)
app.config['SECRET_KEY'] = 'deadbeef'
csrf = CSRFProtect(app)
class LoginForm(FlaskForm):
username = StringField('username')
password = PasswordField('Password')
submit = SubmitField('submit')
TEMPLATE = '''\
<!DOCTYPE html>
<html>
<head> </head>
<body>
<form>
{{ form.hidden_tag() }}
<p>
{{ form.username.label }}
{{ form.username() }}
</p>
<p>
{{ form.password.label }}
{{ form.password() }}
</p>
<p>{{ form.submit() }}</p>
</form>
</body>
</html>
'''
#app.route('/', methods=['GET', 'POST'])
def index() -> str:
form = LoginForm()
if form.validate_on_submit():
return redirect(url_for('index'))
return render_template_string(TEMPLATE, form=form)
if __name__ == '__main__':
app.run(debug=True)
For testing flask apps, there is special test client as described in the testing flask docs, I am also assuming you are using this. With Flask-WTF there is an option called: WTF_CSRF_ENABLED (docs) which you can use to turn off the CSRF-Protection.
It is common to switch this off for testing to make it easier. You can do this by setting WTF_CSRF_ENABLED to False in your test client's app-config:
app.config['WTF_CSRF_ENABLED'] = False
A test with it switched off can look like this and passes without a missing CSRF-token error:
import pytest
from t import app # your app instance from the other file
#pytest.fixture
def client():
app.config['TESTING'] = True
app.config['WTF_CSRF_ENABLED'] = False
with app.test_client() as client:
yield client
def test_login(client):
rv = client.post(
'/',
data={'username': 'test', 'password': 'test'},
follow_redirects=True,
)
assert rv.status_code == 200
I am learning Flask and I've incurred into a supposedly beginners error:
Method Not Allowed The method is not allowed for the requested URL.
The thing is that, even though I reduce the code into something really simple, I can't find it. As far as I can see, the POST method is correctly set up (I will use comments to explain it).
The files are:
flaskblog.py
from flask import Flask, render_template, url_for, flash, redirect, request
from forms import RegistrationForm
app = Flask(__name__)
app.config['SECRET_KEY'] = '5791628bb0b13ce0c676dfde280ba245'
#app.route("/")
#app.route('/home', methods=['GET', 'POST']) # As one can see, the POST method is written there.
def home():
form = RegistrationForm()
return render_template('home.html', title='Home', form=form)
if __name__ == '__main__':
app.run(debug=True)
home.html
{% extends "layout.html" %}
{% block content %}
<div class="content-section">
<form method="POST" action=""> # The POST method is written there as well.
{{ form.hidden_tag() }}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Join Today</legend>
<div class="form-group">
{{ form.username.label(class="form-control-label") }}
{{ form.username(class="form-control form-control-lg") }}
</div>
</fieldset>
<div class="form-group">
{{ form.submit(class="btn btn-outline-info") }}
</div>
</form>
</div>
{% endblock content %}
forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, BooleanField
from wtforms.validators import DataRequired, Length, Email, EqualTo
class RegistrationForm(FlaskForm):
username = StringField('Username', validators=[DataRequired(), Length(min=2, max=20)])
submit = SubmitField('Sign Up')
I get the error when I submit the form, any help would be greatly appreciated!
Thanks!
You're not distinguishing between GET and POST requests appropriately in your home route. Flask defaults the incoming request, from the client, as a GET request since your definitions are off. However, this is an issue because the incoming request is actually a POST request as we can see from your form:
<form method="POST" action=""> # The POST method is written there as well.
Perhaps you could try modifying your home route to handle both POST/GET requests approriately, something along the lines of:
#app.route("/")
#app.route('/home', methods=['GET', 'POST'])
def home():
form = RegistrationForm()
# POST request
if form.validate_on_submit():
return redirect(url_for(<route>)) #route to be rendered if form properly validates
# GET request
return render_template('home.html', title='Home', form=form)
Hopefully that helps!
Here is the code snippet I have written
MainPage.py
from flask import Flask, render_template,url_for,request, redirect
from form import SearchForm
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secretkey1234'
#app.route("/")
#app.route("/home", methods=['GET', 'POST'])
def home():
forms = SearchForm()
return render_template('home.html', title='Home', forms=forms)
#app.route("/about")
def about():
return render_template('about.html', title='About')
if __name__ == '__main__':
app.run(debug=True)
form.py
from flask_wtf import FlaskForm
import Get_Test_Suite
from wtforms.fields import SelectField, SubmitField
from wtforms.fields.html5 import DateField
from wtforms.validators import DataRequired
class SearchForm(FlaskForm):
test_suite_list = Get_Test_Suite.get_test_suite()
suite_list = []
for i in test_suite_list:
suite_list.append((i, i))
test_suite_selected = SelectField('Test Suite Name', choices=suite_list)
test_module_list = Get_Test_Suite.get_module_name()
module_list = []
for j in test_module_list:
module_list.append((j, j))
test_module_selected = SelectField('Test Module Name', choices=module_list,validators=[DataRequired()])
date_selected = DateField('Date', format='%m-%d-%Y')
status = SelectField('Status', choices=[('Active', 'Active'), ('Closed', 'Closed')])
submit = SubmitField('Submit')
home.html
{% extends "layouts.html" %}
{% block content %}
<div class = "content-section">
<form method="POST" action="">
{{forms.hidden_tag()}}
<fieldset class="form-group">
<legend class ="border-bottom mb-4">
<center>SEARCH TEST FAILURE STATUS</center>
</legend>
<div class="form-group">
{{forms.test_suite_selected.label(class="form-control-label")}}
{{forms.test_suite_selected(class="form-control form-control-lg")}}
</div>
<div class="form-group">
{{forms.test_module_selected.label(class="form-control-label")}}
{{forms.test_module_selected(class="form-control form-control-lg")}}
</div>
<div class="form-group">
{{forms.date_selected.label(class="form-control-label")}}
{{forms.date_selected(class="form-control form-control-lg")}}
</div>
<div class="form-group">
{{forms.status.label(class="form-control-label")}}
{{forms.status(class="form-control form-control-lg")}}
</div>
</fieldset>
<div class="form-group">
{{forms.submit(class="btn btn-outline-info")}}
</div>
</form>
</div>
{% endblock content %}
I am creating many drop down lists in home.html
How can I get the data that the user selects from each of the drop down list and print it ? I am trying to create a user friendly web application where the user will select the values from a drop down list and the application will fetch the data accordingly from the database.
#app.route("/")
#app.route("/home", methods=['GET', 'POST'])
def home():
forms = SearchForm()
if request.method == 'POST':
print(forms.field_name.data) # <- prints to console.
# to print to web convert to variable and render with jinja2
# return render_template('display.html', field_name=forms.field_name.data)
return render_template('home.html', title='Home', forms=forms)
Have you tried transforming the 'choices' into a dict? You can then get the value for the key form data. Maybe something like this (for your "status" SelectField): value = dict(form.status.choices).get(form.status.data)
I sure that you was find your solution, but for next generation.
You need insert in the select tag the name of the value, and not in options tags.
example:
<select name="sServer">
{% for server in servers %}
<option value="{{server}}">{{server}}</option>
{% endfor %}
</select>
I am writing an application which is managing forms using Flask as backend framework.
When I fill data in the form(username and password) correctly, instead of the program returning 'Form sucessfully submitted', I get the following error:
Not Found
The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
Why is that happening?
Notice 1: I put a HTML file in templates directory, so that is not a cause of the error
Notice 2: I wrote here only the code which is relevant to this problem
Here is the code:
1) application.py
from flask import Flask, render_template, redirect, url_for
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField
from wtforms.validators import InputRequired, Email, Length, AnyOf
from flask_bootstrap import Bootstrap
app = Flask(__name__)
Bootstrap(app)
app.config['SECRET_KEY'] = 'somePassword'
class LoginForm(FlaskForm):
username = StringField('username', validators=[InputRequired(), Email(message='I don\'t like your email.')])
password = PasswordField('password', validators=[InputRequired(), Length(min=5, max=10), AnyOf(['secret', 'password'])])
#app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
return 'Form sucessfully submitted'
return render_template('loginForm1.html', form=form)
if __name__ == '__main__':
app.run(debug=True)
2) loginForm1.html
{% extends "bootstrap/base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}
WTForms
{% endblock %}
{% block content %}
<div class="container">
<form method="POST" action="/">
<dl>
{{ wtf.quick_form(form)}}
<input type="submit" value="Login" class="btn btn-info">
</dl>
</form>
</div>
{% endblock %}
You've defined the route as /login but your form is posting to /.
I need to prepopulate form fields with database (dataset) values.
The problem is that I don't know how to send the argument to the form class.
forms.py
# coding: utf-8
from db import produtosalca as produtos ##dataset imports db['table']
from flask_wtf import FlaskForm
from wtforms import TextField, BooleanField, SubmitField, TextAreaField, validators, ValidationError
class ProductForm(FlaskForm,produto):
descricao = TextField("Nome", default=produto.Descricao)
classificacaoFiscal = TextField("NCM", default=produto.ClassificacaoFiscal)
mva = TextField("MVA",default=produto.MVA)
app.py
# coding: utf-8
import os
from werkzeug import secure_filename
from flask import (
Flask, request, send_from_directory, render_template, current_app, flash
)
from db import produtosalca
from forms import ContactForm, ProductForm
app = Flask("alcax")
PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__))
app.config['MEDIA_ROOT'] = os.path.join(PROJECT_ROOT, 'media_files')
app.secret_key = 'development key'
#app.route('/productform', methods=['GET','POST'])
def productform(product):
form = ProductForm(request.form,product) ## here i was gonna send product
if request.method == 'POST':
'posted'
elif request.method == 'GET':
return render_template('productform.html', form=form.content())
Well everything I tried always get me the error
'produto' is not defined
I'm a beginner in py. Have been researching all night long. Thanks in advance.
Note that class ProductForm(FlaskForm) means ProductForm inherets from FlaskForm. A simple solution for your case would be as follows:
forms.py:
from flask_wtf import FlaskForm
from wtforms import TextField
class ProductForm(FlaskForm):
descricao = TextField("Nome")
classificacaoFiscal = TextField("NCM")
mva = TextField("MVA")
app.py:
from flask import Flask, render_template, redirect, request
from forms import ProductForm
app = Flask(__name__)
app.secret_key = 'development key'
#app.route('/productform', methods=['GET','POST'])
def productform():
form = ProductForm(descricao='default_descricao', classificacaoFiscal='default_classificacaoFiscal', mva='default_mva')
if form.validate_on_submit():
form = ProductForm(request.form)
# Do something with your form and redirect the user to some page.
return render_template('productform.html', form=form)
if __name__ == "__main__":
app.run(host="0.0.0.0", debug=True)
templates/_formhelpers.html
{% macro render_field(field) %}
<dt>{{ field.label }}
<dd>{{ field(**kwargs)|safe }}
{% if field.errors %}
<ul class=errors>
{% for error in field.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</dd>
{% endmacro %}
templates/productform.html
<html !DOCTYPE>
<head>
<title>Flask WTF</title>
</head>
<body>
{% from "_formhelpers.html" import render_field %}
<form method="POST" action="/submit">
<dl>
{{ form.hidden_tag() }}
{{ render_field(form.descricao, size=20) }}
{{ render_field(form.classificacaoFiscal, size=20) }}
{{ render_field(form.mva, size=20) }}
</dl>
<input type="submit" value="Go">
</form>
</body>
</html>
Edit to take into account the new queries in the comments below:
forms.py:
class ProductForm(FlaskForm):
descricao = TextField("Nome")
classificacaoFiscal = TextField("NCM")
mva = TextField("MVA")
# ...
def __init__(self, *args, **kwargs):
super(ProductForm, self).__init__(*args, **kwargs)
if 'product' in kwargs:
self.descricao.data = kwargs['product'][0]
self.classificacaoFiscal.data = kwargs['product'][1]
self.mva.data = kwargs['product'][2]
# ...
app.py:
#app.route('/productform', methods=['GET','POST'])
def productform():
# product to be retrieved from your database
form = ProductForm(product=product)
if form.validate_on_submit():
form = ProductForm(request.form)
# Do something with your form and redirect the user to some page.
return render_template('productform.html', form=form)