diff options
| -rw-r--r-- | AUTHORS | 4 | ||||
| -rw-r--r-- | djangorestframework/__init__.py | 2 | ||||
| -rw-r--r-- | djangorestframework/compat.py | 25 | ||||
| -rw-r--r-- | djangorestframework/mixins.py | 6 | ||||
| -rw-r--r-- | djangorestframework/parsers.py | 2 | ||||
| -rw-r--r-- | djangorestframework/renderers.py | 8 | ||||
| -rw-r--r-- | djangorestframework/runtests/runcoverage.py | 1 | ||||
| -rw-r--r-- | djangorestframework/templatetags/add_query_param.py | 12 | ||||
| -rw-r--r-- | djangorestframework/tests/content.py | 25 | ||||
| -rw-r--r-- | djangorestframework/tests/mixins.py | 11 | ||||
| -rw-r--r-- | djangorestframework/tests/renderers.py | 180 | ||||
| -rw-r--r-- | djangorestframework/utils/__init__.py | 2 | ||||
| -rw-r--r-- | djangorestframework/utils/breadcrumbs.py | 2 | ||||
| -rw-r--r-- | djangorestframework/utils/mediatypes.py | 2 | ||||
| -rw-r--r-- | djangorestframework/views.py | 5 | ||||
| -rw-r--r-- | examples/pygments_api/tests.py | 2 | ||||
| -rw-r--r-- | requirements.txt | 1 | ||||
| -rw-r--r-- | setup.py | 1 | ||||
| -rw-r--r-- | tox.ini | 18 |
19 files changed, 271 insertions, 38 deletions
@@ -1,4 +1,4 @@ -Tom Christie <tomchristie> - tom@tomchristie.com, @thisneonsoul +Tom Christie <tomchristie> - tom@tomchristie.com, @_tomchristie Marko Tibold <markotibold> (Additional thanks for providing & managing the Jenkins CI Server) Paul Bagwell <pbgwl> Sébastien Piquemal <sebpiq> @@ -22,6 +22,8 @@ Meurig Freeman <meurig> Anthony Nemitz <anemitz> Ewoud Kohl van Wijngaarden <ekohl> Michael Ding <yandy> +Mjumbe Poe <mjumbewu> +Natim <natim> THANKS TO: diff --git a/djangorestframework/__init__.py b/djangorestframework/__init__.py index 62edc070..55f92cc4 100644 --- a/djangorestframework/__init__.py +++ b/djangorestframework/__init__.py @@ -1,3 +1,3 @@ -__version__ = '0.3.1-dev' +__version__ = '0.3.2-dev' VERSION = __version__ # synonym diff --git a/djangorestframework/compat.py b/djangorestframework/compat.py index 38119811..7690316c 100644 --- a/djangorestframework/compat.py +++ b/djangorestframework/compat.py @@ -432,3 +432,28 @@ try: except ImportError: yaml = None +import unittest +try: + import unittest.skip +except ImportError: # python < 2.7 + from unittest import TestCase + import functools + + def skip(reason): + # Pasted from py27/lib/unittest/case.py + """ + Unconditionally skip a test. + """ + def decorator(test_item): + if not (isinstance(test_item, type) and issubclass(test_item, TestCase)): + @functools.wraps(test_item) + def skip_wrapper(*args, **kwargs): + pass + test_item = skip_wrapper + + test_item.__unittest_skip__ = True + test_item.__unittest_skip_why__ = reason + return test_item + return decorator + + unittest.skip = skip diff --git a/djangorestframework/mixins.py b/djangorestframework/mixins.py index f57760ec..8e6b1ed8 100644 --- a/djangorestframework/mixins.py +++ b/djangorestframework/mixins.py @@ -8,6 +8,7 @@ from django.core.paginator import Paginator from django.db.models.fields.related import ForeignKey from django.db.models.query import Q from django.http import HttpResponse +from urlobject import URLObject from djangorestframework import status from djangorestframework.renderers import BaseRenderer @@ -706,11 +707,12 @@ class PaginatorMixin(object): def url_with_page_number(self, page_number): """ Constructs a url used for getting the next/previous urls """ - url = "%s?page=%d" % (self.request.path, page_number) + url = URLObject.parse(self.request.get_full_path()) + url = url.add_query_param('page', page_number) limit = self.get_limit() if limit != self.limit: - url = "%s&limit=%d" % (url, limit) + url = url.add_query_param('limit', limit) return url diff --git a/djangorestframework/parsers.py b/djangorestframework/parsers.py index 7d765022..c218e5ee 100644 --- a/djangorestframework/parsers.py +++ b/djangorestframework/parsers.py @@ -31,7 +31,7 @@ __all__ = ( 'FormParser', 'MultiPartParser', 'YAMLParser', - 'XMLParser' + 'XMLParser' ) diff --git a/djangorestframework/renderers.py b/djangorestframework/renderers.py index 0c80f07e..42d80bd4 100644 --- a/djangorestframework/renderers.py +++ b/djangorestframework/renderers.py @@ -169,7 +169,7 @@ if yaml: if obj is None: return '' - return yaml.dump(obj) + return yaml.safe_dump(obj) else: YAMLRenderer = None @@ -215,7 +215,7 @@ class DocumentingTemplateRenderer(BaseRenderer): """ # Find the first valid renderer and render the content. (Don't use another documenting renderer.) - renderers = [renderer for renderer in view.renderers if not isinstance(renderer, DocumentingTemplateRenderer)] + renderers = [renderer for renderer in view.renderers if not issubclass(renderer, DocumentingTemplateRenderer)] if not renderers: return '[No renderers were found]' @@ -246,14 +246,14 @@ class DocumentingTemplateRenderer(BaseRenderer): form_instance = view.get_bound_form(view.response.cleaned_content, method=method) if form_instance and not form_instance.is_valid(): form_instance = None - except: + except Exception: form_instance = None # If we still don't have a form instance then try to get an unbound form if not form_instance: try: form_instance = view.get_bound_form(method=method) - except: + except Exception: pass # If we still don't have a form instance then try to get an unbound form which can tunnel arbitrary content types diff --git a/djangorestframework/runtests/runcoverage.py b/djangorestframework/runtests/runcoverage.py index ab1b0498..0ab61479 100644 --- a/djangorestframework/runtests/runcoverage.py +++ b/djangorestframework/runtests/runcoverage.py @@ -35,7 +35,6 @@ def main(): else: test_runner = TestRunner() failures = test_runner.run_tests(['djangorestframework']) - cov.stop() # Discover the list of all modules that we should test coverage for diff --git a/djangorestframework/templatetags/add_query_param.py b/djangorestframework/templatetags/add_query_param.py index 94833bce..ce175b81 100644 --- a/djangorestframework/templatetags/add_query_param.py +++ b/djangorestframework/templatetags/add_query_param.py @@ -1,17 +1,11 @@ from django.template import Library -from urlparse import urlparse, urlunparse -from urllib import quote +from urlobject import URLObject register = Library() + def add_query_param(url, param): (key, sep, val) = param.partition('=') - param = '%s=%s' % (key, quote(val)) - (scheme, netloc, path, params, query, fragment) = urlparse(url) - if query: - query += "&" + param - else: - query = param - return urlunparse((scheme, netloc, path, params, query, fragment)) + return unicode(URLObject(url) & (key, val)) register.filter('add_query_param', add_query_param) diff --git a/djangorestframework/tests/content.py b/djangorestframework/tests/content.py index 048586c8..47dfce80 100644 --- a/djangorestframework/tests/content.py +++ b/djangorestframework/tests/content.py @@ -6,7 +6,7 @@ from django.contrib.auth.models import User from django.test import TestCase, Client from djangorestframework import status from djangorestframework.authentication import UserLoggedInAuthentication -from djangorestframework.compat import RequestFactory +from djangorestframework.compat import RequestFactory, unittest from djangorestframework.mixins import RequestMixin from djangorestframework.parsers import FormParser, MultiPartParser, \ PlainTextParser, JSONParser @@ -114,21 +114,22 @@ class TestContentParsing(TestCase): self.assertEqual(view.DATA.items(), form_data.items()) self.assertEqual(view.request.POST.items(), form_data.items()) - # def test_accessing_post_after_data_for_json(self): - # """Ensures request.POST can be accessed after request.DATA in json request""" - # from django.utils import simplejson as json + @unittest.skip('This test was disabled some time ago for some reason') + def test_accessing_post_after_data_for_json(self): + """Ensures request.POST can be accessed after request.DATA in json request""" + from django.utils import simplejson as json - # data = {'qwerty': 'uiop'} - # content = json.dumps(data) - # content_type = 'application/json' + data = {'qwerty': 'uiop'} + content = json.dumps(data) + content_type = 'application/json' - # view = RequestMixin() - # view.parsers = (JSONParser,) + view = RequestMixin() + view.parsers = (JSONParser,) - # view.request = self.req.post('/', content, content_type=content_type) + view.request = self.req.post('/', content, content_type=content_type) - # self.assertEqual(view.DATA.items(), data.items()) - # self.assertEqual(view.request.POST.items(), []) + self.assertEqual(view.DATA.items(), data.items()) + self.assertEqual(view.request.POST.items(), []) def test_accessing_post_after_data_for_overloaded_json(self): """Ensures request.POST can be accessed after request.DATA in overloaded json request""" diff --git a/djangorestframework/tests/mixins.py b/djangorestframework/tests/mixins.py index 3b814aa7..2913160d 100644 --- a/djangorestframework/tests/mixins.py +++ b/djangorestframework/tests/mixins.py @@ -237,3 +237,14 @@ class TestPagination(TestCase): response = MockPaginatorView.as_view()(request) content = json.loads(response.content) self.assertEqual(response.status_code, status.NOT_FOUND) + + def test_existing_query_parameters_are_preserved(self): + """ Tests that existing query parameters are preserved when + generating next/previous page links """ + request = self.req.get('/paginator/?foo=bar&another=something') + response = MockPaginatorView.as_view()(request) + content = json.loads(response.content) + self.assertEqual(response.status_code, status.OK) + self.assertTrue('foo=bar' in content['next']) + self.assertTrue('another=something' in content['next']) + self.assertTrue('page=2' in content['next']) diff --git a/djangorestframework/tests/renderers.py b/djangorestframework/tests/renderers.py index bd0d360c..e80f0f20 100644 --- a/djangorestframework/tests/renderers.py +++ b/djangorestframework/tests/renderers.py @@ -5,7 +5,7 @@ from djangorestframework import status from djangorestframework.views import View from djangorestframework.compat import View as DjangoView from djangorestframework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \ - XMLRenderer, JSONPRenderer + XMLRenderer, JSONPRenderer, DocumentingHTMLRenderer from djangorestframework.parsers import JSONParser, YAMLParser from djangorestframework.mixins import ResponseMixin from djangorestframework.response import Response @@ -46,15 +46,30 @@ class MockView(ResponseMixin, DjangoView): class MockGETView(View): + def get(self, request, **kwargs): return {'foo': ['bar', 'baz']} +class HTMLView(View): + renderers = (DocumentingHTMLRenderer, ) + + def get(self, request, **kwargs): + return 'text' + +class HTMLView1(View): + renderers = (DocumentingHTMLRenderer, JSONRenderer) + + def get(self, request, **kwargs): + return 'text' + urlpatterns = patterns('', url(r'^.*\.(?P<format>.+)$', MockView.as_view(renderers=[RendererA, RendererB])), url(r'^$', MockView.as_view(renderers=[RendererA, RendererB])), url(r'^jsonp/jsonrenderer$', MockGETView.as_view(renderers=[JSONRenderer, JSONPRenderer])), url(r'^jsonp/nojsonrenderer$', MockGETView.as_view(renderers=[JSONPRenderer])), + url(r'^html$', HTMLView.as_view()), + url(r'^html1$', HTMLView1.as_view()), ) @@ -327,3 +342,166 @@ class XMLRendererTestCase(TestCase): self.assertTrue(xml.startswith('<?xml version="1.0" encoding="utf-8"?>\n<root>')) self.assertTrue(xml.endswith('</root>')) self.assertTrue(string in xml, '%r not in %r' % (string, xml)) + +class HTMLView(View): + renderers = (DocumentingHTMLRenderer) + + def get(self, request, **kwargs): + return 'text' + +urlpatterns += patterns('', + url(r'^/html$', HTMLView.as_view()), +) + +class Issue122Tests(TestCase): + """ + Tests that cover issues. + """ + + urls = 'djangorestframework.tests.renderers' + + def test_without_callback_with_json_renderer(self): + """ + Test JSONP rendering with View JSON Renderer. + """ + resp = self.client.get('/jsonp/jsonrenderer', + HTTP_ACCEPT='application/json-p') + self.assertEquals(resp.status_code, 200) + self.assertEquals(resp['Content-Type'], 'application/json-p') + self.assertEquals(resp.content, 'callback(%s);' % _flat_repr) + + def test_without_callback_without_json_renderer(self): + """ + Test JSONP rendering without View JSON Renderer. + """ + resp = self.client.get('/jsonp/nojsonrenderer', + HTTP_ACCEPT='application/json-p') + self.assertEquals(resp.status_code, 200) + self.assertEquals(resp['Content-Type'], 'application/json-p') + self.assertEquals(resp.content, 'callback(%s);' % _flat_repr) + + def test_with_callback(self): + """ + Test JSONP rendering with callback function name. + """ + callback_func = 'myjsonpcallback' + resp = self.client.get('/jsonp/nojsonrenderer?callback=' + callback_func, + HTTP_ACCEPT='application/json-p') + self.assertEquals(resp.status_code, 200) + self.assertEquals(resp['Content-Type'], 'application/json-p') + self.assertEquals(resp.content, '%s(%s);' % (callback_func, _flat_repr)) + + +if YAMLRenderer: + _yaml_repr = 'foo: [bar, baz]\n' + + class YAMLRendererTests(TestCase): + """ + Tests specific to the JSON Renderer + """ + + def test_render(self): + """ + Test basic YAML rendering. + """ + obj = {'foo': ['bar', 'baz']} + renderer = YAMLRenderer(None) + content = renderer.render(obj, 'application/yaml') + self.assertEquals(content, _yaml_repr) + + def test_render_and_parse(self): + """ + Test rendering and then parsing returns the original object. + IE obj -> render -> parse -> obj. + """ + obj = {'foo': ['bar', 'baz']} + + renderer = YAMLRenderer(None) + parser = YAMLParser(None) + + content = renderer.render(obj, 'application/yaml') + (data, files) = parser.parse(StringIO(content)) + self.assertEquals(obj, data) + + +class XMLRendererTestCase(TestCase): + """ + Tests specific to the XML Renderer + """ + + def test_render_string(self): + """ + Test XML rendering. + """ + renderer = XMLRenderer(None) + content = renderer.render({'field': 'astring'}, 'application/xml') + self.assertXMLContains(content, '<field>astring</field>') + + def test_render_integer(self): + """ + Test XML rendering. + """ + renderer = XMLRenderer(None) + content = renderer.render({'field': 111}, 'application/xml') + self.assertXMLContains(content, '<field>111</field>') + + def test_render_datetime(self): + """ + Test XML rendering. + """ + renderer = XMLRenderer(None) + content = renderer.render({ + 'field': datetime.datetime(2011, 12, 25, 12, 45, 00) + }, 'application/xml') + self.assertXMLContains(content, '<field>2011-12-25 12:45:00</field>') + + def test_render_float(self): + """ + Test XML rendering. + """ + renderer = XMLRenderer(None) + content = renderer.render({'field': 123.4}, 'application/xml') + self.assertXMLContains(content, '<field>123.4</field>') + + def test_render_decimal(self): + """ + Test XML rendering. + """ + renderer = XMLRenderer(None) + content = renderer.render({'field': Decimal('111.2')}, 'application/xml') + self.assertXMLContains(content, '<field>111.2</field>') + + def test_render_none(self): + """ + Test XML rendering. + """ + renderer = XMLRenderer(None) + content = renderer.render({'field': None}, 'application/xml') + self.assertXMLContains(content, '<field></field>') + + def assertXMLContains(self, xml, string): + self.assertTrue(xml.startswith('<?xml version="1.0" encoding="utf-8"?>\n<root>')) + self.assertTrue(xml.endswith('</root>')) + self.assertTrue(string in xml, '%r not in %r' % (string, xml)) + + + +class Issue122Tests(TestCase): + """ + Tests that covers #122. + """ + + urls = 'djangorestframework.tests.renderers' + + def test_only_html_renderer(self): + """ + Test if no recursion occurs. + """ + resp = self.client.get('/html') + + def test_html_renderer_is_first(self): + """ + Test if no recursion occurs. + """ + resp = self.client.get('/html1') + diff --git a/djangorestframework/utils/__init__.py b/djangorestframework/utils/__init__.py index 305311f4..2b4fd10e 100644 --- a/djangorestframework/utils/__init__.py +++ b/djangorestframework/utils/__init__.py @@ -43,7 +43,7 @@ def url_resolves(url): """ try: resolve(url) - except: + except Exception: return False return True diff --git a/djangorestframework/utils/breadcrumbs.py b/djangorestframework/utils/breadcrumbs.py index 6cf978ed..fd966282 100644 --- a/djangorestframework/utils/breadcrumbs.py +++ b/djangorestframework/utils/breadcrumbs.py @@ -11,7 +11,7 @@ def get_breadcrumbs(url): try: (view, unused_args, unused_kwargs) = resolve(url) - except: + except Exception: pass else: # Check if this is a REST framework view, and if so add it to the breadcrumbs diff --git a/djangorestframework/utils/mediatypes.py b/djangorestframework/utils/mediatypes.py index 3c0eefc4..c0071f01 100644 --- a/djangorestframework/utils/mediatypes.py +++ b/djangorestframework/utils/mediatypes.py @@ -109,7 +109,7 @@ class _MediaType(object): # """ # try: # return Decimal(self.params.get('q', '1.0')) - # except: + # except Exception: # return Decimal(0) #def score(self): diff --git a/djangorestframework/views.py b/djangorestframework/views.py index 4cc2c7cb..4606e50b 100644 --- a/djangorestframework/views.py +++ b/djangorestframework/views.py @@ -115,8 +115,9 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView): # Calls to 'reverse' will not be fully qualified unless we set the scheme/host/port here. orig_prefix = get_script_prefix() - prefix = '%s://%s' % (request.is_secure() and 'https' or 'http', request.get_host()) - set_script_prefix(prefix + orig_prefix) + if not (orig_prefix.startswith('http:') or orig_prefix.startswith('https:')): + prefix = '%s://%s' % (request.is_secure() and 'https' or 'http', request.get_host()) + set_script_prefix(prefix + orig_prefix) try: self.initial(request, *args, **kwargs) diff --git a/examples/pygments_api/tests.py b/examples/pygments_api/tests.py index 98139ce2..24726647 100644 --- a/examples/pygments_api/tests.py +++ b/examples/pygments_api/tests.py @@ -18,7 +18,7 @@ class TestPygmentsExample(TestCase): def tearDown(self):
try:
shutil.rmtree(self.temp_dir)
- except:
+ except Exception:
pass
def test_get_to_root(self):
diff --git a/requirements.txt b/requirements.txt index 2b54c95b..56926c0f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ Django>=1.2 coverage>=3.4 +URLObject>=0.6.0 @@ -26,6 +26,7 @@ setup( package_dir={'djangorestframework': 'djangorestframework'}, package_data = {'djangorestframework': ['templates/*', 'static/*']}, test_suite = 'djangorestframework.runtests.runcoverage.main', + install_requires=['URLObject>=0.6.0'], classifiers = [ 'Development Status :: 4 - Beta', 'Environment :: Web Environment', @@ -33,6 +33,7 @@ basepython=python2.5 deps= django==1.2.4 coverage==3.4 + URLObject>=0.6.0 unittest-xml-reporting==1.2 Pyyaml==3.10 # Optional packages: @@ -43,6 +44,7 @@ basepython=python2.6 deps= django==1.2.4 coverage==3.4 + URLObject>=0.6.0 unittest-xml-reporting==1.2 Pyyaml==3.10 # Optional packages: @@ -53,6 +55,7 @@ basepython=python2.7 deps= django==1.2.4 coverage==3.4 + URLObject>=0.6.0 unittest-xml-reporting==1.2 Pyyaml==3.10 # Optional packages: @@ -63,6 +66,7 @@ basepython=python2.5 deps= django==1.3 coverage==3.4 + URLObject>=0.6.0 unittest-xml-reporting==1.2 Pyyaml==3.10 # Optional packages: @@ -73,6 +77,7 @@ basepython=python2.6 deps= django==1.3 coverage==3.4 + URLObject>=0.6.0 unittest-xml-reporting==1.2 Pyyaml==3.10 # Optional packages: @@ -83,6 +88,7 @@ basepython=python2.7 deps= django==1.3 coverage==3.4 + URLObject>=0.6.0 unittest-xml-reporting==1.2 Pyyaml==3.10 # Optional packages: @@ -93,6 +99,7 @@ basepython=python2.5 deps= http://www.djangoproject.com/download/1.4-alpha-1/tarball/ coverage==3.4 + URLObject>=0.6.0 unittest-xml-reporting==1.2 Pyyaml==3.10 # Optional packages: @@ -103,6 +110,7 @@ basepython=python2.6 deps= http://www.djangoproject.com/download/1.4-alpha-1/tarball/ coverage==3.4 + URLObject>=0.6.0 unittest-xml-reporting==1.2 Pyyaml==3.10 # Optional packages: @@ -113,6 +121,7 @@ basepython=python2.7 deps= http://www.djangoproject.com/download/1.4-alpha-1/tarball/ coverage==3.4 + URLObject>=0.6.0 unittest-xml-reporting==1.2 Pyyaml==3.10 # Optional packages: @@ -127,6 +136,7 @@ commands= deps= django==1.2.4 coverage==3.4 + URLObject>=0.6.0 wsgiref==0.1.2 Pygments==1.4 httplib2==0.6.0 @@ -141,6 +151,7 @@ commands= deps= django==1.2.4 coverage==3.4 + URLObject>=0.6.0 wsgiref==0.1.2 Pygments==1.4 httplib2==0.6.0 @@ -155,6 +166,7 @@ commands= deps= django==1.2.4 coverage==3.4 + URLObject>=0.6.0 wsgiref==0.1.2 Pygments==1.4 httplib2==0.6.0 @@ -169,6 +181,7 @@ commands= deps= django==1.3 coverage==3.4 + URLObject>=0.6.0 wsgiref==0.1.2 Pygments==1.4 httplib2==0.6.0 @@ -183,6 +196,7 @@ commands= deps= django==1.3 coverage==3.4 + URLObject>=0.6.0 wsgiref==0.1.2 Pygments==1.4 httplib2==0.6.0 @@ -197,6 +211,7 @@ commands= deps= django==1.3 coverage==3.4 + URLObject>=0.6.0 wsgiref==0.1.2 Pygments==1.4 httplib2==0.6.0 @@ -211,6 +226,7 @@ commands= deps= http://www.djangoproject.com/download/1.4-alpha-1/tarball/ coverage==3.4 + URLObject>=0.6.0 wsgiref==0.1.2 Pygments==1.4 httplib2==0.6.0 @@ -225,6 +241,7 @@ commands= deps= http://www.djangoproject.com/download/1.4-alpha-1/tarball/ coverage==3.4 + URLObject>=0.6.0 wsgiref==0.1.2 Pygments==1.4 httplib2==0.6.0 @@ -239,6 +256,7 @@ commands= deps= http://www.djangoproject.com/download/1.4-alpha-1/tarball/ coverage==3.4 + URLObject>=0.6.0 wsgiref==0.1.2 Pygments==1.4 httplib2==0.6.0 |
