Django JSON not sending proper data - python

I am developing a django python web application. In my webpage, I am sending a request to my API by sending a 'term' and my API is supposed to return the 'content' field of the search.
My content contains 'xxx is good' in my database.
Here is my code in views.py
def get_RuleStatement(request):
if request.is_ajax():
q = request.GET.get('term', '')
rule_statements = RuleStatement.objects.filter(content__icontains = q )[:1]
results = []
for rule_statement in rule_statements:
rule_statement_json = {}
rule_statement_json['content'] = rule_statement.content
results.append(rule_statement_json)
data = json.dumps(results)
else:
data = 'fail'
mimetype = 'application/json'
return HttpResponse(data, mimetype)
For some reason, whenever I send the following request: http://website.com/api/get_RuleStatement/?term=xxx
It returns 'fail' even through my database contains data 'xxx is good'. Can anyone suggest where I am going wrong?

The only reason possible for it is is_ajax() is return False. That means your are not making the AJAX request. I believe you are making normal HTTP call to your endpoint.

See the django documentation and check that your HTTP request has the HTTP header HTTP_X_REQUESTED_WITH set with the string 'XMLHttpRequest'. Then request.is_ajax() will return True.
Note modern libraries like jQuery will automatically set this HTTP header for you; e.g., if you get jquery and do
<script>
$.get('/api/get_RuleStatement', {'term': 'xxx'})
</script>
Alternatively, you can get rid of the if request.is_ajax(): line and make it
def get_RuleStatement(request):
q = request.GET.get('term', '')
rule_statements = RuleStatement.objects.filter(content__icontains = q)[:1]
results = []
for rule_statement in rule_statements:
results.append({'content': rule_statement.content})
data = json.dumps(results)
mimetype = 'application/json'
return HttpResponse(data, mimetype)
if you don't care about it being an ajax call and want to be able to see the response otherwise. Also you may want to consider using JsonResponse instead of HttpResponse so you don't have to serialize or set MIME-type yourself. E.g.,
def get_RuleStatement(request):
q = request.GET.get('term', '')
rule_statements = RuleStatement.objects.filter(content__icontains = q)[:1]
results = []
for rule_statement in rule_statements:
results.append({'content': rule_statement.content})
return JsonResponse(results, safe=False)
# the safe = False is neccessary when
# you serialize non-dicts like results which is a list

Related

How do I POST boolean data to Flask route

I want to pass a variable called manual to Flask a route, which will then do something based on the value in the POST form data. But the forms data is interpreted as string in flask even though I send it in a form as a dictionary.
here's the code
#app.route("/result", methods= [ 'POST', 'GET'])
def result():
manual = request.form.get("manual")
if manual is None:
return "manual is required"
here's how I am sending the data
r = requests.get('http://127.0.0.1:5000/result'
,data={manual':False})
I understand that I can do something like;
if manual == 'True'
but I don't want to be comparing strings, I want to do it in the standard way whichever it is.
Thanks
First of all, do a POST request, not a GET:
r = requests.post('http://127.0.0.1:5000/result', json={'manual': False})
Then (untested):
#app.route("/result", methods=['POST'])
def result():
json_data = flask.request.json
manual = json_data.get("manual")
if manual is None:
return "manual is required"
Have a look at the doc for details: More complicated POST requests.
Note that there are differences between using the data parameter and the json parameter. An important thing to note is the presence of the Content-Type header:
Using the json parameter in the request will change the Content-Type
in the header to application/json.

flask not providing results to ajax call

I am making ajax call to flask function to get data using a token as following
#app.route("/questgen/results", methods = ['POST', 'GET'])
def fetch_results():
token = request.form.get('token')
print(token)
conn = sqlite3.connect("database.db" , timeout=20)
cur= conn.cursor()
select_query = '''SELECT processed, output_text
FROM results
WHERE token = token
'''
cur.execute(select_query)
records = cur.fetchall()
processed = ""
html = ""
for row in records:
processed = row[0]
html = row[1]
conn.close()
data = {'processed': processed, 'html' : html}
return redirect(url_for('questgen', data = data))
ajax call is as following
$.ajax({
type: "POST",
url: "/questgen/results",
data: { token: token },
datatype: "json",
success: function (data) {
if (data.processed == 1) {
$('#divresults').html(data.html);
$('#divresults').show();
hideMyModal();
clearInterval(saveInterval);
}
}
});
the complete is a bit lengthy it can be found in this gist the problem is that it returns
TypeError: The view function did not return a valid response. The
function either returned None or ended without a return statement.
even though I have tried same flask function as python function by using return data on the same database and it works. I have even tried to get token as function parameter but still it's not working. Can someone help with what am I doing wrong here? Thank you
The jQuery ajax call does not handle redirects automatically. You'll just get a response with a 301 or 302 status code. If you really need to have it redirect, you'll need to check for a 302 status return and make the call again with the changed data. It would be better if you could just do the redirection internally by calling the other function.
Try jsonify to return data
from flask import Flask, jsonify, request #import this
And then use this to return data
return jsonify({"res": data})
In ajax you will get your data in res
console.log(data.res) // your data
console.log(data.res.processed) // your if condition
Also check whether you need to parse response body or not

Handle post request in flask and bottle

I've been trying to build an API for more than 4 hours now and I searched and asked everywhere I could but I can't find help. The problem is at the level of handling POST requests. I tried with NodeJS (testify and express (as well as middlewares)) and Python (Flask, bottle) and I still can't get why I get an empty object with express or None in python. I have the following code with bottle
1 from bottle import run, Bottle, post, request, redirect
2 import json
3
4 pika = Bottle()
5
6 #post("/shorturl")
7 def shorten():
8 data = request.json
9 #data = json.dumps(rdata)
10 print(data)
11 return f"You posted {data}"
12
13 run(host="localhost", port=3000, debug=True)
And I had the following code at the beginning (I deleted and restarted from scratch) - you can find tweet here.
I can't get None with flask and bottle when using request.get_json() and request.json() respectively which I've found are the way to do it from the docs.
Any help appreciated.
Thanks
Your python code seems to be correct, because I tried and it worked.
For troubleshooting you can insert:
print(request.headers['Content-Type']) #output should be: application/json'
print(request.body.read()) #output should be like: b'{"key":"value"}'
I used Postman and when I tried the first time, I made the mistake to select body form instead of raw. In Postman you have to write Content-Type:appliction/json in the header manually and insert the json as raw.
I assume in Restler it's similar (I never used Restler).
So if you have to configure it manually, make sure that your header contains 'Content-Type':'application/json'
And your body is set to raw and look like this.
If for example form-data was selected, the manually set header would not be used by postman and print(request.header['Content-Type']) would output something like this: multipart/form-data; boundary=--------------------------742283902180513961059188
Could imagine Restler has the same snare.
Here is a way to handle a dynamic routing of the api. Now you just have to add methods to the API class and they are automatically picked up by the bottle app. I am merging POST and GET into one method string to merge query parameters and forms into one payload which you can access via self.payload
import ujson as json
from login import User
def check(data):
try:
if isinstance(data, (str,bytes)):
return json.loads(data)
except:
return data
return data
def merge_dicts(*args):
result = {}
for dictionary in args:
result.update(dictionary or {})
return result
class API:
def __init__(self, payload, user):
self.payload = payload
self.option = ''
self.request = None
#classmethod
def bot(cls, request, option, user):
payload = merge_dicts(dict(request.forms), dict(request.query.decode())) # merge query and form inputs
slf = cls(payload, user)
slf.request = request
slf.option = str(option)
return slf
def test(self): # localhost/api/test
return self.payload
#get('/api/<command>')
#post('/api/<command>')
#get('/api/<command>/<option>')
#post('/api/<command>/<option>')
def routeapi(command='', option=''):
user = User()
wapi = API.bot(request, option, user)
func = getattr(wapi, f"{command}", None)
if callable(func):
result = func()
if result:
if request.method == 'GET':
response.headers['Content-Type'] = 'application/json'
response.headers['Cache-Control'] = 'no-cache'
return {command:json.check(result)}
else:
return {command:None}

Django - post InMemoryUploadedFile to external REST api

In the Django Rest Framework I would like to post a file, received as an InMemoryUploadedFile, to a different server as soon as it is received.
It sounds simple, but the request.post() function does not seem to properly send over such a file :
def post(self, request, *args, **kwargs):
data = request.data
print(data)
# <QueryDict: {'file': [<InMemoryUploadedFile: myfile.pdf (application/pdf)>]}>
endpoint = OTHER_API_URL + "/endpoint"
r = requests.post(endpoint, files=data)
My other server receives the request (through flask) with the name of the file, but not the content:
#app.route("/endpoint", methods=["POST"])
def endpoint():
if flask.request.method == "POST":
# I removed the many checks to simplify the code
file = flask.request.files['file']
path = os.path.join(UPLOAD_FOLDER, file.filename)
file.save(path)
print(file) #<FileStorage: u'file.pdf' (None)>
print(os.path.getsize(path)) #0
return [{"response":"ok"}]
When posting a file directly to that api in form-data with postman, It works as expected:
print(file) # <FileStorage: u'file.pdf' ('application/pdf')>
print(os.path.getsize(path)) #8541
Any help on how to fix this, i.e. transform the InMemoryUploadedFile type in something a normal REST api can understand? Or maybe just adding the right headers?
I had to figure this issue out passing an uploaded file from a Django front end website to a Django backend API in Python 3. The InMemoryUploadedFile's actual file data can be accessed via the object's .file property's .getvalue() method.
path="path/to/api"
in_memory_uploaded_file = request.FILES['my_file']
io_file = in_memory_uploaded_file.file
file_value = io_file.getvalue()
files = {'my_file': file_value}
make_http_request(path, files=files)
and can be shortened
file = request.FILES['my_file'].file.getvalue()
files = {'my_file': file}
Before this, trying to send InMemoryUploadFile objects, the file property, or the result of the read() method all proved to send a blank/empty file by the time it got to the API.
I had the same problem and the same case.
My working solution
headers = {
"Host": API_HOST,
"cache-control": "no-cache",
}
try:
data = json_request = request.POST['json_request'].strip()
data = json.loads(data) # important!
except:
raise Http404
try:
in_memory_uploaded_file = request.FILES['file'].file.getvalue()
files = {'photo': in_memory_uploaded_file} # important!
except:
files = {}
if USE_HTTPS:
API_HOST = f'https://{API_HOST}'
else:
API_HOST = f'http://{API_HOST}'
if authorization_key and len(authorization_key) > 0:
response = requests.post(f'{API_HOST}/api/json/?authorization_key={authorization_key}', headers=headers, data=data, files=files)
else:
response = requests.post(f'{API_HOST}/api/json/', headers=headers, data=data)
json_response = json.dumps(response.json())

How to utilise payload instead of params in Taskqueue?

Within my unitest when I attempt the following POST; I get the params within the request.base_url. I would like to have it within request.form. How do I achieve that?
self.taskqueue_stub = apiproxy_stub_map.apiproxy.GetStub('taskqueue')
tasks = self.taskqueue_stub.GetTasks("postios")
self.assertEqual(len(tasks), 1)
task = tasks[0]
params = base64.b64decode(task["body"])
headers['Content-Type'] = 'application/json'
response = self.client.post(task["url"], params, headers=headers)
I found a way to pass it as data: request.data, but thats not good enough.
response = self.client.post(task["url"], data=params, headers=headers)
The reason I have to do this, is the way I add the task in my code.
taskqueue.Task(url='/worker',
params={"json_records": jsonified_task_records,
"user": user.key.urlsafe()}
).add(queue_name='postios')
Hence within the /worker view I expect to find the params in request.form.
I ended up using payload instead of params.
params = {"json_records": jsonified_task_records,
"user": user.key.urlsafe()}
taskqueue.Task(url='/worker',
payload=json.dumps(params)
).add(queue_name='postios')
Now I always have it as request.data within my /worker view so that both unit tests and production code can expect to find it there.
Beware that payloads is unicode in this case and nested json structure needs to be converted once again via json.loads.
e.g. /worker view:
jsons = json.loads(request.data)
user_hash = jsons['user']
json_records = json.loads(jsons['json_records']) # jsons['json_records'] is unicode

Categories