mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-02-02 20:54:42 +03:00
Merge master
This commit is contained in:
commit
39f26c9eca
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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']
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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'}
|
||||
|
|
Loading…
Reference in New Issue
Block a user