Flask: use [GET, POST] function for a single URL - python

I'm new to Flask and wondering if it is possible to use the same URL to display a form in html and query to display something
Ideally, I want to have the following result to happen.
If I'm going to create a query inside 138.10.2.1/sample by doing this:
http://138.10.2.1:8000/sample?psi=1&lavr=1&dsc=1&ifsc=1&ics=1&eng=3&ol1=1&ol2=1&reso=1&educ=1&listen=1&time=1&probe=1&unders=1
It will display:
*something in json format*
in the webpage
Else, if I'll just go straight to this:
http://138.10.2.1:8000/sample
It will direct me to a .html page with a form to fill or allow a user to attach a file to use and upload to display a transformed file in json format also.
Here's my code
sample.py
from flask import Flask, flash, request, redirect, url_for, make_response, send_from_directory, render_template
import convert as ps
app = Flask(__name__)
#app.route("/sample", methods=["GET", "POST"])
def query_strings():
#This is not working: if request.method == "POST":
args1 = request.args["psi"]
args2 = request.args["lavr"]
args3 = request.args["dsc"]
args4 = request.args["ifsc"]
args5 = request.args["ics"]
args6 = request.args["eng"]
args7 = request.args["ol1"]
args8 = request.args["ol2"]
args9 = request.args["reso"]
args10 = request.args["educ"]
args11 = request.args["listen"]
args12 = request.args["time"]
args13 = request.args["probe"]
args14 = request.args["unders"]
args_list = [args1, args2, args3, args4, args5, args6, args7, args8,args9, args10, args11, args12, args13, args14]
result = ps.execute(args_list)
response = app.response_class(
response=result,
status=200,
mimetype='application/json'
)
return response
#This is my html form: return render_template("form.html")
if __name__ == '__main__':
app.run(debug = True)
Right now, what I can have is to run the query but I am getting prompted to enter the paramaters I declared if I just entered:
http://138.10.2.1:8000/sample

You can check the number of arguments and return the HTML form if the length is 0 like this:
# if there are no arguments provided, show HTML form
if len(request.args) == 0:
return render_template("form.html")
Also, there's no need to store each argument as a separate variable and then combine them into a list. request.args is already a dictionary so you can simply get a list of the argument names and values with:
list(request.args.keys()) # list of argument names eg. ['psi', 'lavr', 'dsc', 'ifsc'...]
list(request.args.values()) # list of argument values eg. [1, 1, 1, 1...]
You can check if the argument names match a desired set:
if set(argument_names) == {"psi","lavr","dsc","ifsc","ics","eng","ol1","ol2","reso","educ","listen","time","probe","unders"}:
# return JSON
Overall, your code could look something like this:
from flask import Flask, request, render_template
import json
app = Flask(__name__)
#app.route("/sample", methods=["GET", "POST"])
def query_strings():
# if there are no arguments provided, show HTML form
if len(request.args) == 0:
return render_template("form.html")
argument_names = list(request.args.keys())
# if the argument list is valid
if set(argument_names) == {"psi","lavr","dsc","ifsc","ics","eng","ol1","ol2","reso","educ","listen","time","probe","unders"}:
# return JSON
response = app.response_class(
response=json.dumps(request.args),
status=200,
mimetype='application/json'
)
return response
return "Invalid arguments"
if __name__ == '__main__':
app.run(debug = True)
This will:
show form.html if you load /sample with no arguments
show the arguments as JSON if you load /sample with valid arguments (eg. /sample?psi=1&lavr=1&dsc=1&ifsc=1&ics=1&eng=3&ol1=1&ol2=1&reso=1&educ=1&listen=1&time=1&probe=1&unders=1)
show "Invalid arguments" in any other case

Ohh..I see..so how can I have a .html to display if I don't enter any parameters? This will allow me to attach a file inside the html form and create to POST and get the same json result
Based on your comment to the question and sample code you've posted I assume you might be looking for something like this:
#app.route("/sample", methods=["GET", "POST"])
def query_strings():
args1 = request.args.get("psi")
args2 = request.args.get("lavr")
args3 = request.args.get("dsc")
args4 = request.args.get("ifsc")
args5 = request.args.get("ics")
args6 = request.args.get("eng")
args7 = request.args.get("ol1")
args8 = request.args.get("ol2")
args9 = request.args.get("reso")
args10 = request.args.get("educ")
args11 = request.args.get("listen")
args12 = request.args.get("time")
args13 = request.args.get("probe")
args14 = request.args.get("unders")
args_list = [
args1, args2, args3, args4, args5, args6, args7, args8,
args9, args10, args11, args12, args13, args14
]
if not all(args_list):
return render_template('form.html')
else:
result = ps.execute(args_list)
response = app.response_class(
response=result,
status=200,
mimetype='application/json'
)
return response
In this case, if you give no parameters in GET request it will render a template with html form.
Also rather than always check for request method I suggest you take a look at Flask's MethodView. Using that you can nicely split you logic onto request with arguments in the query string and form submitting with json:
http://flask.pocoo.org/docs/1.0/api/#flask.views.MethodView

Try this code, I hope it will help you. By default, it uses the GET method, so it is not working. When you click on the submit button then it calls the POST method.
from flask import Flask, flash, request, redirect, url_for, make_response, send_from_directory, render_template
import convert as ps
app = Flask(__name__)
#app.route("/sample", methods=["GET", "POST"])
def query_strings():
if request.method == "POST":
args1 = request.args["psi"]
args2 = request.args["lavr"]
args3 = request.args["dsc"]
args4 = request.args["ifsc"]
args5 = request.args["ics"]
args6 = request.args["eng"]
args7 = request.args["ol1"]
args8 = request.args["ol2"]
args9 = request.args["reso"]
args10 = request.args["educ"]
args11 = request.args["listen"]
args12 = request.args["time"]
args13 = request.args["probe"]
args14 = request.args["unders"]
args_list = [args1, args2, args3, args4, args5, args6, args7, args8,args9, args10, args11, args12, args13, args14]
result = ps.execute(args_list)
response = app.response_class(
response=result,
status=200,
mimetype='application/json'
)
# return response
return render_template("form.html", response = response)
return render_template("form.html")
#This is my html form: return render_template("form.html")
if __name__ == '__main__':
app.run(debug = True)

Related

How to test a Flask (non-route) function that processes a request, using pytest?

Context: I have two Flask routes which process the same request data (one interactively, one as an API). To keep my code DRY, I want to write a function process_post_request() that
accepts the request as in input parameter from each route,
parses the request,
returns results which the routes can use.
For example, in views.py:
#app.route('/interactive', methods=['GET', 'POST'])
def interactive():
if request.method == 'POST':
sum, product = process_post_request(request)
# present the results on a web page
#app.route('/api', methods=['GET', 'POST'])
def api():
if request.method == 'POST':
sum, product = process_post_request(request)
# return the results so that JavaScript can parse them
def process_post_request(request):
param1 = request.form.get('param1')
param2 = request.form.get('param2')
sum = param1 + param2
product = param1 * param2
return sum, product
Question: How can I write a pytest for process_post_request()? The problem is that if I create a "request" and try to pass it to process_post_request(), the request goes to a route, so that route returns a result. For example, in views_test.py:
import pytest
#pytest.fixture
def client():
"""Create a client to make requests from"""
with app.test_client() as client:
with app.app_context():
pass
yield client
def test_process_post_request():
request = client.post('/interactive', data={'param1': 5, 'param2': 7})
sum, product = process_post_request(request)
assert sum == 12
Because request returns a response, pytest throws this error:
> request = client.post('/interactive', data={'param1': 5, 'param2': 7})
E AttributeError: 'function' object has no attribute 'post'
I created an app route to return comma-separated parameters:
#app.route('/pass-through', methods = ['GET', 'POST'])
def pass_through():
if request.method == 'POST':
params = process_post_request(request)
return ','.join(params)
Then tested that app route:
def test_process_post_request():
with app.test_client() as client:
response = client.post('/pass-through', data={'param1': 5, 'param2': 7})
sum, product = response.data.decode().split(',')
assert sum == 12
where decode() translates from bytes to string.
It's not the most satisfying solution because it really tests the app route, so it depends on the app route function pass_through() using the same parameter names as process_post_request().

How to send a file to post method that accepts form data in python flask, and also how to retrieve file and other fields in the called method?

I'm trying something like this.
Caller code -
files = {'file': open(file_name, 'rb').read()}
response = requests.post(url, headers=headers, data=metadata, files=files)
Called method code-
metadata = {}
for key, value in request.form.items():
metadata[key] = value
print(metadata)
print(type(metadata))
print("Request data")
print(request.data)
print("Files")
print(request.files)
print(request.files is None)
print('file' not in request.files)
The output in called method is empty
{}
<class 'dict'>
Request data
b''
Files
ImmutableMultiDict([])
False
True
Can someone tell me how to fix this?
files = {'upload_file': open('file.txt','rb')}
values = {'DB': 'photcat', 'OUT': 'csv', 'SHORT': 'short'}
r = requests.post(url, files=files, data=values)
from flask import Flask, render_template, request, redirect, url_for
app = Flask(__name__)
#app.route('/')
def index():
return render_template('index.html')
#app.route('/', methods=['POST'])
def upload_file():
uploaded_file = request.files['file']
if uploaded_file.filename != '':
uploaded_file.save(uploaded_file.filename)
return redirect(url_for('index'))

Flask jsonify returns bytes and string instead of json object

In Postman post_new_cafe prints json as it suppose to be, but when I want to print it inside console and webpage it prints differently. See example below.
#app.route('/add_form', methods=['GET', 'POST'])
def add_new_cafe_form():
form = CafeForm()
if form.validate_on_submit():
response = post_new_cafe()
print(response)
return render_template("add.html", form=form)
This prints out
<Response 52 bytes [200 OK]>
and
#app.route('/add_form', methods=['GET', 'POST'])
def add_new_cafe_form():
form = CafeForm()
if form.validate_on_submit():
response = post_new_cafe()
print(response.response)
return render_template("add.html", form=form)
prints out
[b'{\n "success": "Successfully added the new cafe."\n}\n']
and this
#app.route('/add_form', methods=['GET', 'POST'])
def add_new_cafe_form():
form = CafeForm()
if form.validate_on_submit():
response = post_new_cafe()
print(response.json())
return render_template("add.html", form=form)
gives error
TypeError: 'dict' object is not callable
This is function that returns jsonify
# # HTTP POST - Create Record
#app.route('/add', methods=['POST'])
def post_new_cafe():
new_cafe = Cafe(
name=request.form.get('name'),
map_url=request.form.get('map_url'),
img_url=request.form.get('img_url'),
location=request.form.get('location'),
seats=request.form.get('seats'),
has_toilet=bool(strtobool(request.form.get('has_toilet'))),
has_wifi=bool(strtobool(request.form.get('has_wifi'))),
has_sockets=bool(strtobool(request.form.get('has_sockets'))),
can_take_calls=bool(strtobool(request.form.get('can_take_calls'))),
coffee_price=request.form.get('coffee_price')
)
# db.session.add(new_cafe)
# db.session.commit()
return jsonify(success="Successfully added the new cafe.")
I have tried this
resp = Response(response={"success":"Successfully added the new cafe."},
status=200,
mimetype="application/json")
return jsonify(resp)
and it's not working, also I have tried using make_response still nothing.
What I want is when I store post_new_cafe() into response variable to have this
response = post_new_cafe()
data = response.json()
print(data)
{"success": "Successfully added the new cafe."}
print(data["success"])
Successfully added the new cafe.
Hey you can solve this issue with the json library.
Example:
import json
def post_new_cafe():
new_cafe = Cafe(
name=request.form.get('name'),
map_url=request.form.get('map_url'),
img_url=request.form.get('img_url'),
location=request.form.get('location'),
seats=request.form.get('seats'),
has_toilet=bool(strtobool(request.form.get('has_toilet'))),
has_wifi=bool(strtobool(request.form.get('has_wifi'))),
has_sockets=bool(strtobool(request.form.get('has_sockets'))),
can_take_calls=bool(strtobool(request.form.get('can_take_calls'))),
coffee_price=request.form.get('coffee_price')
)
return json.dumps({"success": "Succesfully added the new cafe."})
response = post_new_cafe()
data = json.loads(response)
print(data)
print(data["success"])
For more information look at the Documentation about JSON
If you need to serialize a numpy array, there is a question on how to serialize a numpy array as JSON
Regarding your other issue:
#app.route('/add_form', methods=['GET', 'POST'])
def add_new_cafe_form():
form = CafeForm()
if form.validate_on_submit():
response = post_new_cafe()
print(response.json())
return render_template("add.html", form=form)
You need to convert the response from binary to string first: response.decode('utf-8') and then parse it as JSON: json.loads(response.decode('utf-8'))

Error occuring while sending test mail using flask-mail

I am basically building a flask web app which takes codeforces user id and returns user info
i am trying to add flask-mail functionality into it but it displays the
error:
TypeError: cannot unpack non-iterable NoneType object
The error is in the mail.send(msg) line
from flask import Flask, render_template,redirect,request
from flask_mail import Mail,Message
import requests
import json
import os
app = Flask(__name__)
app.config['MAIL_SERVER']='smtp.gmail.com'
app.config['MAIL_PORT'] = 465
app.config['MAIL_USERNAME'] = 'testdev582#gmail.com'
app.config['MAIL_PASSWORD'] = 'hardik1234'
app.config['MAIL_USE_TLS'] = False
app.config['MAIL_USE_SSL'] = True
#print(app.config.update(mail_settings))
#app.config.update(mail_settings)
mail = Mail(app)
#app.route('/',methods=['GET','POST'])
def index():
if request.method == 'POST':
username = request.form['username']
email_id = request.form.get('email-id')
msg = Message("test",sender=app.config.get("MAIL_USERNAME"),recipients=[email_id])
mail.send(msg)
print(email_id)
#print(username)
url = 'http://codeforces.com/api/user.info?handles=' + username
wvurl = 'http://codeforces.com/api/contest.list?gym=false'
resp = requests.get(url)
r = requests.get(wvurl)
if resp and r:
user_data = json.loads(resp.text)['result']
contest_data = json.loads(r.text)['result']
for contest in contest_data:
if contest['phase'] == 'BEFORE':
break
print(contest['name'])
return render_template('show.html',user_data = user_data,contest_data = contest)
else:
print("Error in getting response")
return render_template('index.html')
return render_template('index.html')
if __name__=='__main__':
app.run(debug=True)
You will want to use os.environ
example:
app.config['MAIL_USERNAME'] = os.environ.get('EMAIL_USER', 'my#email.com')
app.config['MAIL_PASSWORD'] = os.environ.get('EMAIL_PASS', 'mypassword')
or set environment variables
windows:
https://www.youtube.com/watch?v=IolxqkL7cD8
linux:
https://www.youtube.com/watch?v=5iWhQWVXosU
You can try to check email_id if it is wrong.
I was just wrong there.

How to handle multiple WSGI applications in Flask?

I'm a newbie in Flask and was wondering how to implement Flask API (http://www.flaskapi.org/) along with the main website, for example, main website (/), access API (/api/). In fact, this gives me an Internal Server Error.
Thank you.
from flask import Flask, request, url_for
from flask.ext.api import FlaskAPI, status, exceptions
# the all-important app variable:
app = Flask(__name__)
app2 = FlaskAPI(__name__)
#app.route("/")
def hello():
return "Oh, Hello Worldaa"
notes = {
0: 'do the shopping',
1: 'build the codez',
2: 'paint the door',
}
def note_repr(key):
return {
'url': request.host_url.rstrip('/api/') + url_for('notes_detail', key=key),
'text': notes[key]
}
#app2.route("/api/", methods=['GET', 'POST'])
def notes_list():
"""
List or create notes.
"""
if request.method == 'POST':
note = str(request.data.get('text', ''))
idx = max(notes.keys()) + 1
notes[idx] = note
return note_repr(idx), status.HTTP_201_CREATED
# request.method == 'GET'
return [note_repr(idx) for idx in sorted(notes.keys())]
#app2.route("/api/<int:key>/", methods=['GET', 'PUT', 'DELETE'])
def notes_detail(key):
"""
Retrieve, update or delete note instances.
"""
if request.method == 'PUT':
note = str(request.data.get('text', ''))
notes[key] = note
return note_repr(key)
elif request.method == 'DELETE':
notes.pop(key, None)
return '', status.HTTP_204_NO_CONTENT
# request.method == 'GET'
if key not in notes:
raise exceptions.NotFound()
return note_repr(key)
if __name__ == "__main__":
app.run(debug=True)
app2.run(debug=True)
You can look at Blueprint in the documentation of Flask.
For example:
main = Blueprint(__name__)
#main.route("/")
def index():
return "Hello, World."
api = Blurprint(__name__)
#api.route("/test")
def test():
return jsonify(dict(test="test")})
app = Flask(__name__)
app.register_blueprint(main)
app.register_blueprint(api, url_prefix="/api")
and then, you access index page by http://example.com, access api by http://example.com/api/test

Categories

Resources