diff options
| -rw-r--r-- | .travis.yml | 4 | ||||
| -rwxr-xr-x | docs/api-guide/authentication.md | 2 | ||||
| -rw-r--r-- | rest_framework/parsers.py | 2 | ||||
| -rw-r--r-- | rest_framework/renderers.py | 3 | ||||
| -rw-r--r-- | rest_framework/response.py | 7 | ||||
| -rw-r--r-- | rest_framework/utils/model_meta.py | 10 | ||||
| -rw-r--r-- | rest_framework/utils/serializer_helpers.py | 10 | ||||
| -rwxr-xr-x | runtests.py | 4 | ||||
| -rw-r--r-- | tests/test_filters.py | 1 | ||||
| -rw-r--r-- | tests/test_htmlrenderer.py | 6 | ||||
| -rw-r--r-- | tests/test_parsers.py | 4 | ||||
| -rw-r--r-- | tests/test_renderers.py | 85 | ||||
| -rw-r--r-- | tests/test_serializer.py | 17 | ||||
| -rw-r--r-- | tox.ini | 2 | 
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'} @@ -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] | 
