aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--AUTHORS6
-rw-r--r--djangorestframework/compat.py12
-rw-r--r--djangorestframework/mixins.py25
-rw-r--r--djangorestframework/renderers.py17
-rw-r--r--djangorestframework/resources.py6
-rw-r--r--djangorestframework/response.py4
-rw-r--r--djangorestframework/runtests/settings.py16
-rw-r--r--djangorestframework/templates/renderer.html8
-rw-r--r--djangorestframework/tests/content.py10
-rw-r--r--djangorestframework/tests/methods.py6
-rw-r--r--djangorestframework/tests/oauthentication.py399
-rw-r--r--djangorestframework/tests/renderers.py64
-rw-r--r--djangorestframework/tests/reverse.py6
-rw-r--r--examples/requirements.txt8
14 files changed, 353 insertions, 234 deletions
diff --git a/AUTHORS b/AUTHORS
index a85b7ff8..95ffded1 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,11 +1,13 @@
-Tom Christie <tomchristie> - tom@tomchristie.com, @thisneonsoul
-
+Tom Christie <tomchristie> - tom@tomchristie.com, @thisneonsoul - Author.
Paul Bagwell <pbgwl> - Suggestions & bugfixes.
Marko Tibold <markotibold> - Contributions & Providing the Jenkins CI Server.
Sébastien Piquemal <sebpiq> - Contributions.
Carmen Wick <cwick> - Bugfixes.
Alex Ehlke <aehlke> - Design Contributions.
Alen Mujezinovic <flashingpumpkin> - Contributions.
+Carles Barrobés <txels> - HEAD support.
+Michael Fötsch <mfoetsch> - File format support.
+David Larlet <david> - OAuth support.
THANKS TO:
diff --git a/djangorestframework/compat.py b/djangorestframework/compat.py
index 0274511a..827b4adf 100644
--- a/djangorestframework/compat.py
+++ b/djangorestframework/compat.py
@@ -67,6 +67,14 @@ except ImportError:
# django.views.generic.View (Django >= 1.3)
try:
from django.views.generic import View
+ if not hasattr(View, 'head'):
+ # First implementation of Django class-based views did not include head method
+ # in base View class - https://code.djangoproject.com/ticket/15668
+ class ViewPlusHead(View):
+ def head(self, request, *args, **kwargs):
+ return self.get(request, *args, **kwargs)
+ View = ViewPlusHead
+
except ImportError:
from django import http
from django.utils.functional import update_wrapper
@@ -145,6 +153,8 @@ except ImportError:
#)
return http.HttpResponseNotAllowed(allowed_methods)
+ def head(self, request, *args, **kwargs):
+ return self.get(request, *args, **kwargs)
try:
import markdown
@@ -193,4 +203,4 @@ try:
return md.convert(text)
except ImportError:
- apply_markdown = None \ No newline at end of file
+ apply_markdown = None
diff --git a/djangorestframework/mixins.py b/djangorestframework/mixins.py
index 910d06ae..1b3aa241 100644
--- a/djangorestframework/mixins.py
+++ b/djangorestframework/mixins.py
@@ -11,6 +11,7 @@ from django.http.multipartparser import LimitBytes
from djangorestframework import status
from djangorestframework.parsers import FormParser, MultiPartParser
+from djangorestframework.renderers import BaseRenderer
from djangorestframework.resources import Resource, FormResource, ModelResource
from djangorestframework.response import Response, ErrorResponse
from djangorestframework.utils import as_tuple, MSIE_USER_AGENT_REGEX
@@ -290,7 +291,7 @@ class ResponseMixin(object):
accept_list = [token.strip() for token in request.META["HTTP_ACCEPT"].split(',')]
else:
# No accept header specified
- return (self._default_renderer(self), self._default_renderer.media_type)
+ accept_list = ['*/*']
# Check the acceptable media types against each renderer,
# attempting more specific media types first
@@ -298,12 +299,12 @@ class ResponseMixin(object):
# Worst case is we're looping over len(accept_list) * len(self.renderers)
renderers = [renderer_cls(self) for renderer_cls in self.renderers]
- for media_type_lst in order_by_precedence(accept_list):
+ for accepted_media_type_lst in order_by_precedence(accept_list):
for renderer in renderers:
- for media_type in media_type_lst:
- if renderer.can_handle_response(media_type):
- return renderer, media_type
-
+ for accepted_media_type in accepted_media_type_lst:
+ if renderer.can_handle_response(accepted_media_type):
+ return renderer, accepted_media_type
+
# No acceptable renderers were found
raise ErrorResponse(status.HTTP_406_NOT_ACCEPTABLE,
{'detail': 'Could not satisfy the client\'s Accept header',
@@ -316,6 +317,13 @@ class ResponseMixin(object):
Return an list of all the media types that this view can render.
"""
return [renderer.media_type for renderer in self.renderers]
+
+ @property
+ def _rendered_formats(self):
+ """
+ Return a list of all the formats that this view can render.
+ """
+ return [renderer.format for renderer in self.renderers]
@property
def _default_renderer(self):
@@ -486,7 +494,10 @@ class ReadModelMixin(object):
instance = model.objects.get(pk=args[-1], **kwargs)
else:
# Otherwise assume the kwargs uniquely identify the model
- instance = model.objects.get(**kwargs)
+ filtered_keywords = kwargs.copy()
+ if BaseRenderer._FORMAT_QUERY_PARAM in filtered_keywords:
+ del filtered_keywords[BaseRenderer._FORMAT_QUERY_PARAM]
+ instance = model.objects.get(**filtered_keywords)
except model.DoesNotExist:
raise ErrorResponse(status.HTTP_404_NOT_FOUND)
diff --git a/djangorestframework/renderers.py b/djangorestframework/renderers.py
index 13cd52f5..e09e2abc 100644
--- a/djangorestframework/renderers.py
+++ b/djangorestframework/renderers.py
@@ -40,8 +40,11 @@ class BaseRenderer(object):
All renderers must extend this class, set the :attr:`media_type` attribute,
and override the :meth:`render` method.
"""
+
+ _FORMAT_QUERY_PARAM = 'format'
media_type = None
+ format = None
def __init__(self, view):
self.view = view
@@ -58,6 +61,11 @@ class BaseRenderer(object):
This may be overridden to provide for other behavior, but typically you'll
instead want to just set the :attr:`media_type` attribute on the class.
"""
+ format = self.view.kwargs.get(self._FORMAT_QUERY_PARAM, None)
+ if format is None:
+ format = self.view.request.GET.get(self._FORMAT_QUERY_PARAM, None)
+ if format is not None:
+ return format == self.format
return media_type_matches(self.media_type, accept)
def render(self, obj=None, media_type=None):
@@ -84,6 +92,7 @@ class JSONRenderer(BaseRenderer):
"""
media_type = 'application/json'
+ format = 'json'
def render(self, obj=None, media_type=None):
"""
@@ -111,6 +120,7 @@ class XMLRenderer(BaseRenderer):
"""
media_type = 'application/xml'
+ format = 'xml'
def render(self, obj=None, media_type=None):
"""
@@ -289,12 +299,12 @@ class DocumentingTemplateRenderer(BaseRenderer):
'version': VERSION,
'markeddown': markeddown,
'breadcrumblist': breadcrumb_list,
- 'available_media_types': self.view._rendered_media_types,
+ 'available_formats': self.view._rendered_formats,
'put_form': put_form_instance,
'post_form': post_form_instance,
'login_url': login_url,
'logout_url': logout_url,
- 'ACCEPT_PARAM': getattr(self.view, '_ACCEPT_QUERY_PARAM', None),
+ 'FORMAT_PARAM': self._FORMAT_QUERY_PARAM,
'METHOD_PARAM': getattr(self.view, '_METHOD_PARAM', None),
'ADMIN_MEDIA_PREFIX': settings.ADMIN_MEDIA_PREFIX
})
@@ -317,6 +327,7 @@ class DocumentingHTMLRenderer(DocumentingTemplateRenderer):
"""
media_type = 'text/html'
+ format = 'html'
template = 'renderer.html'
@@ -328,6 +339,7 @@ class DocumentingXHTMLRenderer(DocumentingTemplateRenderer):
"""
media_type = 'application/xhtml+xml'
+ format = 'xhtml'
template = 'renderer.html'
@@ -339,6 +351,7 @@ class DocumentingPlainTextRenderer(DocumentingTemplateRenderer):
"""
media_type = 'text/plain'
+ format = 'txt'
template = 'renderer.txt'
diff --git a/djangorestframework/resources.py b/djangorestframework/resources.py
index 08f9e0ae..b42bd952 100644
--- a/djangorestframework/resources.py
+++ b/djangorestframework/resources.py
@@ -6,7 +6,7 @@ from django.db.models.fields.related import RelatedField
from django.utils.encoding import smart_unicode
from djangorestframework.response import ErrorResponse
-from djangorestframework.serializer import Serializer
+from djangorestframework.serializer import Serializer, _SkipField
from djangorestframework.utils import as_tuple
import decimal
@@ -342,7 +342,7 @@ class ModelResource(FormResource):
"""
if not hasattr(self, 'view_callable'):
- raise NoReverseMatch
+ raise _SkipField
# dis does teh magicks...
urlconf = get_urlconf()
@@ -371,7 +371,7 @@ class ModelResource(FormResource):
return reverse(self.view_callable[0], kwargs=instance_attrs)
except NoReverseMatch:
pass
- raise NoReverseMatch
+ raise _SkipField
@property
diff --git a/djangorestframework/response.py b/djangorestframework/response.py
index d68ececf..311e0bb7 100644
--- a/djangorestframework/response.py
+++ b/djangorestframework/response.py
@@ -16,13 +16,13 @@ class Response(object):
An HttpResponse that may include content that hasn't yet been serialized.
"""
- def __init__(self, status=200, content=None, headers={}):
+ def __init__(self, status=200, content=None, headers=None):
self.status = status
self.media_type = None
self.has_content_body = content is not None
self.raw_content = content # content prior to filtering
self.cleaned_content = content # content after filtering
- self.headers = headers
+ self.headers = headers or {}
@property
def status_text(self):
diff --git a/djangorestframework/runtests/settings.py b/djangorestframework/runtests/settings.py
index 1b63256c..006727bc 100644
--- a/djangorestframework/runtests/settings.py
+++ b/djangorestframework/runtests/settings.py
@@ -2,6 +2,7 @@
DEBUG = True
TEMPLATE_DEBUG = DEBUG
+DEBUG_PROPAGATE_EXCEPTIONS = True
ADMINS = (
# ('Your Name', 'your_email@domain.com'),
@@ -83,7 +84,7 @@ TEMPLATE_DIRS = (
# Don't forget to use absolute paths, not relative paths.
)
-INSTALLED_APPS = (
+INSTALLED_APPS = [
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
@@ -94,10 +95,17 @@ INSTALLED_APPS = (
# Uncomment the next line to enable admin documentation:
# 'django.contrib.admindocs',
'djangorestframework',
-
- 'oauth_provider',
-)
+]
+
+# OAuth support is optional, so we only test oauth if it's installed.
+try:
+ import oauth_provider
+except:
+ pass
+else:
+ INSTALLED_APPS.append('oauth_provider')
+# If we're running on the Jenkins server we want to archive the coverage reports as XML.
import os
if os.environ.get('HUDSON_URL', None):
TEST_RUNNER = 'xmlrunner.extra.djangotestrunner.XMLTestRunner'
diff --git a/djangorestframework/templates/renderer.html b/djangorestframework/templates/renderer.html
index 97d3837a..5b32d1ec 100644
--- a/djangorestframework/templates/renderer.html
+++ b/djangorestframework/templates/renderer.html
@@ -48,9 +48,9 @@
<h2>GET {{ name }}</h2>
<div class='submit-row' style='margin: 0; border: 0'>
<a href='{{ request.get_full_path }}' rel="nofollow" style='float: left'>GET</a>
- {% for media_type in available_media_types %}
- {% with ACCEPT_PARAM|add:"="|add:media_type as param %}
- [<a href='{{ request.get_full_path|add_query_param:param }}' rel="nofollow">{{ media_type }}</a>]
+ {% for format in available_formats %}
+ {% with FORMAT_PARAM|add:"="|add:format as param %}
+ [<a href='{{ request.get_full_path|add_query_param:param }}' rel="nofollow">{{ format }}</a>]
{% endwith %}
{% endfor %}
</div>
@@ -122,4 +122,4 @@
</div>
</div>
</body>
-</html> \ No newline at end of file
+</html>
diff --git a/djangorestframework/tests/content.py b/djangorestframework/tests/content.py
index ee3597a4..83ad72d0 100644
--- a/djangorestframework/tests/content.py
+++ b/djangorestframework/tests/content.py
@@ -6,7 +6,6 @@ from djangorestframework.compat import RequestFactory
from djangorestframework.mixins import RequestMixin
from djangorestframework.parsers import FormParser, MultiPartParser, PlainTextParser
-
class TestContentParsing(TestCase):
def setUp(self):
self.req = RequestFactory()
@@ -16,6 +15,11 @@ class TestContentParsing(TestCase):
view.request = self.req.get('/')
self.assertEqual(view.DATA, None)
+ def ensure_determines_no_content_HEAD(self, view):
+ """Ensure view.DATA returns None for HEAD request."""
+ view.request = self.req.head('/')
+ self.assertEqual(view.DATA, None)
+
def ensure_determines_form_content_POST(self, view):
"""Ensure view.DATA returns content for POST request with form content."""
form_data = {'qwerty': 'uiop'}
@@ -50,6 +54,10 @@ class TestContentParsing(TestCase):
"""Ensure view.DATA returns None for GET request with no content."""
self.ensure_determines_no_content_GET(RequestMixin())
+ def test_standard_behaviour_determines_no_content_HEAD(self):
+ """Ensure view.DATA returns None for HEAD request."""
+ self.ensure_determines_no_content_HEAD(RequestMixin())
+
def test_standard_behaviour_determines_form_content_POST(self):
"""Ensure view.DATA returns content for POST request with form content."""
self.ensure_determines_form_content_POST(RequestMixin())
diff --git a/djangorestframework/tests/methods.py b/djangorestframework/tests/methods.py
index d8f0d919..c3a3a28d 100644
--- a/djangorestframework/tests/methods.py
+++ b/djangorestframework/tests/methods.py
@@ -24,3 +24,9 @@ class TestMethodOverloading(TestCase):
view = RequestMixin()
view.request = self.req.post('/', {view._METHOD_PARAM: 'DELETE'})
self.assertEqual(view.method, 'DELETE')
+
+ def test_HEAD_is_a_valid_method(self):
+ """HEAD requests identified"""
+ view = RequestMixin()
+ view.request = self.req.head('/')
+ self.assertEqual(view.method, 'HEAD')
diff --git a/djangorestframework/tests/oauthentication.py b/djangorestframework/tests/oauthentication.py
index 0604ddad..7f74b804 100644
--- a/djangorestframework/tests/oauthentication.py
+++ b/djangorestframework/tests/oauthentication.py
@@ -6,198 +6,207 @@ from django.test import Client, TestCase
from djangorestframework.views import View
-import oauth2 as oauth
-from oauth_provider.decorators import oauth_required
-from oauth_provider.models import Resource, Consumer, Token
-
-
-class ClientView(View):
- def get(self, request):
- return {'resource': 'Protected!'}
-
-urlpatterns = patterns('',
- url(r'^$', oauth_required(ClientView.as_view())),
- url(r'^oauth/', include('oauth_provider.urls')),
- url(r'^accounts/login/$', 'djangorestframework.utils.staticviews.api_login'),
-)
-
-
-class OAuthTests(TestCase):
- """
- OAuth authentication:
- * the user would like to access his API data from a third-party website
- * the third-party website proposes a link to get that API data
- * the user is redirected to the API and must log in if not authenticated
- * the API displays a webpage to confirm that the user trusts the third-party website
- * if confirmed, the user is redirected to the third-party website through the callback view
- * the third-party website is able to retrieve data from the API
- """
- urls = 'djangorestframework.tests.oauthentication'
-
- def setUp(self):
- self.client = Client()
- self.username = 'john'
- self.email = 'lennon@thebeatles.com'
- self.password = 'password'
- self.user = User.objects.create_user(self.username, self.email, self.password)
-
- # OAuth requirements
- self.resource = Resource(name='data', url='/')
- self.resource.save()
- self.CONSUMER_KEY = 'dpf43f3p2l4k3l03'
- self.CONSUMER_SECRET = 'kd94hf93k423kf44'
- self.consumer = Consumer(key=self.CONSUMER_KEY, secret=self.CONSUMER_SECRET,
- name='api.example.com', user=self.user)
- self.consumer.save()
-
- def test_oauth_invalid_and_anonymous_access(self):
- """
- Verify that the resource is protected and the OAuth authorization view
- require the user to be logged in.
- """
- response = self.client.get('/')
- self.assertEqual(response.content, 'Invalid request parameters.')
- self.assertEqual(response.status_code, 401)
- response = self.client.get('/oauth/authorize/', follow=True)
- self.assertRedirects(response, '/accounts/login/?next=/oauth/authorize/')
-
- def test_oauth_authorize_access(self):
- """
- Verify that once logged in, the user can access the authorization page
- but can't display the page because the request token is not specified.
- """
- self.client.login(username=self.username, password=self.password)
- response = self.client.get('/oauth/authorize/', follow=True)
- self.assertEqual(response.content, 'No request token specified.')
-
- def _create_request_token_parameters(self):
- """
- A shortcut to create request's token parameters.
- """
- return {
- 'oauth_consumer_key': self.CONSUMER_KEY,
- 'oauth_signature_method': 'PLAINTEXT',
- 'oauth_signature': '%s&' % self.CONSUMER_SECRET,
- 'oauth_timestamp': str(int(time.time())),
- 'oauth_nonce': 'requestnonce',
- 'oauth_version': '1.0',
- 'oauth_callback': 'http://api.example.com/request_token_ready',
- 'scope': 'data',
- }
-
- def test_oauth_request_token_retrieval(self):
- """
- Verify that the request token can be retrieved by the server.
- """
- response = self.client.get("/oauth/request_token/",
- self._create_request_token_parameters())
- self.assertEqual(response.status_code, 200)
- token = list(Token.objects.all())[-1]
- self.failIf(token.key not in response.content)
- self.failIf(token.secret not in response.content)
-
- def test_oauth_user_request_authorization(self):
- """
- Verify that the user can access the authorization page once logged in
- and the request token has been retrieved.
- """
- # Setup
- response = self.client.get("/oauth/request_token/",
- self._create_request_token_parameters())
- token = list(Token.objects.all())[-1]
-
- # Starting the test here
- self.client.login(username=self.username, password=self.password)
- parameters = {'oauth_token': token.key,}
- response = self.client.get("/oauth/authorize/", parameters)
- self.assertEqual(response.status_code, 200)
- self.failIf(not response.content.startswith('Fake authorize view for api.example.com with params: oauth_token='))
- self.assertEqual(token.is_approved, 0)
- parameters['authorize_access'] = 1 # fake authorization by the user
- response = self.client.post("/oauth/authorize/", parameters)
- self.assertEqual(response.status_code, 302)
- self.failIf(not response['Location'].startswith('http://api.example.com/request_token_ready?oauth_verifier='))
- token = Token.objects.get(key=token.key)
- self.failIf(token.key not in response['Location'])
- self.assertEqual(token.is_approved, 1)
-
- def _create_access_token_parameters(self, token):
- """
- A shortcut to create access' token parameters.
- """
- return {
- 'oauth_consumer_key': self.CONSUMER_KEY,
- 'oauth_token': token.key,
- 'oauth_signature_method': 'PLAINTEXT',
- 'oauth_signature': '%s&%s' % (self.CONSUMER_SECRET, token.secret),
- 'oauth_timestamp': str(int(time.time())),
- 'oauth_nonce': 'accessnonce',
- 'oauth_version': '1.0',
- 'oauth_verifier': token.verifier,
- 'scope': 'data',
- }
-
- def test_oauth_access_token_retrieval(self):
- """
- Verify that the request token can be retrieved by the server.
- """
- # Setup
- response = self.client.get("/oauth/request_token/",
- self._create_request_token_parameters())
- token = list(Token.objects.all())[-1]
- self.client.login(username=self.username, password=self.password)
- parameters = {'oauth_token': token.key,}
- response = self.client.get("/oauth/authorize/", parameters)
- parameters['authorize_access'] = 1 # fake authorization by the user
- response = self.client.post("/oauth/authorize/", parameters)
- token = Token.objects.get(key=token.key)
-
- # Starting the test here
- response = self.client.get("/oauth/access_token/", self._create_access_token_parameters(token))
- self.assertEqual(response.status_code, 200)
- self.failIf(not response.content.startswith('oauth_token_secret='))
- access_token = list(Token.objects.filter(token_type=Token.ACCESS))[-1]
- self.failIf(access_token.key not in response.content)
- self.failIf(access_token.secret not in response.content)
- self.assertEqual(access_token.user.username, 'john')
-
- def _create_access_parameters(self, access_token):
- """
- A shortcut to create access' parameters.
- """
- parameters = {
- 'oauth_consumer_key': self.CONSUMER_KEY,
- 'oauth_token': access_token.key,
- 'oauth_signature_method': 'HMAC-SHA1',
- 'oauth_timestamp': str(int(time.time())),
- 'oauth_nonce': 'accessresourcenonce',
- 'oauth_version': '1.0',
- }
- oauth_request = oauth.Request.from_token_and_callback(access_token,
- http_url='http://testserver/', parameters=parameters)
- signature_method = oauth.SignatureMethod_HMAC_SHA1()
- signature = signature_method.sign(oauth_request, self.consumer, access_token)
- parameters['oauth_signature'] = signature
- return parameters
-
- def test_oauth_protected_resource_access(self):
- """
- Verify that the request token can be retrieved by the server.
- """
- # Setup
- response = self.client.get("/oauth/request_token/",
- self._create_request_token_parameters())
- token = list(Token.objects.all())[-1]
- self.client.login(username=self.username, password=self.password)
- parameters = {'oauth_token': token.key,}
- response = self.client.get("/oauth/authorize/", parameters)
- parameters['authorize_access'] = 1 # fake authorization by the user
- response = self.client.post("/oauth/authorize/", parameters)
- token = Token.objects.get(key=token.key)
- response = self.client.get("/oauth/access_token/", self._create_access_token_parameters(token))
- access_token = list(Token.objects.filter(token_type=Token.ACCESS))[-1]
-
- # Starting the test here
- response = self.client.get("/", self._create_access_token_parameters(access_token))
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.content, '{"resource": "Protected!"}')
+# Since oauth2 / django-oauth-plus are optional dependancies, we don't want to
+# always run these tests.
+
+# Unfortunatly we can't skip tests easily until 2.7, se we'll just do this for now.
+try:
+ import oauth2 as oauth
+ from oauth_provider.decorators import oauth_required
+ from oauth_provider.models import Resource, Consumer, Token
+
+except:
+ pass
+
+else:
+ # Alrighty, we're good to go here.
+ class ClientView(View):
+ def get(self, request):
+ return {'resource': 'Protected!'}
+
+ urlpatterns = patterns('',
+ url(r'^$', oauth_required(ClientView.as_view())),
+ url(r'^oauth/', include('oauth_provider.urls')),
+ url(r'^accounts/login/$', 'djangorestframework.utils.staticviews.api_login'),
+ )
+
+
+ class OAuthTests(TestCase):
+ """
+ OAuth authentication:
+ * the user would like to access his API data from a third-party website
+ * the third-party website proposes a link to get that API data
+ * the user is redirected to the API and must log in if not authenticated
+ * the API displays a webpage to confirm that the user trusts the third-party website
+ * if confirmed, the user is redirected to the third-party website through the callback view
+ * the third-party website is able to retrieve data from the API
+ """
+ urls = 'djangorestframework.tests.oauthentication'
+
+ def setUp(self):
+ self.client = Client()
+ self.username = 'john'
+ self.email = 'lennon@thebeatles.com'
+ self.password = 'password'
+ self.user = User.objects.create_user(self.username, self.email, self.password)
+
+ # OAuth requirements
+ self.resource = Resource(name='data', url='/')
+ self.resource.save()
+ self.CONSUMER_KEY = 'dpf43f3p2l4k3l03'
+ self.CONSUMER_SECRET = 'kd94hf93k423kf44'
+ self.consumer = Consumer(key=self.CONSUMER_KEY, secret=self.CONSUMER_SECRET,
+ name='api.example.com', user=self.user)
+ self.consumer.save()
+
+ def test_oauth_invalid_and_anonymous_access(self):
+ """
+ Verify that the resource is protected and the OAuth authorization view
+ require the user to be logged in.
+ """
+ response = self.client.get('/')
+ self.assertEqual(response.content, 'Invalid request parameters.')
+ self.assertEqual(response.status_code, 401)
+ response = self.client.get('/oauth/authorize/', follow=True)
+ self.assertRedirects(response, '/accounts/login/?next=/oauth/authorize/')
+
+ def test_oauth_authorize_access(self):
+ """
+ Verify that once logged in, the user can access the authorization page
+ but can't display the page because the request token is not specified.
+ """
+ self.client.login(username=self.username, password=self.password)
+ response = self.client.get('/oauth/authorize/', follow=True)
+ self.assertEqual(response.content, 'No request token specified.')
+
+ def _create_request_token_parameters(self):
+ """
+ A shortcut to create request's token parameters.
+ """
+ return {
+ 'oauth_consumer_key': self.CONSUMER_KEY,
+ 'oauth_signature_method': 'PLAINTEXT',
+ 'oauth_signature': '%s&' % self.CONSUMER_SECRET,
+ 'oauth_timestamp': str(int(time.time())),
+ 'oauth_nonce': 'requestnonce',
+ 'oauth_version': '1.0',
+ 'oauth_callback': 'http://api.example.com/request_token_ready',
+ 'scope': 'data',
+ }
+
+ def test_oauth_request_token_retrieval(self):
+ """
+ Verify that the request token can be retrieved by the server.
+ """
+ response = self.client.get("/oauth/request_token/",
+ self._create_request_token_parameters())
+ self.assertEqual(response.status_code, 200)
+ token = list(Token.objects.all())[-1]
+ self.failIf(token.key not in response.content)
+ self.failIf(token.secret not in response.content)
+
+ def test_oauth_user_request_authorization(self):
+ """
+ Verify that the user can access the authorization page once logged in
+ and the request token has been retrieved.
+ """
+ # Setup
+ response = self.client.get("/oauth/request_token/",
+ self._create_request_token_parameters())
+ token = list(Token.objects.all())[-1]
+
+ # Starting the test here
+ self.client.login(username=self.username, password=self.password)
+ parameters = {'oauth_token': token.key,}
+ response = self.client.get("/oauth/authorize/", parameters)
+ self.assertEqual(response.status_code, 200)
+ self.failIf(not response.content.startswith('Fake authorize view for api.example.com with params: oauth_token='))
+ self.assertEqual(token.is_approved, 0)
+ parameters['authorize_access'] = 1 # fake authorization by the user
+ response = self.client.post("/oauth/authorize/", parameters)
+ self.assertEqual(response.status_code, 302)
+ self.failIf(not response['Location'].startswith('http://api.example.com/request_token_ready?oauth_verifier='))
+ token = Token.objects.get(key=token.key)
+ self.failIf(token.key not in response['Location'])
+ self.assertEqual(token.is_approved, 1)
+
+ def _create_access_token_parameters(self, token):
+ """
+ A shortcut to create access' token parameters.
+ """
+ return {
+ 'oauth_consumer_key': self.CONSUMER_KEY,
+ 'oauth_token': token.key,
+ 'oauth_signature_method': 'PLAINTEXT',
+ 'oauth_signature': '%s&%s' % (self.CONSUMER_SECRET, token.secret),
+ 'oauth_timestamp': str(int(time.time())),
+ 'oauth_nonce': 'accessnonce',
+ 'oauth_version': '1.0',
+ 'oauth_verifier': token.verifier,
+ 'scope': 'data',
+ }
+
+ def test_oauth_access_token_retrieval(self):
+ """
+ Verify that the request token can be retrieved by the server.
+ """
+ # Setup
+ response = self.client.get("/oauth/request_token/",
+ self._create_request_token_parameters())
+ token = list(Token.objects.all())[-1]
+ self.client.login(username=self.username, password=self.password)
+ parameters = {'oauth_token': token.key,}
+ response = self.client.get("/oauth/authorize/", parameters)
+ parameters['authorize_access'] = 1 # fake authorization by the user
+ response = self.client.post("/oauth/authorize/", parameters)
+ token = Token.objects.get(key=token.key)
+
+ # Starting the test here
+ response = self.client.get("/oauth/access_token/", self._create_access_token_parameters(token))
+ self.assertEqual(response.status_code, 200)
+ self.failIf(not response.content.startswith('oauth_token_secret='))
+ access_token = list(Token.objects.filter(token_type=Token.ACCESS))[-1]
+ self.failIf(access_token.key not in response.content)
+ self.failIf(access_token.secret not in response.content)
+ self.assertEqual(access_token.user.username, 'john')
+
+ def _create_access_parameters(self, access_token):
+ """
+ A shortcut to create access' parameters.
+ """
+ parameters = {
+ 'oauth_consumer_key': self.CONSUMER_KEY,
+ 'oauth_token': access_token.key,
+ 'oauth_signature_method': 'HMAC-SHA1',
+ 'oauth_timestamp': str(int(time.time())),
+ 'oauth_nonce': 'accessresourcenonce',
+ 'oauth_version': '1.0',
+ }
+ oauth_request = oauth.Request.from_token_and_callback(access_token,
+ http_url='http://testserver/', parameters=parameters)
+ signature_method = oauth.SignatureMethod_HMAC_SHA1()
+ signature = signature_method.sign(oauth_request, self.consumer, access_token)
+ parameters['oauth_signature'] = signature
+ return parameters
+
+ def test_oauth_protected_resource_access(self):
+ """
+ Verify that the request token can be retrieved by the server.
+ """
+ # Setup
+ response = self.client.get("/oauth/request_token/",
+ self._create_request_token_parameters())
+ token = list(Token.objects.all())[-1]
+ self.client.login(username=self.username, password=self.password)
+ parameters = {'oauth_token': token.key,}
+ response = self.client.get("/oauth/authorize/", parameters)
+ parameters['authorize_access'] = 1 # fake authorization by the user
+ response = self.client.post("/oauth/authorize/", parameters)
+ token = Token.objects.get(key=token.key)
+ response = self.client.get("/oauth/access_token/", self._create_access_token_parameters(token))
+ access_token = list(Token.objects.filter(token_type=Token.ACCESS))[-1]
+
+ # Starting the test here
+ response = self.client.get("/", self._create_access_token_parameters(access_token))
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.content, '{"resource": "Protected!"}')
diff --git a/djangorestframework/tests/renderers.py b/djangorestframework/tests/renderers.py
index d403873f..bf135e55 100644
--- a/djangorestframework/tests/renderers.py
+++ b/djangorestframework/tests/renderers.py
@@ -2,6 +2,7 @@ from django.conf.urls.defaults import patterns, url
from django import http
from django.test import TestCase
+from djangorestframework import status
from djangorestframework.compat import View as DjangoView
from djangorestframework.renderers import BaseRenderer, JSONRenderer
from djangorestframework.parsers import JSONParser
@@ -11,7 +12,7 @@ from djangorestframework.utils.mediatypes import add_media_type_param
from StringIO import StringIO
-DUMMYSTATUS = 200
+DUMMYSTATUS = status.HTTP_200_OK
DUMMYCONTENT = 'dummycontent'
RENDERER_A_SERIALIZER = lambda x: 'Renderer A: %s' % x
@@ -19,12 +20,14 @@ RENDERER_B_SERIALIZER = lambda x: 'Renderer B: %s' % x
class RendererA(BaseRenderer):
media_type = 'mock/renderera'
+ format="formata"
def render(self, obj=None, media_type=None):
return RENDERER_A_SERIALIZER(obj)
class RendererB(BaseRenderer):
media_type = 'mock/rendererb'
+ format="formatb"
def render(self, obj=None, media_type=None):
return RENDERER_B_SERIALIZER(obj)
@@ -32,11 +35,13 @@ class RendererB(BaseRenderer):
class MockView(ResponseMixin, DjangoView):
renderers = (RendererA, RendererB)
- def get(self, request):
+ def get(self, request, **kwargs):
response = Response(DUMMYSTATUS, DUMMYCONTENT)
return self.render(response)
+
urlpatterns = patterns('',
+ url(r'^.*\.(?P<format>.+)$', MockView.as_view(renderers=[RendererA, RendererB])),
url(r'^$', MockView.as_view(renderers=[RendererA, RendererB])),
)
@@ -55,6 +60,13 @@ class RendererIntegrationTests(TestCase):
self.assertEquals(resp.content, RENDERER_A_SERIALIZER(DUMMYCONTENT))
self.assertEquals(resp.status_code, DUMMYSTATUS)
+ def test_head_method_serializes_no_content(self):
+ """No response must be included in HEAD requests."""
+ resp = self.client.head('/')
+ self.assertEquals(resp.status_code, DUMMYSTATUS)
+ self.assertEquals(resp['Content-Type'], RendererA.media_type)
+ self.assertEquals(resp.content, '')
+
def test_default_renderer_serializes_content_on_accept_any(self):
"""If the Accept header is set to */* the default renderer should serialize the response."""
resp = self.client.get('/', HTTP_ACCEPT='*/*')
@@ -78,12 +90,58 @@ class RendererIntegrationTests(TestCase):
self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
self.assertEquals(resp.status_code, DUMMYSTATUS)
+ def test_specified_renderer_serializes_content_on_accept_query(self):
+ """The '_accept' query string should behave in the same way as the Accept header."""
+ resp = self.client.get('/?_accept=%s' % RendererB.media_type)
+ self.assertEquals(resp['Content-Type'], RendererB.media_type)
+ self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
+ self.assertEquals(resp.status_code, DUMMYSTATUS)
+
def test_unsatisfiable_accept_header_on_request_returns_406_status(self):
"""If the Accept header is unsatisfiable we should return a 406 Not Acceptable response."""
resp = self.client.get('/', HTTP_ACCEPT='foo/bar')
- self.assertEquals(resp.status_code, 406)
+ self.assertEquals(resp.status_code, status.HTTP_406_NOT_ACCEPTABLE)
+
+ def test_specified_renderer_serializes_content_on_format_query(self):
+ """If a 'format' query is specified, the renderer with the matching
+ format attribute should serialize the response."""
+ resp = self.client.get('/?format=%s' % RendererB.format)
+ self.assertEquals(resp['Content-Type'], RendererB.media_type)
+ self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
+ self.assertEquals(resp.status_code, DUMMYSTATUS)
+
+ def test_specified_renderer_serializes_content_on_format_kwargs(self):
+ """If a 'format' keyword arg is specified, the renderer with the matching
+ format attribute should serialize the response."""
+ resp = self.client.get('/something.formatb')
+ self.assertEquals(resp['Content-Type'], RendererB.media_type)
+ self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
+ self.assertEquals(resp.status_code, DUMMYSTATUS)
+ def test_specified_renderer_is_used_on_format_query_with_matching_accept(self):
+ """If both a 'format' query and a matching Accept header specified,
+ the renderer with the matching format attribute should serialize the response."""
+ resp = self.client.get('/?format=%s' % RendererB.format,
+ HTTP_ACCEPT=RendererB.media_type)
+ self.assertEquals(resp['Content-Type'], RendererB.media_type)
+ self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
+ self.assertEquals(resp.status_code, DUMMYSTATUS)
+ def test_conflicting_format_query_and_accept_ignores_accept(self):
+ """If a 'format' query is specified that does not match the Accept
+ header, we should only honor the 'format' query string."""
+ resp = self.client.get('/?format=%s' % RendererB.format,
+ HTTP_ACCEPT='dummy')
+ self.assertEquals(resp['Content-Type'], RendererB.media_type)
+ self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
+ self.assertEquals(resp.status_code, DUMMYSTATUS)
+
+ def test_bla(self):
+ resp = self.client.get('/?format=formatb',
+ HTTP_ACCEPT='text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8')
+ self.assertEquals(resp['Content-Type'], RendererB.media_type)
+ self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
+ self.assertEquals(resp.status_code, DUMMYSTATUS)
_flat_repr = '{"foo": ["bar", "baz"]}'
diff --git a/djangorestframework/tests/reverse.py b/djangorestframework/tests/reverse.py
index b4b0a793..2d1ca79e 100644
--- a/djangorestframework/tests/reverse.py
+++ b/djangorestframework/tests/reverse.py
@@ -24,9 +24,5 @@ class ReverseTests(TestCase):
urls = 'djangorestframework.tests.reverse'
def test_reversed_urls_are_fully_qualified(self):
- try:
- response = self.client.get('/')
- except:
- import traceback
- traceback.print_exc()
+ response = self.client.get('/')
self.assertEqual(json.loads(response.content), 'http://testserver/another')
diff --git a/examples/requirements.txt b/examples/requirements.txt
index dffe0d99..0bcd8d43 100644
--- a/examples/requirements.txt
+++ b/examples/requirements.txt
@@ -1,8 +1,6 @@
-# For the examples we need Django, pygments and httplib2...
+# Pygments for the code highlighting example,
+# markdown for the docstring -> auto-documentation
-Django==1.2.4
-wsgiref==0.1.2
Pygments==1.4
-httplib2==0.6.0
Markdown==2.0.3
-django-oauth-plus==2.0.0
+