Unsure how to post form data in Unit Test - python

I have a HTML template which submits a form containing the results of a user selection. This form is posted to a python function in "routes.py" which adds to a database, based on the value of this selection. I am trying to run a unit test on this function, however do not know how to post the form within the unit tests file.
Here is the relevant part of the form in the HTML template:
<form method="POST" action="{{ url_for('select') }}">
<div class="info">
<div >
<div >
<span>Select a car</span>
<br/>
<br>
<select name="car_select">
{% for i in cars %}
<option>{{i}}</option>
{% endfor %}
</select>
</div>
<br><br>
<button type="submit" onclick="time()">Vote</button>
</div>
</form>
Here is the relevant part of my function in "routes.py", that I am running the unit test on:
#app.route("/select" , methods=['GET', 'POST'])
def select():
select = request.form.get('car_select')
Here is the Helper Function that I have used in my unit test to initiate the "select" function (INCORRECT):
def select(self, car):
return self.app.post("/select", data=dict(car_select=car), follow_redirects = True)
What I expect this Helper Function to do, is simulate the POST performed by the HTML form method, such that "request.form.get('car_select')" is equal to the "car" parameter of the helper function.

You can use flask's built-in test_client:
app.test_client().post("/select", data=dict(car_select=car))

Related

How to render results from API based on the user's search using Django

As you can see in the picture below I'm trying to have the user search for a given country, start/end date and get the result of "Confirmed Cases" and "Date" back from the API, but I'm not sure how to do it.
I tried using this API, to fill the drop-down menu of the countries -->
https://api.covid19api.com/summary
but this is the other API that I have to use but while changing the parameters for the country and dates -->
https://api.covid19api.com/country/afghanistan/status/confirmed?from=2020-09-06T00:00:00Z&to=2020-09-11T00:00:00Z
Here are snippets of my code:
views.py
def home(request):
# second_response = requests.get('https://api.covid19api.com/country/afghanistan/status/confirmed?from=2020-09-06T00:00:00Z&to=2020-09-11T00:00:00Z').json()
second_response = requests.get('https://api.covid19api.com/summary').json()
my_list = []
for i in range(0, len(second_response['Countries'])):
my_list.append(second_response['Countries'][i]['Country'])
if request.method=='POST':
selected_country = request.POST['selected_country']
print('here', selected_country)
return render(request, 'home.html', {'my_list': my_list})
home.html
<div class="container justify-content-center">
<form action="{% url 'home' %}" method="post">
{% csrf_token %}
<label for="selected_country" style="margin-right: 5px;"> Select a Country, Start & End Dates : </label>
<select name="selected_country" >
{% for object in my_list %}
<option value="">{{object}}</option>
{% endfor %}
</select>
<label for="startdate"></label>
<input type="date" id="startdate">
<label for="enddate"></label>
<input type="date" id="enddate">
<input type="submit" value="Search" />
</form>
</div>
PLUS: when I click on "search" i should get the value of the selected_country because I tried printing it, but it doesn't show for some reason, so the method is post but for some reason I can't get back the selected_country
Any help is appreciated
JAVASCRIPT
if you have any solid grasp of javascript i recommend you do that in javascript, because it will just make it better and easier
otherwise :
view.py
def handler(request):
if request.method=='POST':
selected_country = request.POST['selected_country']
startDate= request.POST['startdate']
endDate= request.POST['enddate']
request_handler = requests.get(f"https://api.covid19api.com/country/{selected_country}/status/confirmed?from={startDate}T00:00:00Z&to={endDate}T00:00:00Z")
if request_handler.status_code=200:
#To prevent errors
request_json=request_handler.json()
else:
pass # do something
return render(request, 'result.html', {"json":request_json})
#you should handle the data at the front end using jinja blocks
note : i don't know much about Django so the code may break

Getting checkbox form data when the form is generated by Jinja2

I'm trying to make a quiz website. I have a form where the user can select multiple checkboxes at once. I'm using Flask's Jinja2 templating to create the checkboxes. The number of questions per section may change. This is the code for the questions page. The user selects some checkboxes and submits them to the server. How do I know which checkbox was checked since multiple questions share the same name? Is there a better approach to this problem?
{% block head %}
<title>Questions Page</title>
{% endblock %}
{% block body %}
<div class="container-fluid">
<h1>{{title}}</h1>
{% for question in rel%}
<form action="/questions" method="POST">
<div class="form-check">
<input class="form-check-input" type="checkbox" value="" id="defaultCheck1" name="check1">
<label class="form-check-label" for="defaultCheck1">
{{question}}
</div>
{% endfor %}
<button class="btn btn-success" type="submit">
Submit
</button>
</form>
</div>
{% endblock %}
Python code:
I'm using a list to store question set title and list of questions.
#hen loaded for the first time, /questions will show the title as "A" and the checkboxes as "A1", "A2", "A3", "A4". After the form is submitted, /questions will show the title as "B" and the checkboxes as "B1" and so on until the list of questions is empty. Is it possible for me to get a list of which questions were checked, like ["A1", "A2"] for example?
from flask import Flask, render_template,request
app = Flask(__name__)
title = ["A","B"]
rel = [["A1", "A2","A3","A4"], ["B1","B2","B3"]]
#app.route("/")
def hello_world():
return render_template('index.html')
#app.route("/questions", methods = ['GET','POST'])
def questions():
if request.method == "GET":
return render_template('questions.html', rel=rel.pop(0), title=title.pop(0))
elif request.method == "POST":
#???
return str(request.form.getlist('check1')) #This returns a list like [","]
app.run(debug=True)
I figured it out. I just had to add {{question}} to the value of the checkbox and I got the list of checkboxes selected with their values.

Getting selected element of python list

I have a list of 6 elements. I get it as a dropdown through bootstrap-select. I can receive element of list in the console only if the form is submitted for handling.
Main question is:
How to make it so that when I select an element, I can immediately get it(print it) in the console, before submitting the form?
app.py
description = ['one','two','three','four','five','six']
#app.route('/', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
docs = request.form.getlist('sel')
print(docs)
return render_template('login.html', description=description)
return render_template('login.html', description=description)
login.html
<form action="" method="POST" class="form-inline">
<select id='sel' name='sel' class="selectpicker" multiple data-live-search="true">
{% for desc in description %}
<option value="{{desc}}">{{desc}}</option>
{% endfor %}
</select>
<button type="submit" class="btn btn-primary">Identify</button>
</form>
<script type="text/javascript">
$('select').selectpicker();
</script>
If you want to print in the console, you don't need to work on the app.py, however, you need to work on your login.html.
To enable this, your answer is onchange, which is predefined for the <select>, captures your data when you select an option. No need to have a submit button either.
UPDATED ANSWER: The <p> waits for the value, which is being set inside our function printValue()
<select id='sel' name='sel' class="selectpicker" multiple data-live-search="true" onchange="printValue(this)">
{% for desc in description %}
<option value="{{desc}}">{{desc}}</option>
{% endfor %}
</select>
<p id="mySelectedValue" style="margin-top: 10px;"></p>
<!-- In your script, call your function, which prints the data in your console-->
<script type="text/javascript">
function printValue(selectedItem){
$('#mySelectedValue').html(selectedItem.value);
}
</script>
In the method printValue(), you print your data value using the method :). You can do anything, here, like adding to selected item to your array or Map. It is upto, we're just printing in console for the sake of requirement :)
This is the SNIPPET for the REFERENCE on how onchange works in SELECT.
function printValue(selectedItem){
console.log(selectedItem.value);
}
<select class="form-control" id="your_id" onchange="printValue(this)">
<option value="Value1" selected>Value1</option>
<option value="Value2">Value2</option>
<option value="Value3">Value3</option>
</select>

Validating GET Params with WTForms in Flask

I have spent a couple of days trying to get WTForms to validate my request.args, but I just can not get form.validate() to return True.
The idea is that I have a simple text field for user input in a WTForm as shown below.
form.py
class SearchForm(FlaskForm):
q = StringField('q',
validators=[])
search = SubmitField('Search')
def validate_q(self, q):
if q.data not in allowed_values: #"allowed_values" is just a list I want to check against
raise ValidationError('')
search.html
<form method="GET" action="{{ url_for('finance.search') }}">
<div class="col-9 col-md-5 p-0 m-0">
{% if form.q.errors %} {{ form.q(class="form-control form-control-md is-invalid") }}
<div class="invalid-feedback">
{% for error in form.q.errors %}
<span>{{ error }}</span> {% endfor %}
</div>
{% else %} {{ form.q(class="form-control form-control-md") }} {% endif %}
</div>
<div class="col-2 col-md-2 p-0">
{{ form.search(class="btn btn-md btn-dark") }}
</div>
</form>
routes.py
#finance.route('/finance/search')
def search():
form = SearchForm(request.args)
print(form.validate()) #always gives false
The HTML code for the form is included on several templates and submitting the form always directs to the search route that is shown below. I tried following WTForms documentation and passed in request.args into the form. When I ran the .validate() on the object, the validate function for the q parameter also executed, but for some reason .validate() always returns False.
Can anyone please elaborate on why that might be? I know I can use post request, or add a custom validation function inside the route, but I want to avoid workarounds if possible.
(stack-overflow seems to use a similar type of architecture for their search http://127.0.0.1:8000/finance/search?q=aapl&search=Search vs https://stackoverflow.com/search?q=aapl and I want to follow that if possible.)
Thanks!
I actually just figured out that the error was happening because I was not including a crsf_token in the form. The token is not needed since the its a get request, but this needs to be explicitly stated with meta = {'csrf': False}.
#finance.route('/finance/search')
def search():
form = SearchForm(request.args, meta={'csrf': False})
print(form.validate()) #Now gives True if validation function does not raise error

How to call a different render after a form submission

I'm diving into Flask for the first time and I'm having some trouble getting something work.
I currently have a template for when my tab values is empty, it contains a form that when submitted should call a specific function using the parameters of the form and return another template. Each call of the form should in, fact call the index.html template with different values.
Relevant parts of code are as follows:
main.py
#app.route('/', methods=['POST','GET'])
def main():
global maxDepth, numberOfRelated
if not values:
return render_template('initial.html')
if request.method=='POST':
url = request.form['url']
maxDepth = request.form['depth']
numberOfRelated = request.form['numberOfRelated']
values = crawling(url,maxDepth,numberOfRelated)
return render_template('index.html',var=values)
The form part of initial.html and index.html are actually the same
<form class="form-inline" action="/" method="POST">
<div class="form-group">
<input name='url' type="text" class="form-control"/>
</div>
<div class="form-group minorForm">
<input name='numberOfRelated' type="text" class="form-control" />
</div>
<div class="form-group minorForm">
<input name='depth' type="text" class="form-control" />
</div>
<div class="form-group navbar-right">
<button class="btn btn-success minorForm generate" type="submit"> Generate</button>
</div>
</form>
In your main method, unless values is global, it won't be defined for if not values.
As to your question, add another render_template call just after the conditional for if the form was submitted:
if request.method=='POST':
url = request.form['url']
maxDepth = request.form['depth']
numberOfRelated = request.form['numberOfRelated']
values = crawling(url,maxDepth,numberOfRelated)
return render_template('index.html',var=values) # 1
return render_template('index.html',var=values) # 2
If the form is submitted, the conditional will be true and the template will be rendered at comment #1. If the user is navigating to the page normally, the conditional will be false and the line with comment #2 will be called.
I'm a bit confused about the question, but you should always redirect after a POST (unless there was an error in the form and no action was taken). That way the same action won't be repeated if the user reloads the page.

Categories

Resources