mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-02-16 19:41:06 +03:00
Added tests
This commit is contained in:
parent
650111dc8c
commit
8a12f89aaa
|
@ -3,3 +3,5 @@ syntax: glob
|
||||||
*.pyc
|
*.pyc
|
||||||
*.db
|
*.db
|
||||||
env
|
env
|
||||||
|
.project
|
||||||
|
.pydevproject
|
||||||
|
|
13
README.txt
Normal file
13
README.txt
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# To install django-rest-framework...
|
||||||
|
#
|
||||||
|
# Requirements:
|
||||||
|
# python2.6
|
||||||
|
# virtualenv
|
||||||
|
|
||||||
|
hg clone https://tomchristie@bitbucket.org/tomchristie/django-rest-framework
|
||||||
|
cd django-rest-framework/
|
||||||
|
virtualenv --no-site-packages --distribute --python=python2.6 env
|
||||||
|
source ./env/bin/activate
|
||||||
|
pip install -r ./requirements.txt
|
||||||
|
python ./src/manage.py test
|
||||||
|
|
|
@ -4,6 +4,14 @@ from rest import emitters, parsers
|
||||||
|
|
||||||
class Resource(object):
|
class Resource(object):
|
||||||
|
|
||||||
|
class HTTPException(Exception):
|
||||||
|
def __init__(self, status, content, headers):
|
||||||
|
self.status = status
|
||||||
|
self.content = content
|
||||||
|
self.headers = headers
|
||||||
|
|
||||||
|
allowed_methods = ('GET',)
|
||||||
|
|
||||||
callmap = { 'GET': 'read', 'POST': 'create',
|
callmap = { 'GET': 'read', 'POST': 'create',
|
||||||
'PUT': 'update', 'DELETE': 'delete' }
|
'PUT': 'update', 'DELETE': 'delete' }
|
||||||
|
|
||||||
|
@ -17,6 +25,7 @@ class Resource(object):
|
||||||
'application/xml': parsers.XMLParser,
|
'application/xml': parsers.XMLParser,
|
||||||
'application/x-www-form-urlencoded': parsers.FormParser }
|
'application/x-www-form-urlencoded': parsers.FormParser }
|
||||||
|
|
||||||
|
|
||||||
def __new__(cls, request, *args, **kwargs):
|
def __new__(cls, request, *args, **kwargs):
|
||||||
self = object.__new__(cls)
|
self = object.__new__(cls)
|
||||||
self.__init__()
|
self.__init__()
|
||||||
|
@ -28,7 +37,6 @@ class Resource(object):
|
||||||
def _determine_parser(self, request):
|
def _determine_parser(self, request):
|
||||||
"""Return the appropriate parser for the input, given the client's 'Content-Type' header,
|
"""Return the appropriate parser for the input, given the client's 'Content-Type' header,
|
||||||
and the content types that this Resource knows how to parse."""
|
and the content types that this Resource knows how to parse."""
|
||||||
print request.META
|
|
||||||
return self.parsers.values()[0]
|
return self.parsers.values()[0]
|
||||||
|
|
||||||
# TODO: Raise 415 Unsupported media type
|
# TODO: Raise 415 Unsupported media type
|
||||||
|
@ -79,25 +87,40 @@ class Resource(object):
|
||||||
(accept_mimetype == mimetype)):
|
(accept_mimetype == mimetype)):
|
||||||
return (mimetype, emitter)
|
return (mimetype, emitter)
|
||||||
|
|
||||||
# TODO: Raise 406, Not Acceptable
|
raise self.HTTPException(406, {'status': 'Not Acceptable',
|
||||||
|
'accepts': ','.join(item[0] for item in self.emitters)}, {})
|
||||||
|
|
||||||
|
|
||||||
def _handle_request(self, request, *args, **kwargs):
|
def _handle_request(self, request, *args, **kwargs):
|
||||||
meth = request.method
|
method = request.method
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not method in self.allowed_methods:
|
||||||
|
raise self.HTTPException(405, {'status': 'Method Not Allowed'}, {})
|
||||||
|
|
||||||
# Parse the HTTP Request content
|
# Parse the HTTP Request content
|
||||||
if meth in ('PUT', 'POST'):
|
func = getattr(self, self.callmap.get(method, ''))
|
||||||
|
|
||||||
|
if method in ('PUT', 'POST'):
|
||||||
parser = self._determine_parser(request)
|
parser = self._determine_parser(request)
|
||||||
data = parser(self, request).parse(request.raw_post_data)
|
data = parser(self, request).parse(request.raw_post_data)
|
||||||
|
(status, ret, headers) = func(data, request.META, *args, **kwargs)
|
||||||
|
|
||||||
if meth == "POST":
|
|
||||||
(status, ret, headers) = self.handle_post(data, request.META, *args, **kwargs)
|
|
||||||
else:
|
else:
|
||||||
(status, ret, headers) = self.handle_get(request.META, *args, **kwargs)
|
(status, ret, headers) = func(request.META, *args, **kwargs)
|
||||||
|
except self.HTTPException, exc:
|
||||||
|
(status, ret, headers) = (exc.status, exc.content, exc.headers)
|
||||||
|
|
||||||
|
headers['Allow'] = ', '.join(self.allowed_methods)
|
||||||
|
|
||||||
# Serialize the HTTP Response content
|
# Serialize the HTTP Response content
|
||||||
|
try:
|
||||||
mimetype, emitter = self._determine_emitter(request)
|
mimetype, emitter = self._determine_emitter(request)
|
||||||
|
except self.HTTPException, exc:
|
||||||
|
(status, ret, headers) = (exc.status, exc.content, exc.headers)
|
||||||
|
mimetype, emitter = self.emitters[0]
|
||||||
|
|
||||||
content = emitter(self, status, headers).emit(ret)
|
content = emitter(self, status, headers).emit(ret)
|
||||||
print mimetype, emitter, content
|
|
||||||
|
|
||||||
# Build the HTTP Response
|
# Build the HTTP Response
|
||||||
resp = HttpResponse(content, mimetype=mimetype, status=status)
|
resp = HttpResponse(content, mimetype=mimetype, status=status)
|
||||||
|
@ -106,8 +129,19 @@ class Resource(object):
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
def handle_get(self):
|
def _not_implemented(self, operation):
|
||||||
raise NotImplementedError(self.handle_get)
|
resource_name = self.__class__.__name__
|
||||||
|
return (500, {'status': 'Internal Server Error',
|
||||||
|
'detail': '%s %s operation is permitted but has not been implemented' % (resource_name, operation)}, {})
|
||||||
|
|
||||||
def handle_post(self):
|
def read(self, headers={}, *args, **kwargs):
|
||||||
raise NotImplementedError(self.handle_post)
|
return self._not_implemented('read')
|
||||||
|
|
||||||
|
def create(self, data=None, headers={}, *args, **kwargs):
|
||||||
|
return self._not_implemented('create')
|
||||||
|
|
||||||
|
def update(self, data=None, headers={}, *args, **kwargs):
|
||||||
|
return self._not_implemented('update')
|
||||||
|
|
||||||
|
def delete(self, headers={}, *args, **kwargs):
|
||||||
|
return self._not_implemented('delete')
|
||||||
|
|
|
@ -75,7 +75,7 @@ MIDDLEWARE_CLASSES = (
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
)
|
)
|
||||||
|
|
||||||
ROOT_URLCONF = 'src.urls'
|
ROOT_URLCONF = 'urls'
|
||||||
|
|
||||||
TEMPLATE_DIRS = (
|
TEMPLATE_DIRS = (
|
||||||
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
|
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
|
||||||
|
@ -93,5 +93,6 @@ INSTALLED_APPS = (
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
# Uncomment the next line to enable admin documentation:
|
# Uncomment the next line to enable admin documentation:
|
||||||
# 'django.contrib.admindocs',
|
# 'django.contrib.admindocs',
|
||||||
'testarchive',
|
'testapp',
|
||||||
|
'rest',
|
||||||
)
|
)
|
||||||
|
|
54
src/testapp/tests.py
Normal file
54
src/testapp/tests.py
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
"""
|
||||||
|
This file demonstrates two different styles of tests (one doctest and one
|
||||||
|
unittest). These will both pass when you run "manage.py test".
|
||||||
|
|
||||||
|
Replace these with more appropriate tests for your application.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from testapp.views import ReadOnlyResource, MirroringWriteResource
|
||||||
|
|
||||||
|
class AcceptHeaderTests(TestCase):
|
||||||
|
def assert_accept_mimetype(self, mimetype, expect=None, expect_match=True):
|
||||||
|
"""
|
||||||
|
Assert that a request with given mimetype in the accept header,
|
||||||
|
gives a response with the appropriate content-type.
|
||||||
|
"""
|
||||||
|
if expect is None:
|
||||||
|
expect = mimetype
|
||||||
|
|
||||||
|
resp = self.client.get(reverse(ReadOnlyResource), HTTP_ACCEPT=mimetype)
|
||||||
|
|
||||||
|
if expect_match:
|
||||||
|
self.assertEquals(resp['content-type'], expect)
|
||||||
|
else:
|
||||||
|
self.assertNotEquals(resp['content-type'], expect)
|
||||||
|
|
||||||
|
def test_accept_xml(self):
|
||||||
|
self.assert_accept_mimetype('application/xml')
|
||||||
|
|
||||||
|
def test_accept_json(self):
|
||||||
|
self.assert_accept_mimetype('application/json')
|
||||||
|
|
||||||
|
def test_accept_xml_prefered_to_json(self):
|
||||||
|
self.assert_accept_mimetype('application/xml,q=0.9;application/json,q=0.1', expect='application/xml')
|
||||||
|
|
||||||
|
def test_accept_json_prefered_to_xml(self):
|
||||||
|
self.assert_accept_mimetype('application/json,q=0.9;application/xml,q=0.1', expect='application/json')
|
||||||
|
|
||||||
|
def test_dont_accept_invalid(self):
|
||||||
|
self.assert_accept_mimetype('application/invalid', expect_match=False)
|
||||||
|
|
||||||
|
def test_invalid_accept_header_returns_406(self):
|
||||||
|
resp = self.client.get(reverse(ReadOnlyResource), HTTP_ACCEPT='invalid/invalid')
|
||||||
|
self.assertEquals(resp.status_code, 406)
|
||||||
|
|
||||||
|
class AllowedMethodsTests(TestCase):
|
||||||
|
def test_write_on_read_only_resource_returns_405(self):
|
||||||
|
resp = self.client.put(reverse(ReadOnlyResource), {})
|
||||||
|
self.assertEquals(resp.status_code, 405)
|
||||||
|
|
||||||
|
def test_read_on_write_only_resource_returns_405(self):
|
||||||
|
resp = self.client.get(reverse(MirroringWriteResource))
|
||||||
|
self.assertEquals(resp.status_code, 405)
|
8
src/testapp/urls.py
Normal file
8
src/testapp/urls.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
from django.conf.urls.defaults import patterns
|
||||||
|
from testapp.views import ReadOnlyResource, MirroringWriteResource
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = patterns('',
|
||||||
|
(r'^read-only$', ReadOnlyResource),
|
||||||
|
(r'^mirroring-write$', MirroringWriteResource),
|
||||||
|
)
|
21
src/testapp/views.py
Normal file
21
src/testapp/views.py
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
from decimal import Decimal
|
||||||
|
from rest.resource import Resource
|
||||||
|
|
||||||
|
class ReadOnlyResource(Resource):
|
||||||
|
"""This is my docstring
|
||||||
|
"""
|
||||||
|
allowed_methods = ('GET',)
|
||||||
|
|
||||||
|
def read(self, headers={}, *args, **kwargs):
|
||||||
|
return (200, {'ExampleString': 'Example',
|
||||||
|
'ExampleInt': 1,
|
||||||
|
'ExampleDecimal': 1.0}, {})
|
||||||
|
|
||||||
|
|
||||||
|
class MirroringWriteResource(Resource):
|
||||||
|
"""This is my docstring
|
||||||
|
"""
|
||||||
|
allowed_methods = ('PUT',)
|
||||||
|
|
||||||
|
def create(self, data, headers={}, *args, **kwargs):
|
||||||
|
return (200, data, {})
|
|
@ -1,23 +0,0 @@
|
||||||
"""
|
|
||||||
This file demonstrates two different styles of tests (one doctest and one
|
|
||||||
unittest). These will both pass when you run "manage.py test".
|
|
||||||
|
|
||||||
Replace these with more appropriate tests for your application.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
class SimpleTest(TestCase):
|
|
||||||
def test_basic_addition(self):
|
|
||||||
"""
|
|
||||||
Tests that 1 + 1 always equals 2.
|
|
||||||
"""
|
|
||||||
self.failUnlessEqual(1 + 1, 3)
|
|
||||||
|
|
||||||
__test__ = {"doctest": """
|
|
||||||
Another way to test that 1 + 1 is equal to 2.
|
|
||||||
|
|
||||||
>>> 1 + 1 == 2
|
|
||||||
True
|
|
||||||
"""}
|
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
from django.conf.urls.defaults import patterns
|
|
||||||
from testarchive.views import RootResource
|
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
|
||||||
(r'^$', RootResource),
|
|
||||||
)
|
|
|
@ -1,8 +0,0 @@
|
||||||
from rest.resource import Resource
|
|
||||||
|
|
||||||
class RootResource(Resource):
|
|
||||||
"""This is my docstring
|
|
||||||
"""
|
|
||||||
|
|
||||||
def handle_get(self, headers={}, *args, **kwargs):
|
|
||||||
return (200, {'Name': 'Test', 'Value': 1}, {'Location': 'BLAH'})
|
|
|
@ -5,7 +5,7 @@ admin.autodiscover()
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
# Example:
|
# Example:
|
||||||
(r'^testarchive/', include('testarchive.urls')),
|
(r'^testapp/', include('testapp.urls')),
|
||||||
|
|
||||||
# Uncomment the admin/doc line below to enable admin documentation:
|
# Uncomment the admin/doc line below to enable admin documentation:
|
||||||
(r'^admin/doc/', include('django.contrib.admindocs.urls')),
|
(r'^admin/doc/', include('django.contrib.admindocs.urls')),
|
||||||
|
|
Loading…
Reference in New Issue
Block a user