aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTom Christie2015-01-23 14:28:59 +0000
committerTom Christie2015-01-23 14:28:59 +0000
commit39f26c9eca6fa8b749f9197ad78e5cba69870e50 (patch)
tree86ebd311194a185217e6fefb31d16316bf80779f
parent37dc2520f9adbaf54de759a1fdc41985ebd38a0e (diff)
parent4201c9fb01beae84fc34a5b74e138e721de42de1 (diff)
downloaddjango-rest-framework-39f26c9eca6fa8b749f9197ad78e5cba69870e50.tar.bz2
Merge master
-rw-r--r--.travis.yml4
-rwxr-xr-xdocs/api-guide/authentication.md2
-rw-r--r--rest_framework/parsers.py2
-rw-r--r--rest_framework/renderers.py3
-rw-r--r--rest_framework/response.py7
-rw-r--r--rest_framework/utils/model_meta.py10
-rw-r--r--rest_framework/utils/serializer_helpers.py10
-rwxr-xr-xruntests.py4
-rw-r--r--tests/test_filters.py1
-rw-r--r--tests/test_htmlrenderer.py6
-rw-r--r--tests/test_parsers.py4
-rw-r--r--tests/test_renderers.py85
-rw-r--r--tests/test_serializer.py17
-rw-r--r--tox.ini2
14 files changed, 70 insertions, 87 deletions
diff --git a/.travis.yml b/.travis.yml
index 996c3ae8..28ebfc00 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -33,10 +33,6 @@ env:
matrix:
fast_finish: true
allow_failures:
- - env: TOX_ENV=py34-django18alpha
- - env: TOX_ENV=py33-django18alpha
- - env: TOX_ENV=py32-django18alpha
- - env: TOX_ENV=py27-django18alpha
- env: TOX_ENV=py34-djangomaster
- env: TOX_ENV=py33-djangomaster
- env: TOX_ENV=py32-djangomaster
diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md
index ba114513..4b8110bd 100755
--- a/docs/api-guide/authentication.md
+++ b/docs/api-guide/authentication.md
@@ -360,7 +360,7 @@ HTTP Signature (currently a [IETF draft][http-signature-ietf-draft]) provides a
[oauth]: http://oauth.net/2/
[permission]: permissions.md
[throttling]: throttling.md
-[csrf-ajax]: https://docs.djangoproject.com/en/dev/ref/contrib/csrf/#ajax
+[csrf-ajax]: https://docs.djangoproject.com/en/dev/ref/csrf/#ajax
[mod_wsgi_official]: http://code.google.com/p/modwsgi/wiki/ConfigurationDirectives#WSGIPassAuthorization
[custom-user-model]: https://docs.djangoproject.com/en/dev/topics/auth/customizing/#specifying-a-custom-user-model
[south-dependencies]: http://south.readthedocs.org/en/latest/dependencies.html
diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py
index dd069840..7a51bb05 100644
--- a/rest_framework/parsers.py
+++ b/rest_framework/parsers.py
@@ -200,7 +200,7 @@ class FileUploadParser(BaseParser):
if 'filename*' in filename_parm:
return self.get_encoded_filename(filename_parm)
return force_text(filename_parm['filename'])
- except (AttributeError, KeyError):
+ except (AttributeError, KeyError, ValueError):
pass
def get_encoded_filename(self, filename_parm):
diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py
index f970a363..6256acdd 100644
--- a/rest_framework/renderers.py
+++ b/rest_framework/renderers.py
@@ -305,6 +305,9 @@ class HTMLFormRenderer(BaseRenderer):
})
def render_field(self, field, parent_style):
+ if isinstance(field, serializers.HiddenField):
+ return ''
+
style = dict(self.default_style[field])
style.update(field.style)
if 'template_pack' not in style:
diff --git a/rest_framework/response.py b/rest_framework/response.py
index d6ca1aad..7f90bae1 100644
--- a/rest_framework/response.py
+++ b/rest_framework/response.py
@@ -81,10 +81,13 @@ class Response(SimpleTemplateResponse):
def __getstate__(self):
"""
- Remove attributes from the response that shouldn't be cached
+ Remove attributes from the response that shouldn't be cached.
"""
state = super(Response, self).__getstate__()
- for key in ('accepted_renderer', 'renderer_context', 'data'):
+ for key in (
+ 'accepted_renderer', 'renderer_context', 'resolver_match',
+ 'client', 'request', 'wsgi_request', '_closable_objects'
+ ):
if key in state:
del state[key]
return state
diff --git a/rest_framework/utils/model_meta.py b/rest_framework/utils/model_meta.py
index b9fb6f67..dd92f8b6 100644
--- a/rest_framework/utils/model_meta.py
+++ b/rest_framework/utils/model_meta.py
@@ -121,12 +121,17 @@ def _get_reverse_relationships(opts):
"""
Returns an `OrderedDict` of field names to `RelationInfo`.
"""
+ # Note that we have a hack here to handle internal API differences for
+ # this internal API across Django 1.7 -> Django 1.8.
+ # See: https://code.djangoproject.com/ticket/24208
+
reverse_relations = OrderedDict()
for relation in opts.get_all_related_objects():
accessor_name = relation.get_accessor_name()
+ related = getattr(relation, 'related_model', relation.model)
reverse_relations[accessor_name] = RelationInfo(
model_field=None,
- related_model=relation.model,
+ related_model=related,
to_many=relation.field.rel.multiple,
has_through_model=False
)
@@ -134,9 +139,10 @@ def _get_reverse_relationships(opts):
# Deal with reverse many-to-many relationships.
for relation in opts.get_all_related_many_to_many_objects():
accessor_name = relation.get_accessor_name()
+ related = getattr(relation, 'related_model', relation.model)
reverse_relations[accessor_name] = RelationInfo(
model_field=None,
- related_model=relation.model,
+ related_model=related,
to_many=True,
has_through_model=(
(getattr(relation.field.rel, 'through', None) is not None)
diff --git a/rest_framework/utils/serializer_helpers.py b/rest_framework/utils/serializer_helpers.py
index ab057862..87bb3ac0 100644
--- a/rest_framework/utils/serializer_helpers.py
+++ b/rest_framework/utils/serializer_helpers.py
@@ -19,6 +19,11 @@ class ReturnDict(OrderedDict):
def __repr__(self):
return dict.__repr__(self)
+ def __reduce__(self):
+ # Pickling these objects will drop the .serializer backlink,
+ # but preserve the raw data.
+ return (dict, (dict(self),))
+
class ReturnList(list):
"""
@@ -33,6 +38,11 @@ class ReturnList(list):
def __repr__(self):
return list.__repr__(self)
+ def __reduce__(self):
+ # Pickling these objects will drop the .serializer backlink,
+ # but preserve the raw data.
+ return (list, (list(self),))
+
class BoundField(object):
"""
diff --git a/runtests.py b/runtests.py
index abf15a62..0008bfae 100755
--- a/runtests.py
+++ b/runtests.py
@@ -8,8 +8,8 @@ import subprocess
PYTEST_ARGS = {
- 'default': ['tests'],
- 'fast': ['tests', '-q'],
+ 'default': ['tests', '--tb=short'],
+ 'fast': ['tests', '--tb=short', '-q'],
}
FLAKE8_ARGS = ['rest_framework', 'tests', '--ignore=E501']
diff --git a/tests/test_filters.py b/tests/test_filters.py
index dc84dcbd..5b1b6ca5 100644
--- a/tests/test_filters.py
+++ b/tests/test_filters.py
@@ -467,6 +467,7 @@ class DjangoFilterOrderingTests(TestCase):
for d in data:
DjangoFilterOrderingModel.objects.create(**d)
+ @unittest.skipUnless(django_filters, 'django-filter not installed')
def test_default_ordering(self):
class DjangoFilterOrderingView(generics.ListAPIView):
serializer_class = DjangoFilterOrderingSerializer
diff --git a/tests/test_htmlrenderer.py b/tests/test_htmlrenderer.py
index 2edc6b4b..a33b832f 100644
--- a/tests/test_htmlrenderer.py
+++ b/tests/test_htmlrenderer.py
@@ -56,7 +56,13 @@ class TemplateHTMLRendererTests(TestCase):
return Template("example: {{ object }}")
raise TemplateDoesNotExist(template_name)
+ def select_template(template_name_list, dirs=None, using=None):
+ if template_name_list == ['example.html']:
+ return Template("example: {{ object }}")
+ raise TemplateDoesNotExist(template_name_list[0])
+
django.template.loader.get_template = get_template
+ django.template.loader.select_template = select_template
def tearDown(self):
"""
diff --git a/tests/test_parsers.py b/tests/test_parsers.py
index 54455cf6..8816065a 100644
--- a/tests/test_parsers.py
+++ b/tests/test_parsers.py
@@ -101,7 +101,9 @@ class TestFileUploadParser(TestCase):
self.__replace_content_disposition('inline; filename=fallback.txt; filename*=utf-8--ÀĥƦ.txt')
filename = parser.get_filename(self.stream, None, self.parser_context)
- self.assertEqual(filename, 'fallback.txt')
+ # Malformed. Either None or 'fallback.txt' will be acceptable.
+ # See also https://code.djangoproject.com/ticket/24209
+ self.assertIn(filename, ('fallback.txt', None))
def __replace_content_disposition(self, disposition):
self.parser_context['request'].META['HTTP_CONTENT_DISPOSITION'] = disposition
diff --git a/tests/test_renderers.py b/tests/test_renderers.py
index 3e64d8fe..f68405f0 100644
--- a/tests/test_renderers.py
+++ b/tests/test_renderers.py
@@ -15,7 +15,6 @@ from rest_framework.settings import api_settings
from rest_framework.test import APIRequestFactory
from collections import MutableMapping
import json
-import pickle
import re
@@ -408,87 +407,27 @@ class CacheRenderTest(TestCase):
urls = 'tests.test_renderers'
- cache_key = 'just_a_cache_key'
-
- @classmethod
- def _get_pickling_errors(cls, obj, seen=None):
- """ Return any errors that would be raised if `obj' is pickled
- Courtesy of koffie @ http://stackoverflow.com/a/7218986/109897
- """
- if seen is None:
- seen = []
- try:
- state = obj.__getstate__()
- except AttributeError:
- return
- if state is None:
- return
- if isinstance(state, tuple):
- if not isinstance(state[0], dict):
- state = state[1]
- else:
- state = state[0].update(state[1])
- result = {}
- for i in state:
- try:
- pickle.dumps(state[i], protocol=2)
- except pickle.PicklingError:
- if not state[i] in seen:
- seen.append(state[i])
- result[i] = cls._get_pickling_errors(state[i], seen)
- return result
-
- def http_resp(self, http_method, url):
- """
- Simple wrapper for Client http requests
- Removes the `client' and `request' attributes from as they are
- added by django.test.client.Client and not part of caching
- responses outside of tests.
- """
- method = getattr(self.client, http_method)
- resp = method(url)
- resp._closable_objects = []
- del resp.client, resp.request
- try:
- del resp.wsgi_request
- except AttributeError:
- pass
- return resp
-
- def test_obj_pickling(self):
- """
- Test that responses are properly pickled
- """
- resp = self.http_resp('get', '/cache')
-
- # Make sure that no pickling errors occurred
- self.assertEqual(self._get_pickling_errors(resp), {})
-
- # Unfortunately LocMem backend doesn't raise PickleErrors but returns
- # None instead.
- cache.set(self.cache_key, resp)
- self.assertTrue(cache.get(self.cache_key) is not None)
-
def test_head_caching(self):
"""
Test caching of HEAD requests
"""
- resp = self.http_resp('head', '/cache')
- cache.set(self.cache_key, resp)
-
- cached_resp = cache.get(self.cache_key)
- self.assertIsInstance(cached_resp, Response)
+ response = self.client.head('/cache')
+ cache.set('key', response)
+ cached_response = cache.get('key')
+ assert isinstance(cached_response, Response)
+ assert cached_response.content == response.content
+ assert cached_response.status_code == response.status_code
def test_get_caching(self):
"""
Test caching of GET requests
"""
- resp = self.http_resp('get', '/cache')
- cache.set(self.cache_key, resp)
-
- cached_resp = cache.get(self.cache_key)
- self.assertIsInstance(cached_resp, Response)
- self.assertEqual(cached_resp.content, resp.content)
+ response = self.client.get('/cache')
+ cache.set('key', response)
+ cached_response = cache.get('key')
+ assert isinstance(cached_response, Response)
+ assert cached_response.content == response.content
+ assert cached_response.status_code == response.status_code
class TestJSONIndentationStyles:
diff --git a/tests/test_serializer.py b/tests/test_serializer.py
index 68bbbe98..b7a0484b 100644
--- a/tests/test_serializer.py
+++ b/tests/test_serializer.py
@@ -3,6 +3,7 @@ from __future__ import unicode_literals
from .utils import MockObject
from rest_framework import serializers
from rest_framework.compat import unicode_repr
+import pickle
import pytest
@@ -278,3 +279,19 @@ class TestNotRequiredOutput:
serializer = ExampleSerializer(instance)
with pytest.raises(AttributeError):
serializer.data
+
+
+class TestCacheSerializerData:
+ def test_cache_serializer_data(self):
+ """
+ Caching serializer data with pickle will drop the serializer info,
+ but does preserve the data itself.
+ """
+ class ExampleSerializer(serializers.Serializer):
+ field1 = serializers.CharField()
+ field2 = serializers.CharField()
+
+ serializer = ExampleSerializer({'field1': 'a', 'field2': 'b'})
+ pickled = pickle.dumps(serializer.data)
+ data = pickle.loads(pickled)
+ assert data == {'field1': 'a', 'field2': 'b'}
diff --git a/tox.ini b/tox.ini
index 9be14bf8..b4b708be 100644
--- a/tox.ini
+++ b/tox.ini
@@ -18,7 +18,7 @@ deps =
djangomaster: https://github.com/django/django/zipball/master
django-guardian==1.2.4
pytest-django==2.8.0
- django-filter==0.9.1
+ {py26,py27,py32,py33,py34}-django{14,15,16,17}: django-filter==0.9.1
markdown>=2.1.0
[testenv:py27-flake8]