Data in request.body can't be found by request.data - Django Rest Framework - django

I'm writing a django application. I am trying to call my django rest framework from outside, and expecting an answer.
I use requests to send some data to a function in the DRF like this:
j=[i.json() for i in AttachmentType.objects.annotate(text_len=Length('terms')).filter(text_len__gt=1)]
j = json.dumps(j)
url = settings.WEBSERVICE_URL + '/api/v1/inference'
headers = {
'Content-Disposition': f'attachment; filename={file_name}',
'callback': 'http://localhost',
'type':j,
'x-api-key': settings.WEBSERVICE_API_KEY
}
data = {
'type':j
}
files = {
'file':file
}
response = requests.post(
url,
headers=headers,
files=files,
json=data,
)
In the DRF, i use the request object to get the data.
class InferenceView(APIView):
"""
From a pdf file, extract infos and return it
"""
permission_classes = [HasAPIKey]
def post(self, request):
print("REQUEST FILE",request.FILES)
print("REQUEST DATA",request.data)
callback = request.headers.get('callback', None)
# check correctness of callback
msg, ok = check_callback(callback)
if not ok: # if not ok return bad request
return build_json_response(msg, 400)
# get zip file
zip_file = request.FILES.get('file', None)
parsed = json.loads(request.data.get('type', None).replace("'","\""))
The problem is that the data in the DRF are not received correctly. Whatever I send from the requests.post is not received.
I am sending a file and a JSON together. The file somehow is received, but other data are not.
If I try to do something like
request.data.update({"type":j})
in the DRF, the JSON is correctly added to the data, so it is not a problem with the JSON I'm trying to send itself.
Another thing, request.body shows that the JSON is somehow present in the body, but request.data can't find it.
I don't want to use request.body directly because I can't understand why it is present in the body but not visible with request.data.

In this line
response = requests.post(
url,
headers=headers,
files=files,
json=data,
)
replace json=data with data=data
like this:
response = requests.post(
url,
headers=headers,
files=files,
data=data,
)

Related

Django freezes when reading data from request.body

Let's say we have a simple Django view:
def my_view(request):
content = request.body
# some actions with content varible
response = HttpResponse('<h1>It work!</h1>')
And a simple api client, let's say based on the requests library, sending malformed Django view data:
headers = dict()
headers['Accept'] = '*/*'
headers['Content-Length'] = '13409'
headers['Content-Type'] = 'application/x-compressed'
headers['Expect'] = '100-continue'
headers['Host'] = '127.0.0.1:8000'
headers['User-Agent'] = 'Api client'
headers['content-encoding'] = 'gzip'
url = 'http://127.0.0.1:8000/api'
request_body = ''
r = requests.post(
url,
data=request_body,
headers=headers
)
As you can see, request_body contains an empty string, but the Content-Length header stores the value 13409. When such a request arrives, Django hangs on the line reading request.body. No exceptions occur. How to solve this problem? I cannot influence the client, so the only thing I can do is rewrite the Django view. Django version 3.2.15 is used.

Why postman POST method do not provide params in `json` format?

In my flask project, there is a route:
def request_parse(req_data):
if req_data.method == 'POST':
data = req_data.json
elif req_data.method == 'GET':
data = req_data.args
return data
#app.route('/api/d/u', methods=['POST'])
def update(): # name, domain_list, pem_key, pem_cert, origin_ips
data = request_parse(request)
name = data.get('name')
domain_list = data.get('domain_list')
pem_key = data.get('pem_key')
pem_cert = data.get('pem_cert')
origin_ips = data.get('origin_ips')
in Postman I request it like this:
I use postman request the api:
you see it is POST method, and in my project debug, I found the request data is in form,not in json.
I also tried form-data and x-www-form-urlencoded format, all are in form.
why postman POST method do not provide params to request.json? and is it possible to provide params in request.json?
If you want to send it as JSON, change from x-www-form-urlencoded to raw and you should see a drop down for Text, JSON, HTML. You can then select JSON

How to make API call in Flask?

I am trying to make request to Clash of Clan Api and after requesting the right data it returns 200 ok & if i search wrong data it returns 404 not found. How to flash message after the data is not found according to the HTTP response from the API?
my views in flask
#app.route('/player', methods=['GET', 'POST'])
def player():
headers = header
url = ('https://api.clashofclans.com/v1/players/{}')
query = request.form.get('search')
player_id = urllib.parse.quote(query)
stats = requests.get(url.format(player_id), headers=headers).json()
return render_template('player.html', stats=stats, data=stats['achievements'])
stats = requests.get(url.format(player_id), headers=headers).json()
Here, you just take the JSON from the body and discard a bunch of useful data. Instead,
response = requests.get(url.format(player_id), headers=headers)
stats = response.json()
status_code = response.status_code
success = response.ok
# ...
You can see all the things you can get from the Response object in API documentation.

Django API Client Get Request with Payload

I am looking to see if this is possible. If I send a get request with body in postman I get back results matching what I need.
Can we send a get request with a body using the APIClient?
Here is my code;
def setUp(self):
self.client = APIClient()
HistoryLog.objects.create(username='User1', log_time='2020-05-05', logon_time='08:00', logout_time='09:00')
HistoryLog.objects.create(username='User2', log_time='2020-05-05', logon_time='08:30', logout_time='10:00')
HistoryLog.objects.create(username='User3', log_time='2020-05-08', logon_time='08:40', logout_time='11:00')
HistoryLog.objects.create(username='User4', log_time='2020-05-10', logon_time='08:50', logout_time='12:00')
def test_get_data(self):
payload = {'date': '2020-05-05', 'start_time': '06:00', 'end_time': '12:00'}
res = self.client.get(HISTORY_URL, payload)
self.assertEqual(res.status_code, status.HTTP_200_OK) -- this passes.
self.assertEqual(len(res.data), 2) -- this always come back that res.data is zero
A bit hacky, but seems to work:
def test_get_data(self):
payload = {'date': '2020-05-05', 'start_time': '06:00', 'end_time': '12:00'}
data, content_type = self.client._encode_data(payload, 'json')
res = self.client.generic('get', HISTORY_URL, data, content_type)
...

Query parameters in PUT call of APIClient

I have an API endpoint to which I want to make a PUT call which needs both a body and query parameters. I use Django's test client to call my endpoint in a test case (docs).
I read in the documentation that for a GET call, query parameters are introduced using the argument data. I also read that for a PUT call, the argument data represents the body. I miss documentation how to add query parameters in a PUT call.
In particular, this test case fails:
data = ['image_1', 'image_2']
url = reverse('images')
response = self.client.put(url,
data=data,
content_type='application/json',
params={'width': 100, 'height': 200})
And this test case passes:
data = ['image_1', 'image_2']
url = reverse('images') + '?width=100&height=200'
response = self.client.put(url,
data=data,
content_type='application/json')
In other words: is this manually URL building really necessary?
Assuming you're using rest_framework's APITestClient, I found this:
def get(self, path, data=None, secure=False, **extra):
"""Construct a GET request."""
data = {} if data is None else data
r = {
'QUERY_STRING': urlencode(data, doseq=True),
}
r.update(extra)
return self.generic('GET', path, secure=secure, **r)
whereas the put is:
def put(self, path, data='', content_type='application/octet-stream',
secure=False, **extra):
"""Construct a PUT request."""
return self.generic('PUT', path, data, content_type,
secure=secure, **extra)
and the interesting part (an excerpt from the self.generic code):
# If QUERY_STRING is absent or empty, we want to extract it from the URL.
if not r.get('QUERY_STRING'):
# WSGI requires latin-1 encoded strings. See get_path_info().
query_string = force_bytes(parsed[4]).decode('iso-8859-1')
r['QUERY_STRING'] = query_string
return self.request(**r)
so you could probably try to create that dict with QUERY_STRING and pass it to put's kwargs, I'm not sure how worthy effort-wise that is though.
I just specify what #henriquesalvaro answer more detail.
You can pass query-parameters in PUT or POST method like below.
# tests.py
def test_xxxxx(self):
url = 'xxxxx'
res = self.client.put(url,**{'QUERY_STRING': 'a=10&b=20'})
# views.py
class TestViewSet(.....):
def ...(self, request, *args, **kwargs):
print(request.query_params.get('a'))
print(request.query_params.get('b'))