flask_restful cannot return file - flask

I have the following flak_restful file to return back a csv file to the user, but I'm getting the following error.
File "/usr/local/Cellar/python3/3.6.1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/encoder.py", line 180, in default
o.__class__.__name__)
TypeError: Object of type 'Response' is not JSON serializable
import flask
from flask import request
from flask_restful import Resource
class MyAPIRes(Resource):
#classmethod
def get(cls):
csv = '1,2,3\n4,5,6\n'
response = flask.make_response(csv)
response.headers['content-type'] = 'application/octet-stream'
return response, 200

Removing the , 200 from the second part of the return statement should work.
That second argument will call a helper from flask_restful to create a response, but in this case, you've already gone ahead and created a response object with make_response. That's fine, you need to create your own object to return anything other than JSON. But one of the things the helper does is serialize your data for you (turn it into JSON), and the Response type is not serializable.
Taking a look at the trace, you can see it happening in the error message here:
File ".../flask_restful/__init__.py", line 510, in make_response
resp = self.representations[mediatype](data, *args, **kwargs)
File ".../flask_restful/representations/json.py", line 20, in output_json
dumped = dumps(data, **settings) + "\n"
Docs on response types can be found in the section on flask_restful response formats.

Related

Django REST API: How to respond to POST request?

I want send POST request with axios(VueJS) and when Django server got a POST request then I want to get back that request message.
I tried make functions when got an POST request in Django server and then return JsonResponse({"response": "got post request"), safe=False)
JS function
sendMessage() {
axios({
method: "POST",
url: url,
data: this.message
})
.then(response => {
this.receive = response;
})
.catch(response => {
alert('Failed to POST.' + response);
})
}
}
views.py
from chat.serializer import chatSerializer
from chat.models import *
from rest_framework.routers import DefaultRouter
from rest_framework import viewsets
from django.http import JsonResponse
from django.views.generic import View
# Create your views here.
class get_post(View):
def post(self, request):
if request.method == 'POST':
JsonResponse({"response": 'got post request'}, safe=False)
but error says like that in django
Internal Server Error: /api/chat/
Traceback (most recent call last):
File "/usr/lib/python3/dist-packages/django/core/handlers/exception.py", line
47, in inner
response = get_response(request)
File "/usr/lib/python3/dist-packages/django/core/handlers/base.py", line 188,
in _get_response
self.check_response(response, callback)
File "/usr/lib/python3/dist-packages/django/core/handlers/base.py", line 309,
in check_response
raise ValueError(
ValueError: The view chat.views.views.get_post didn't return an HttpResponse object. It returned None instead.
[26/Oct/2022 17:06:51] "POST /api/chat/ HTTP/1.1" 500 60946
I think POST request is working properly, but Django code is something wrong.
Therefore, my question is..
How to fix and solve this error?
When I call axios in JS, inside '.then' we got a response so which data is come to this variable? Should I return this data like Response() or JsonResponse() method?
just add a return in the view ... like this:
class get_post(View):
def post(self, request):
if request.method == 'POST':
return JsonResponse({"response": 'got post request'}, safe=False)
because the error complains that the post function must return anything.
for the second question ya you need to return JsonResponse because you are dealing with APIs.

How can I test binary file uploading with django-rest-framework's test client?

I have a Django application with a view that accepts a file to be uploaded. Using the Django REST framework I'm subclassing APIView and implementing the post() method like this:
class FileUpload(APIView):
permission_classes = (IsAuthenticated,)
def post(self, request, *args, **kwargs):
try:
image = request.FILES['image']
# Image processing here.
return Response(status=status.HTTP_201_CREATED)
except KeyError:
return Response(status=status.HTTP_400_BAD_REQUEST, data={'detail' : 'Expected image.'})
Now I'm trying to write a couple of unittests to ensure authentication is required and that an uploaded file is actually processed.
class TestFileUpload(APITestCase):
def test_that_authentication_is_required(self):
self.assertEqual(self.client.post('my_url').status_code, status.HTTP_401_UNAUTHORIZED)
def test_file_is_accepted(self):
self.client.force_authenticate(self.user)
image = Image.new('RGB', (100, 100))
tmp_file = tempfile.NamedTemporaryFile(suffix='.jpg')
image.save(tmp_file)
with open(tmp_file.name, 'rb') as data:
response = self.client.post('my_url', {'image': data}, format='multipart')
self.assertEqual(status.HTTP_201_CREATED, response.status_code)
But this fails when the REST framework attempts to encode the request
Traceback (most recent call last):
File "/home/vagrant/.virtualenvs/myapp/lib/python3.3/site-packages/django/utils/encoding.py", line 104, in force_text
s = six.text_type(s, encoding, errors)
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 118: invalid start byte
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/vagrant/webapp/myproject/myapp/tests.py", line 31, in test_that_jpeg_image_is_accepted
response = self.client.post('my_url', { 'image': data}, format='multipart')
File "/home/vagrant/.virtualenvs/myapp/lib/python3.3/site- packages/rest_framework/test.py", line 76, in post
return self.generic('POST', path, data, content_type, **extra)
File "/home/vagrant/.virtualenvs/myapp/lib/python3.3/site-packages/rest_framework/compat.py", line 470, in generic
data = force_bytes_or_smart_bytes(data, settings.DEFAULT_CHARSET)
File "/home/vagrant/.virtualenvs/myapp/lib/python3.3/site-packages/django/utils/encoding.py", line 73, in smart_text
return force_text(s, encoding, strings_only, errors)
File "/home/vagrant/.virtualenvs/myapp/lib/python3.3/site-packages/django/utils/encoding.py", line 116, in force_text
raise DjangoUnicodeDecodeError(s, *e.args)
django.utils.encoding.DjangoUnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 118: invalid start byte. You passed in b'--BoUnDaRyStRiNg\r\nContent-Disposition: form-data; name="image"; filename="tmpyz2wac.jpg"\r\nContent-Type: image/jpeg\r\n\r\n\xff\xd8\xff[binary data omitted]' (<class 'bytes'>)
How can I make the test client send the data without attempting to decode it as UTF-8?
When testing file uploads, you should pass the stream object into the request, not the data.
This was pointed out in the comments by #arocks
Pass { 'image': file} instead
But that didn't full explain why it was needed (and also didn't match the question). For this specific question, you should be doing
from PIL import Image
class TestFileUpload(APITestCase):
def test_file_is_accepted(self):
self.client.force_authenticate(self.user)
image = Image.new('RGB', (100, 100))
tmp_file = tempfile.NamedTemporaryFile(suffix='.jpg')
image.save(tmp_file)
tmp_file.seek(0)
response = self.client.post('my_url', {'image': tmp_file}, format='multipart')
self.assertEqual(status.HTTP_201_CREATED, response.status_code)
This will match a standard Django request, where the file is passed in as a stream object, and Django REST Framework handles it. When you just pass in the file data, Django and Django REST Framework interpret it as a string, which causes issues because it is expecting a stream.
And for those coming here looking to another common error, why file uploads just won't work but normal form data will: make sure to set format="multipart" when creating the request.
This also gives a similar issue, and was pointed out by #RobinElvin in the comments
It was because I was missing format='multipart'
Python 3 users: make sure you open the file in mode='rb' (read,binary). Otherwise, when Django calls read on the file the utf-8 codec will immediately start choking. The file should be decoded as binary not utf-8, ascii or any other encoding.
# This won't work in Python 3
with open(tmp_file.name) as fp:
response = self.client.post('my_url',
{'image': fp},
format='multipart')
# Set the mode to binary and read so it can be decoded as binary
with open(tmp_file.name, 'rb') as fp:
response = self.client.post('my_url',
{'image': fp},
format='multipart')
You can use Django built-in SimpleUploadedFile:
from django.core.files.uploadedfile import SimpleUploadedFile
class TestFileUpload(APITestCase):
...
def test_file_is_accepted(self):
...
tmp_file = SimpleUploadedFile(
"file.jpg", "file_content", content_type="image/jpg")
response = self.client.post(
'my_url', {'image': tmp_file}, format='multipart')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
It's not so simple to understand how to do it if you want to use the PATCH method, but I found the solution in this question.
from django.test.client import BOUNDARY, MULTIPART_CONTENT, encode_multipart
with open(tmp_file.name, 'rb') as fp:
response = self.client.patch(
'my_url',
encode_multipart(BOUNDARY, {'image': fp}),
content_type=MULTIPART_CONTENT
)
For those in Windows, the answer is a bit different. I had to do the following:
resp = None
with tempfile.NamedTemporaryFile(suffix='.jpg', delete=False) as tmp_file:
image = Image.new('RGB', (100, 100), "#ddd")
image.save(tmp_file, format="JPEG")
tmp_file.close()
# create status update
with open(tmp_file.name, 'rb') as photo:
resp = self.client.post('/api/articles/', {'title': 'title',
'content': 'content',
'photo': photo,
}, format='multipart')
os.remove(tmp_file.name)
The difference, as pointed in this answer (https://stackoverflow.com/a/23212515/72350), the file cannot be used after it was closed in Windows. Under Linux, #Meistro's answer should work.

Werkzeug test client and utf-8

Here's the code. When I send both fields it fails.
import unittest
class UnicodeTestCase(unittest.TestCase):
def test_unicode(self):
from cStringIO import StringIO
from flask import Flask, request
app = Flask(__name__)
app.config['TESTING'] = True
#app.route('/', methods=["POST"])
def test_view():
print request.values, request.files
return "OK"
file = (StringIO("0" * 1000), "filename.txt")
string = u"∆_∆"
client = app.test_client(use_cookies=False)
self.assertEquals(200, client.post('/', data={'file': file}).status_code)
self.assertEquals(200, client.post('/', data={'string': string}).status_code)
self.assertEquals(200, client.post('/', data={'file': file, 'string': string}).status_code)
On the last assert it fails with:
Error
Traceback (most recent call last):
File "/Users/user1/tests/test_uni.py", line 108, in test_unicode
self.assertEquals(200, client.post('/', data={'file': file, 'string': string}).status_code)
File "/Users/user1/.virtualenvs/test/lib/python2.7/site-packages/werkzeug/test.py", line 771, in post
return self.open(*args, **kw)
File "/Users/user1/.virtualenvs/test/lib/python2.7/site-packages/flask/testing.py", line 108, in open
follow_redirects=follow_redirects)
File "/Users/user1/.virtualenvs/test/lib/python2.7/site-packages/werkzeug/test.py", line 725, in open
environ = args[0].get_environ()
File "/Users/user1/.virtualenvs/test/lib/python2.7/site-packages/werkzeug/test.py", line 535, in get_environ
stream_encode_multipart(values, charset=self.charset)
File "/Users/user1/.virtualenvs/test/lib/python2.7/site-packages/werkzeug/test.py", line 104, in stream_encode_multipart
write('\r\n\r\n' + value)
File "/Users/user1/.virtualenvs/test/lib/python2.7/site-packages/werkzeug/test.py", line 71, in write
write_binary(string.encode(charset))
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 4: ordinal not in range(128)
It works fine when I'm sending both fields with the Postman (a Google Chrome extension).
Is it OK and should I wrap fields with unicode with base64 or something else? Or is it a bug in the werkzeug test client?
Look like test client bug, I already have another bug with test client when direct request work fine, but test client has unexpected result.
For me in https://github.com/mitsuhiko/werkzeug/blob/master/werkzeug/test.py#L71 I have string type as str. For string only this method not called, for file only this method do not called with your string. You can try temporary update this method with next for python 2 only:
def write(string):
if isinstance(string, str):
write_binary(string)
else:
write_binary(string.encode(charset))
I created bug for your example: https://github.com/mitsuhiko/flask/issues/973.

Exporting data from App Engine datastore as a Google Drive spreadsheet

I have an app engine application on which I mark my monthly expenses along with some comments or reason. I would like to export these data into a
Google Drive Spreadsheet. I use Django framework.
I had gone through the tutorials provided by Google here.
But they have implemented it using webapp2 and jinja. Moreover, the doc for Implementing Using Django seems way too obsolete since I do not use Django ORM.
Below is my code sample which I use to upload. I strongly apologize if what I paste below is rubbish. Please help.
from django.utils.datastructures import SortedDict
import os
from apiclient.discovery import build
from apiclient.http import MediaFileUpload
from oauth2client.appengine import OAuth2DecoratorFromClientSecrets
decorator = OAuth2DecoratorFromClientSecrets(os.path.join(os.path.dirname(__file__ ), 'clientSecrets.json'), 'https://www.googleapis.com/auth/drive')
drive_service = build('drive', 'v2')
class Exporter(object):
serializedObjects = []
mime_type = 'text/plain'
fileToExport = None
request = None
def __init__(self, serializedObjects, request):
self.serializedObjects = serializedObjects
self.request = request
def createCSV(self):
import csv
import StringIO
stdout = StringIO.StringIO()
writer = csv.writer(stdout)
for obj in self.serializedObjects:
for value in obj.values():
writer.writerow([value])
# I will get the csv produced from my datastore objects here.
# I would like to upload this into a Google Spreadsheet.
# The child class ExportToSpreadSheet tries to do this.
return stdout.getvalue()
class ExportToSpreadSheet(Exporter):
def __init__(self, *args, **kwargs):
super(ExportToSpreadSheet, self).__init__(*args, **kwargs)
self.mime_type = 'application/vnd.google-apps.spreadsheet'
def create(self):
import datetime
valueToDrive = self.createCSV()
media_body = MediaFileUpload(valueToDrive, mimetype=self.mime_type, resumable=True)
body = {
'title' : 'MyExpense_%s' % datetime.datetime.now().strftime('%d_%b_%Y_%H_%M_%S'),
'description' : '',
'mimeType' : self.mime_type
}
self.fileToExport = drive_service.files().insert(body=body, media_body=media_body, convert=True)
return self.fileToExport
#decorator.oauth_aware
def upload(self):
if decorator.has_credentials():
self.create()
self.fileToExport.execute(decorator.http())
return self.fileToExport
raise Exception('user does not have the credentials to upload to google drive.')
#decorator.oauth_aware only works on webapp.RequestHandler subclasses. Why I am saying it is because I got this error while I ran the code.
INFO 2013-09-19 11:28:04,550 discovery.py:190] URL being requested: https://www.googleapis.com/discovery/v1/apis/drive/v2/rest?userIp=%3A%3A1
ERROR 2013-09-19 11:28:05,670 main.py:13] Exception in request:
Traceback (most recent call last):
File "/home/dev5/divya/jk/MyApp/ItsMyTuition/SDK/google_appengine/lib/django-1.2/django/core/handlers/base.py", line 100, in get_response
response = callback(request, *callback_args, **callback_kwargs)
File "/home/dev5/divya/jk/MyApp/ItsMyTuition/ItsMyTuition/src/tuition/json/ajaxHandler.py", line 27, in mainHandler
responseValues = funtionToCall(*args)
File "/home/dev5/divya/jk/MyApp/ItsMyTuition/ItsMyTuition/src/tuition/json/ajaxHandler.py", line 69, in export
uploadedFile = exporterInstance.upload()
File "/home/dev5/divya/jk/MyApp/ItsMyTuition/ItsMyTuition/src/oauth2client/appengine.py", line 770, in setup_oauth
self._create_flow(request_handler)
File "/home/dev5/divya/jk/MyApp/ItsMyTuition/ItsMyTuition/src/oauth2client/appengine.py", line 734, in _create_flow
redirect_uri = request_handler.request.relative_url(
AttributeError: 'ExportToSpreadSheet' object has no attribute 'request'
INFO 2013-09-19 11:28:05,777 module.py:593] default: "POST /ajaxCall/export HTTP/1.1" 200 964
Since I am using Django framework I cannot get a Request handler as they except.
How can I integrate or do it in my scenario? I would very much appreciate any code samples or relevant links I may have missed.
Moreover, the whole thing happens in an ajax call.
Thanks in advance.
Use mimeType=text/csv and during the upload, request a conversion from csv to Spreadsheets:
drive_service.files().insert(covert=True, body=body, media_body=media_body, convert=True)

django simplejson [] is not JSON serializable

in my view, why does this work:
results = []
results.append({'status':1})
results.append({'bookmarks':[]})
simplejson.dumps(results)
# produces: []
and this doesn't:
from myapp.models import Bookmark
results = []
results.append({'status':1})
results.append({'bookmarks':Bookmark.objects.all()})
# fails with exception saying: [] is not JSON serializable
completely stack trace follows
Traceback:
File "/Users/Ishaq/Projects/github/bookmarks/venv/lib/python2.7/site-packages/django/core/handlers/base.py" in get_response
115. response = callback(request, *callback_args, **callback_kwargs)
File "/Users/Ishaq/Projects/github/bookmarks/bookmarks/views.py" in index
9. return HttpResponse(simplejson.dumps(Bookmark.objects.all()), mimetype='application/json');
File "/usr/local/Cellar/python/2.7.3/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/__init__.py" in dumps
231. return _default_encoder.encode(obj)
File "/usr/local/Cellar/python/2.7.3/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/encoder.py" in encode
201. chunks = self.iterencode(o, _one_shot=True)
File "/usr/local/Cellar/python/2.7.3/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/encoder.py" in iterencode
264. return _iterencode(o, 0)
File "/usr/local/Cellar/python/2.7.3/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/encoder.py" in default
178. raise TypeError(repr(o) + " is not JSON serializable")
Exception Type: TypeError at /conferences/
Exception Value: [] is not JSON serializable
Instead of using simplejson for serialize django objects, use serialization provided by django.
With reference form the link, you can do:
from django.core import serializers
data = serializers.serialize("json", Bookmark.objects.all())
I was waiting for Burhan Khalid to turn his comment into an answer, but since he hasn't, I would.
using simplejson.dumps(list(Bookmark.objects.all())) made it work