Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0a48d21
fix a b0rked code example in the permissions section of api guide
wimglenn Aug 12, 2016
b508bc8
Merge pull request #4396 from wimglenn/docs_bugfix
jpadilla Aug 13, 2016
075a0bd
Fix template syntax error for `as_list_of_strings` (#4403)
jamesbeith Aug 15, 2016
785b206
Tweak doctsring. Closes #4404 [ci skip]
lovelydinosaur Aug 15, 2016
101fd29
Do not include uploads in request.POST (#4407)
lovelydinosaur Aug 15, 2016
e3f8d06
Include .action attribute on viewsets when generating schemas (#4408)
lovelydinosaur Aug 15, 2016
966330a
Replace utf8 character ' with its ascii counterpart, makes bdist_rpm.…
nevun Aug 17, 2016
b76984d
Allow custom CSRF_HEADER_NAME setting. (#4415)
lovelydinosaur Aug 18, 2016
382ea77
Improve debug error handling (#4416)
lovelydinosaur Aug 18, 2016
59ca61a
Add django-rest-framework-roles to third party packages in permission…
r1b Aug 19, 2016
e5b4498
Initial tests for API client
lovelydinosaur Aug 19, 2016
63342e8
Version 3.4.5 (#4421)
lovelydinosaur Aug 19, 2016
a335309
Add __str__ method to PKOnlyObject (#4423)
lovelydinosaur Aug 19, 2016
d540f02
Improve Create to show the original exception traceback (#3508)
orf Aug 19, 2016
c5e80e1
Merge branch 'master' into api-client
lovelydinosaur Aug 19, 2016
1a73c1c
Initial test cases for API client
lovelydinosaur Aug 19, 2016
e615f6d
Tests for API clients
lovelydinosaur Aug 22, 2016
341fa58
Support raw file uplaods with requests client / api client.
lovelydinosaur Aug 22, 2016
fe706eb
Tweak to tests for py3
lovelydinosaur Aug 22, 2016
b56e7d3
Upload and download support
lovelydinosaur Sep 9, 2016
b41ec30
Py3 compat
lovelydinosaur Sep 9, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Upload and download support
  • Loading branch information
lovelydinosaur committed Sep 9, 2016
commit b56e7d3a641ef60ffecdf46fbdfef3de0b31ede9
2 changes: 1 addition & 1 deletion requirements/requirements-optionals.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
markdown==2.6.4
django-guardian==1.4.3
django-filter==0.13.0
coreapi==1.32.3
coreapi==2.0.0
268 changes: 235 additions & 33 deletions tests/test_api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import unittest

from django.conf.urls import url
from django.http import HttpResponse
from django.test import override_settings

from rest_framework.compat import coreapi
Expand Down Expand Up @@ -39,17 +40,65 @@ def get_schema():
'multipart': coreapi.Link('/example/', action='post', encoding='multipart/form-data', fields=[
coreapi.Field(name='example')
]),
'multipart-body': coreapi.Link('/example/', action='post', encoding='multipart/form-data', fields=[
coreapi.Field(name='example', location='body')
]),
'urlencoded': coreapi.Link('/example/', action='post', encoding='application/x-www-form-urlencoded', fields=[
coreapi.Field(name='example')
]),
'urlencoded-body': coreapi.Link('/example/', action='post', encoding='application/x-www-form-urlencoded', fields=[
coreapi.Field(name='example', location='body')
]),
'raw_upload': coreapi.Link('/upload/', action='post', encoding='application/octet-stream', fields=[
coreapi.Field(name='example', location='body')
]),
},
'response': {
'download': coreapi.Link('/download/'),
'text': coreapi.Link('/text/')
}
}
)


def _get_query_params(request):
# Return query params in a plain dict, using a list value if more
# than one item is present for a given key.
return {
key: (value[0] if len(value) == 1 else value)
for key, value in
request.query_params.iterlists()
}


def _get_data(request):
if not isinstance(request.data, dict):
return request.data
# Coerce multidict into regular dict, and remove files to
# make assertions simpler.
if hasattr(request.data, 'iterlists'):
# Use a list value if a QueryDict contains multiple items for a key.
return {
key: value[0] if len(value) == 1 else value
for key, value in request.data.iterlists()
if key not in request.FILES
}
return {
key: value
for key, value in request.data.items()
if key not in request.FILES
}


def _get_files(request):
if not request.FILES:
return {}
return {
key: {'name': value.name, 'content': value.read()}
for key, value in request.FILES.items()
}


class SchemaView(APIView):
renderer_classes = [CoreJSONRenderer]

Expand All @@ -62,7 +111,7 @@ class ListView(APIView):
def get(self, request):
return Response({
'method': request.method,
'query_params': request.query_params
'query_params': _get_query_params(request)
})

def post(self, request):
Expand All @@ -71,29 +120,11 @@ def post(self, request):
else:
content_type = None

if isinstance(request.data, dict):
# Coerce multidict into regular dict, and remove files to
# make assertions simpler.
data = {
key: value for key, value in request.data.items()
if key not in request.FILES
}
else:
data = request.data

if request.FILES:
files = {
key: {'name': value.name, 'contents': value.read()}
for key, value in request.FILES.items()
}
else:
files = None

return Response({
'method': request.method,
'query_params': request.query_params,
'data': data,
'files': files,
'query_params': _get_query_params(request),
'data': _get_data(request),
'files': _get_files(request),
'content_type': content_type
})

Expand All @@ -103,27 +134,38 @@ def get(self, request, id):
return Response({
'id': id,
'method': request.method,
'query_params': request.query_params
'query_params': _get_query_params(request)
})


class UploadView(APIView):
parser_classes = [FileUploadParser]

def post(self, request):
upload = request.data['file']
contents = upload.read()
return Response({
'method': request.method,
'files': {'name': upload.name, 'contents': contents}
'files': _get_files(request),
'content_type': request.content_type
})


class DownloadView(APIView):
def get(self, request):
return HttpResponse('some file content', content_type='image/png')


class TextView(APIView):
def get(self, request):
return HttpResponse('123', content_type='text/plain')


urlpatterns = [
url(r'^$', SchemaView.as_view()),
url(r'^example/$', ListView.as_view()),
url(r'^example/(?P<id>[0-9]+)/$', DetailView.as_view()),
url(r'^upload/$', UploadView.as_view()),
url(r'^download/$', DownloadView.as_view()),
url(r'^text/$', TextView.as_view()),
]


Expand Down Expand Up @@ -154,6 +196,16 @@ def test_query_params(self):
}
assert data == expected

def test_query_params_with_multiple_values(self):
client = get_api_client()
schema = client.get('http://api.example.com/')
data = client.action(schema, ['location', 'query'], params={'example': [1, 2, 3]})
expected = {
'method': 'GET',
'query_params': {'example': ['1', '2', '3']}
}
assert data == expected

def test_form_params(self):
client = get_api_client()
schema = client.get('http://api.example.com/')
Expand All @@ -163,7 +215,7 @@ def test_form_params(self):
'content_type': 'application/json',
'query_params': {},
'data': {'example': 123},
'files': None
'files': {}
}
assert data == expected

Expand All @@ -176,7 +228,7 @@ def test_body_params(self):
'content_type': 'application/json',
'query_params': {},
'data': 123,
'files': None
'files': {}
}
assert data == expected

Expand All @@ -196,7 +248,7 @@ def test_multipart_encoding(self):
schema = client.get('http://api.example.com/')

temp = tempfile.NamedTemporaryFile()
temp.write(b'example file contents')
temp.write(b'example file content')
temp.flush()

with open(temp.name, 'rb') as upload:
Expand All @@ -208,10 +260,80 @@ def test_multipart_encoding(self):
'content_type': 'multipart/form-data',
'query_params': {},
'data': {},
'files': {'example': {'name': name, 'contents': 'example file contents'}}
'files': {'example': {'name': name, 'content': 'example file content'}}
}
assert data == expected

def test_multipart_encoding_no_file(self):
# When no file is included, multipart encoding should still be used.
client = get_api_client()
schema = client.get('http://api.example.com/')

data = client.action(schema, ['encoding', 'multipart'], params={'example': 123})

expected = {
'method': 'POST',
'content_type': 'multipart/form-data',
'query_params': {},
'data': {'example': '123'},
'files': {}
}
assert data == expected

def test_multipart_encoding_multiple_values(self):
client = get_api_client()
schema = client.get('http://api.example.com/')

data = client.action(schema, ['encoding', 'multipart'], params={'example': [1, 2, 3]})

expected = {
'method': 'POST',
'content_type': 'multipart/form-data',
'query_params': {},
'data': {'example': ['1', '2', '3']},
'files': {}
}
assert data == expected

def test_multipart_encoding_string_file_content(self):
# Test for `coreapi.utils.File` support.
from coreapi.utils import File

client = get_api_client()
schema = client.get('http://api.example.com/')

example = File(name='example.txt', content='123')
data = client.action(schema, ['encoding', 'multipart'], params={'example': example})

expected = {
'method': 'POST',
'content_type': 'multipart/form-data',
'query_params': {},
'data': {},
'files': {'example': {'name': 'example.txt', 'content': '123'}}
}
assert data == expected

def test_multipart_encoding_in_body(self):
from coreapi.utils import File

client = get_api_client()
schema = client.get('http://api.example.com/')

example = {'foo': File(name='example.txt', content='123'), 'bar': 'abc'}
data = client.action(schema, ['encoding', 'multipart-body'], params={'example': example})

expected = {
'method': 'POST',
'content_type': 'multipart/form-data',
'query_params': {},
'data': {'bar': 'abc'},
'files': {'foo': {'name': 'example.txt', 'content': '123'}}
}
assert data == expected

# URLencoded

def test_urlencoded_encoding(self):
client = get_api_client()
schema = client.get('http://api.example.com/')
Expand All @@ -221,16 +343,44 @@ def test_urlencoded_encoding(self):
'content_type': 'application/x-www-form-urlencoded',
'query_params': {},
'data': {'example': '123'},
'files': None
'files': {}
}
assert data == expected

def test_urlencoded_encoding_multiple_values(self):
client = get_api_client()
schema = client.get('http://api.example.com/')
data = client.action(schema, ['encoding', 'urlencoded'], params={'example': [1, 2, 3]})
expected = {
'method': 'POST',
'content_type': 'application/x-www-form-urlencoded',
'query_params': {},
'data': {'example': ['1', '2', '3']},
'files': {}
}
assert data == expected

def test_urlencoded_encoding_in_body(self):
client = get_api_client()
schema = client.get('http://api.example.com/')
data = client.action(schema, ['encoding', 'urlencoded-body'], params={'example': {'foo': 123, 'bar': True}})
expected = {
'method': 'POST',
'content_type': 'application/x-www-form-urlencoded',
'query_params': {},
'data': {'foo': '123', 'bar': 'true'},
'files': {}
}
assert data == expected

# Raw uploads

def test_raw_upload(self):
client = get_api_client()
schema = client.get('http://api.example.com/')

temp = tempfile.NamedTemporaryFile()
temp.write(b'example file contents')
temp.write(b'example file content')
temp.flush()

with open(temp.name, 'rb') as upload:
Expand All @@ -239,6 +389,58 @@ def test_raw_upload(self):

expected = {
'method': 'POST',
'files': {'name': name, 'contents': 'example file contents'}
'files': {'file': {'name': name, 'content': 'example file content'}},
'content_type': 'application/octet-stream'
}
assert data == expected

def test_raw_upload_string_file_content(self):
from coreapi.utils import File

client = get_api_client()
schema = client.get('http://api.example.com/')

example = File('example.txt', '123')
data = client.action(schema, ['encoding', 'raw_upload'], params={'example': example})

expected = {
'method': 'POST',
'files': {'file': {'name': 'example.txt', 'content': '123'}},
'content_type': 'text/plain'
}
assert data == expected

def test_raw_upload_explicit_content_type(self):
from coreapi.utils import File

client = get_api_client()
schema = client.get('http://api.example.com/')

example = File('example.txt', '123', 'text/html')
data = client.action(schema, ['encoding', 'raw_upload'], params={'example': example})

expected = {
'method': 'POST',
'files': {'file': {'name': 'example.txt', 'content': '123'}},
'content_type': 'text/html'
}
assert data == expected

# Responses

def test_text_response(self):
client = get_api_client()
schema = client.get('http://api.example.com/')

data = client.action(schema, ['response', 'text'])

expected = '123'
assert data == expected

def test_download_response(self):
client = get_api_client()
schema = client.get('http://api.example.com/')

data = client.action(schema, ['response', 'download'])
assert data.basename == 'download.png'
assert data.read() == b'some file content'