mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-11 04:07:39 +03:00
Merge branch 'jpadilla-yaml' into version-3.1
This commit is contained in:
commit
5e60528117
|
@ -214,6 +214,5 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
[docs]: http://www.django-rest-framework.org/
|
||||
[urlobject]: https://github.com/zacharyvoase/urlobject
|
||||
[markdown]: http://pypi.python.org/pypi/Markdown/
|
||||
[pyyaml]: http://pypi.python.org/pypi/PyYAML
|
||||
[django-filter]: http://pypi.python.org/pypi/django-filter
|
||||
[security-mail]: mailto:rest-framework-security@googlegroups.com
|
||||
|
|
|
@ -26,26 +26,26 @@ As an example, if you are sending `json` encoded data using jQuery with the [.aj
|
|||
|
||||
## Setting the parsers
|
||||
|
||||
The default set of parsers may be set globally, using the `DEFAULT_PARSER_CLASSES` setting. For example, the following settings would allow requests with `YAML` content.
|
||||
The default set of parsers may be set globally, using the `DEFAULT_PARSER_CLASSES` setting. For example, the following settings would allow requests with `JSON` content.
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_PARSER_CLASSES': (
|
||||
'rest_framework.parsers.YAMLParser',
|
||||
'rest_framework.parsers.JSONParser',
|
||||
)
|
||||
}
|
||||
|
||||
You can also set the parsers used for an individual view, or viewset,
|
||||
using the `APIView` class based views.
|
||||
|
||||
from rest_framework.parsers import YAMLParser
|
||||
from rest_framework.parsers import JSONParser
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
class ExampleView(APIView):
|
||||
"""
|
||||
A view that can accept POST requests with YAML content.
|
||||
A view that can accept POST requests with JSON content.
|
||||
"""
|
||||
parser_classes = (YAMLParser,)
|
||||
parser_classes = (JSONParser,)
|
||||
|
||||
def post(self, request, format=None):
|
||||
return Response({'received data': request.data})
|
||||
|
@ -53,10 +53,10 @@ using the `APIView` class based views.
|
|||
Or, if you're using the `@api_view` decorator with function based views.
|
||||
|
||||
@api_view(['POST'])
|
||||
@parser_classes((YAMLParser,))
|
||||
@parser_classes((JSONParser,))
|
||||
def example_view(request, format=None):
|
||||
"""
|
||||
A view that can accept POST requests with YAML content.
|
||||
A view that can accept POST requests with JSON content.
|
||||
"""
|
||||
return Response({'received data': request.data})
|
||||
|
||||
|
@ -70,14 +70,6 @@ Parses `JSON` request content.
|
|||
|
||||
**.media_type**: `application/json`
|
||||
|
||||
## YAMLParser
|
||||
|
||||
Parses `YAML` request content.
|
||||
|
||||
Requires the `pyyaml` package to be installed.
|
||||
|
||||
**.media_type**: `application/yaml`
|
||||
|
||||
## FormParser
|
||||
|
||||
Parses HTML form content. `request.data` will be populated with a `QueryDict` of data.
|
||||
|
|
|
@ -18,11 +18,11 @@ For more information see the documentation on [content negotiation][conneg].
|
|||
|
||||
## Setting the renderers
|
||||
|
||||
The default set of renderers may be set globally, using the `DEFAULT_RENDERER_CLASSES` setting. For example, the following settings would use `YAML` as the main media type and also include the self describing API.
|
||||
The default set of renderers may be set globally, using the `DEFAULT_RENDERER_CLASSES` setting. For example, the following settings would use `JSON` as the main media type and also include the self describing API.
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_RENDERER_CLASSES': (
|
||||
'rest_framework.renderers.YAMLRenderer',
|
||||
'rest_framework.renderers.JSONRenderer',
|
||||
'rest_framework.renderers.BrowsableAPIRenderer',
|
||||
)
|
||||
}
|
||||
|
@ -31,15 +31,15 @@ You can also set the renderers used for an individual view, or viewset,
|
|||
using the `APIView` class based views.
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from rest_framework.renderers import JSONRenderer, YAMLRenderer
|
||||
from rest_framework.renderers import JSONRenderer
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
class UserCountView(APIView):
|
||||
"""
|
||||
A view that returns the count of active users, in JSON or YAML.
|
||||
A view that returns the count of active users in JSON.
|
||||
"""
|
||||
renderer_classes = (JSONRenderer, YAMLRenderer)
|
||||
renderer_classes = (JSONRenderer, )
|
||||
|
||||
def get(self, request, format=None):
|
||||
user_count = User.objects.filter(active=True).count()
|
||||
|
@ -93,38 +93,6 @@ The default JSON encoding style can be altered using the `UNICODE_JSON` and `COM
|
|||
|
||||
**.charset**: `None`
|
||||
|
||||
## YAMLRenderer
|
||||
|
||||
Renders the request data into `YAML`.
|
||||
|
||||
Requires the `pyyaml` package to be installed.
|
||||
|
||||
Note that non-ascii characters will be rendered using `\uXXXX` character escape. For example:
|
||||
|
||||
unicode black star: "\u2605"
|
||||
|
||||
**.media_type**: `application/yaml`
|
||||
|
||||
**.format**: `'.yaml'`
|
||||
|
||||
**.charset**: `utf-8`
|
||||
|
||||
## UnicodeYAMLRenderer
|
||||
|
||||
Renders the request data into `YAML`.
|
||||
|
||||
Requires the `pyyaml` package to be installed.
|
||||
|
||||
Note that non-ascii characters will not be character escaped. For example:
|
||||
|
||||
unicode black star: ★
|
||||
|
||||
**.media_type**: `application/yaml`
|
||||
|
||||
**.format**: `'.yaml'`
|
||||
|
||||
**.charset**: `utf-8`
|
||||
|
||||
## TemplateHTMLRenderer
|
||||
|
||||
Renders data to HTML, using Django's standard template rendering.
|
||||
|
|
|
@ -12,10 +12,10 @@ For example your project's `settings.py` file might include something like this:
|
|||
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_RENDERER_CLASSES': (
|
||||
'rest_framework.renderers.YAMLRenderer',
|
||||
'rest_framework.renderers.JSONRenderer',
|
||||
),
|
||||
'DEFAULT_PARSER_CLASSES': (
|
||||
'rest_framework.parsers.YAMLParser',
|
||||
'rest_framework.parsers.JSONParser',
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -255,14 +255,14 @@ The default format used to make test requests may be set using the `TEST_REQUEST
|
|||
|
||||
If you need to test requests using something other than multipart or json requests, you can do so by setting the `TEST_REQUEST_RENDERER_CLASSES` setting.
|
||||
|
||||
For example, to add support for using `format='yaml'` in test requests, you might have something like this in your `settings.py` file.
|
||||
For example, to add support for using `format='html'` in test requests, you might have something like this in your `settings.py` file.
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
...
|
||||
'TEST_REQUEST_RENDERER_CLASSES': (
|
||||
'rest_framework.renderers.MultiPartRenderer',
|
||||
'rest_framework.renderers.JSONRenderer',
|
||||
'rest_framework.renderers.YAMLRenderer'
|
||||
'rest_framework.renderers.TemplateHTMLRenderer'
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -54,7 +54,6 @@ REST framework requires the following:
|
|||
The following packages are optional:
|
||||
|
||||
* [Markdown][markdown] (2.1.0+) - Markdown support for the browsable API.
|
||||
* [PyYAML][yaml] (3.10+) - YAML content-type support.
|
||||
* [django-filter][django-filter] (0.5.4+) - Filtering support.
|
||||
* [django-restframework-oauth][django-restframework-oauth] package for OAuth 1.0a and 2.0 support.
|
||||
* [django-guardian][django-guardian] (1.1.1+) - Object level permissions support.
|
||||
|
@ -254,7 +253,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
[mozilla]: http://www.mozilla.org/en-US/about/
|
||||
[eventbrite]: https://www.eventbrite.co.uk/about/
|
||||
[markdown]: http://pypi.python.org/pypi/Markdown/
|
||||
[yaml]: http://pypi.python.org/pypi/PyYAML
|
||||
[django-filter]: http://pypi.python.org/pypi/django-filter
|
||||
[django-restframework-oauth]: https://github.com/jlafon/django-rest-framework-oauth
|
||||
[django-guardian]: https://github.com/lukaszb/django-guardian
|
||||
|
|
|
@ -92,7 +92,7 @@ Here is the view for an individual snippet, in the `views.py` module.
|
|||
|
||||
This should all feel very familiar - it is not a lot different from working with regular Django views.
|
||||
|
||||
Notice that we're no longer explicitly tying our requests or responses to a given content type. `request.data` can handle incoming `json` requests, but it can also handle `yaml` and other formats. Similarly we're returning response objects with data, but allowing REST framework to render the response into the correct content type for us.
|
||||
Notice that we're no longer explicitly tying our requests or responses to a given content type. `request.data` can handle incoming `json` requests, but it can also handle other formats. Similarly we're returning response objects with data, but allowing REST framework to render the response into the correct content type for us.
|
||||
|
||||
## Adding optional format suffixes to our URLs
|
||||
|
||||
|
|
|
@ -6,7 +6,11 @@ flake8==2.2.2
|
|||
|
||||
# Optional packages
|
||||
markdown>=2.1.0
|
||||
<<<<<<< HEAD
|
||||
PyYAML>=3.10
|
||||
=======
|
||||
defusedxml>=0.3
|
||||
>>>>>>> 731c8421afe3093a78cdabb9c3cc28fa52cd1c8e
|
||||
django-guardian==1.2.4
|
||||
django-filter>=0.5.4
|
||||
Pillow==2.3.0
|
||||
|
|
|
@ -237,13 +237,6 @@ except ImportError:
|
|||
apply_markdown = None
|
||||
|
||||
|
||||
# Yaml is optional
|
||||
try:
|
||||
import yaml
|
||||
except ImportError:
|
||||
yaml = None
|
||||
|
||||
|
||||
# `seperators` argument to `json.dumps()` differs between 2.x and 3.x
|
||||
# See: http://bugs.python.org/issue22767
|
||||
if six.PY3:
|
||||
|
|
|
@ -12,7 +12,7 @@ from django.http import QueryDict
|
|||
from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser
|
||||
from django.http.multipartparser import MultiPartParserError, parse_header, ChunkIter
|
||||
from django.utils import six
|
||||
from rest_framework.compat import yaml, force_text, urlparse
|
||||
from rest_framework.compat import force_text, urlparse
|
||||
from rest_framework.exceptions import ParseError
|
||||
from rest_framework import renderers
|
||||
import json
|
||||
|
@ -63,29 +63,6 @@ class JSONParser(BaseParser):
|
|||
raise ParseError('JSON parse error - %s' % six.text_type(exc))
|
||||
|
||||
|
||||
class YAMLParser(BaseParser):
|
||||
"""
|
||||
Parses YAML-serialized data.
|
||||
"""
|
||||
|
||||
media_type = 'application/yaml'
|
||||
|
||||
def parse(self, stream, media_type=None, parser_context=None):
|
||||
"""
|
||||
Parses the incoming bytestream as YAML and returns the resulting data.
|
||||
"""
|
||||
assert yaml, 'YAMLParser requires pyyaml to be installed'
|
||||
|
||||
parser_context = parser_context or {}
|
||||
encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
|
||||
|
||||
try:
|
||||
data = stream.read().decode(encoding)
|
||||
return yaml.safe_load(data)
|
||||
except (ValueError, yaml.parser.ParserError) as exc:
|
||||
raise ParseError('YAML parse error - %s' % six.text_type(exc))
|
||||
|
||||
|
||||
class FormParser(BaseParser):
|
||||
"""
|
||||
Parser for form data.
|
||||
|
|
|
@ -17,7 +17,7 @@ from django.template import Context, RequestContext, loader, Template
|
|||
from django.test.client import encode_multipart
|
||||
from django.utils import six
|
||||
from rest_framework import exceptions, serializers, status, VERSION
|
||||
from rest_framework.compat import SHORT_SEPARATORS, LONG_SEPARATORS, yaml
|
||||
from rest_framework.compat import SHORT_SEPARATORS, LONG_SEPARATORS
|
||||
from rest_framework.exceptions import ParseError
|
||||
from rest_framework.settings import api_settings
|
||||
from rest_framework.request import is_form_media_type, override_method
|
||||
|
@ -103,29 +103,6 @@ class JSONRenderer(BaseRenderer):
|
|||
return ret
|
||||
|
||||
|
||||
class YAMLRenderer(BaseRenderer):
|
||||
"""
|
||||
Renderer which serializes to YAML.
|
||||
"""
|
||||
|
||||
media_type = 'application/yaml'
|
||||
format = 'yaml'
|
||||
encoder = encoders.SafeDumper
|
||||
charset = 'utf-8'
|
||||
ensure_ascii = False
|
||||
|
||||
def render(self, data, accepted_media_type=None, renderer_context=None):
|
||||
"""
|
||||
Renders `data` into serialized YAML.
|
||||
"""
|
||||
assert yaml, 'YAMLRenderer requires pyyaml to be installed'
|
||||
|
||||
if data is None:
|
||||
return ''
|
||||
|
||||
return yaml.dump(data, stream=None, encoding=self.charset, Dumper=self.encoder, allow_unicode=not self.ensure_ascii)
|
||||
|
||||
|
||||
class TemplateHTMLRenderer(BaseRenderer):
|
||||
"""
|
||||
An HTML renderer for use with templates.
|
||||
|
|
|
@ -5,11 +5,11 @@ For example your project's `settings.py` file might look like this:
|
|||
REST_FRAMEWORK = {
|
||||
'DEFAULT_RENDERER_CLASSES': (
|
||||
'rest_framework.renderers.JSONRenderer',
|
||||
'rest_framework.renderers.YAMLRenderer',
|
||||
'rest_framework.renderers.TemplateHTMLRenderer',
|
||||
)
|
||||
'DEFAULT_PARSER_CLASSES': (
|
||||
'rest_framework.parsers.JSONParser',
|
||||
'rest_framework.parsers.YAMLParser',
|
||||
'rest_framework.parsers.TemplateHTMLRenderer',
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -5,10 +5,9 @@ from __future__ import unicode_literals
|
|||
from django.db.models.query import QuerySet
|
||||
from django.utils import six, timezone
|
||||
from django.utils.functional import Promise
|
||||
from rest_framework.compat import force_text, OrderedDict
|
||||
from rest_framework.compat import force_text
|
||||
import datetime
|
||||
import decimal
|
||||
import types
|
||||
import json
|
||||
|
||||
|
||||
|
@ -56,65 +55,3 @@ class JSONEncoder(json.JSONEncoder):
|
|||
elif hasattr(obj, '__iter__'):
|
||||
return tuple(item for item in obj)
|
||||
return super(JSONEncoder, self).default(obj)
|
||||
|
||||
|
||||
try:
|
||||
import yaml
|
||||
except ImportError:
|
||||
SafeDumper = None
|
||||
else:
|
||||
# Adapted from http://pyyaml.org/attachment/ticket/161/use_ordered_dict.py
|
||||
class SafeDumper(yaml.SafeDumper):
|
||||
"""
|
||||
Handles decimals as strings.
|
||||
Handles OrderedDicts as usual dicts, but preserves field order, rather
|
||||
than the usual behaviour of sorting the keys.
|
||||
"""
|
||||
def represent_decimal(self, data):
|
||||
return self.represent_scalar('tag:yaml.org,2002:str', six.text_type(data))
|
||||
|
||||
def represent_mapping(self, tag, mapping, flow_style=None):
|
||||
value = []
|
||||
node = yaml.MappingNode(tag, value, flow_style=flow_style)
|
||||
if self.alias_key is not None:
|
||||
self.represented_objects[self.alias_key] = node
|
||||
best_style = True
|
||||
if hasattr(mapping, 'items'):
|
||||
mapping = list(mapping.items())
|
||||
if not isinstance(mapping, OrderedDict):
|
||||
mapping.sort()
|
||||
for item_key, item_value in mapping:
|
||||
node_key = self.represent_data(item_key)
|
||||
node_value = self.represent_data(item_value)
|
||||
if not (isinstance(node_key, yaml.ScalarNode) and not node_key.style):
|
||||
best_style = False
|
||||
if not (isinstance(node_value, yaml.ScalarNode) and not node_value.style):
|
||||
best_style = False
|
||||
value.append((node_key, node_value))
|
||||
if flow_style is None:
|
||||
if self.default_flow_style is not None:
|
||||
node.flow_style = self.default_flow_style
|
||||
else:
|
||||
node.flow_style = best_style
|
||||
return node
|
||||
|
||||
SafeDumper.add_representer(
|
||||
decimal.Decimal,
|
||||
SafeDumper.represent_decimal
|
||||
)
|
||||
SafeDumper.add_representer(
|
||||
OrderedDict,
|
||||
yaml.representer.SafeRepresenter.represent_dict
|
||||
)
|
||||
# SafeDumper.add_representer(
|
||||
# DictWithMetadata,
|
||||
# yaml.representer.SafeRepresenter.represent_dict
|
||||
# )
|
||||
# SafeDumper.add_representer(
|
||||
# OrderedDictWithMetadata,
|
||||
# yaml.representer.SafeRepresenter.represent_dict
|
||||
# )
|
||||
SafeDumper.add_representer(
|
||||
types.GeneratorType,
|
||||
yaml.representer.SafeRepresenter.represent_list
|
||||
)
|
||||
|
|
|
@ -9,11 +9,9 @@ from django.test import TestCase
|
|||
from django.utils import six
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import status, permissions
|
||||
from rest_framework.compat import yaml, BytesIO
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, BrowsableAPIRenderer
|
||||
from rest_framework.parsers import YAMLParser
|
||||
from rest_framework.renderers import BaseRenderer, JSONRenderer, BrowsableAPIRenderer
|
||||
from rest_framework.settings import api_settings
|
||||
from rest_framework.test import APIRequestFactory
|
||||
from collections import MutableMapping
|
||||
|
@ -394,55 +392,6 @@ class AsciiJSONRendererTests(TestCase):
|
|||
self.assertEqual(content, '{"countries":["United Kingdom","France","Espa\\u00f1a"]}'.encode('utf-8'))
|
||||
|
||||
|
||||
if yaml:
|
||||
_yaml_repr = 'foo: [bar, baz]\n'
|
||||
|
||||
class YAMLRendererTests(TestCase):
|
||||
"""
|
||||
Tests specific to the YAML Renderer
|
||||
"""
|
||||
|
||||
def test_render(self):
|
||||
"""
|
||||
Test basic YAML rendering.
|
||||
"""
|
||||
obj = {'foo': ['bar', 'baz']}
|
||||
renderer = YAMLRenderer()
|
||||
content = renderer.render(obj, 'application/yaml')
|
||||
self.assertEqual(content.decode('utf-8'), _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()
|
||||
parser = YAMLParser()
|
||||
|
||||
content = renderer.render(obj, 'application/yaml')
|
||||
data = parser.parse(BytesIO(content))
|
||||
self.assertEqual(obj, data)
|
||||
|
||||
def test_render_decimal(self):
|
||||
"""
|
||||
Test YAML decimal rendering.
|
||||
"""
|
||||
renderer = YAMLRenderer()
|
||||
content = renderer.render({'field': Decimal('111.2')}, 'application/yaml')
|
||||
self.assertYAMLContains(content.decode('utf-8'), "field: '111.2'")
|
||||
|
||||
def assertYAMLContains(self, content, string):
|
||||
self.assertTrue(string in content, '%r not in %r' % (string, content))
|
||||
|
||||
def test_proper_encoding(self):
|
||||
obj = {'countries': ['United Kingdom', 'France', 'España']}
|
||||
renderer = YAMLRenderer()
|
||||
content = renderer.render(obj, 'application/yaml')
|
||||
self.assertEqual(content.strip(), 'countries: [United Kingdom, France, España]'.encode('utf-8'))
|
||||
|
||||
|
||||
# Tests for caching issue, #346
|
||||
class CacheRenderTest(TestCase):
|
||||
"""
|
||||
|
|
|
@ -54,7 +54,7 @@ class Issue1386Tests(TestCase):
|
|||
|
||||
class URLizerTests(TestCase):
|
||||
"""
|
||||
Test if both JSON and YAML URLs are transformed into links well
|
||||
Test if JSON URLs are transformed into links well
|
||||
"""
|
||||
def _urlize_dict_check(self, data):
|
||||
"""
|
||||
|
@ -73,14 +73,3 @@ class URLizerTests(TestCase):
|
|||
data['"foo_set": [\n "http://api/foos/1/"\n], '] = \
|
||||
'"foo_set": [\n "<a href="http://api/foos/1/">http://api/foos/1/</a>"\n], '
|
||||
self._urlize_dict_check(data)
|
||||
|
||||
def test_yaml_with_url(self):
|
||||
"""
|
||||
Test if YAML URLs are transformed into links well
|
||||
"""
|
||||
data = {}
|
||||
data['''{users: 'http://api/users/'}'''] = \
|
||||
'''{users: '<a href="http://api/users/">http://api/users/</a>'}'''
|
||||
data['''foo_set: ['http://api/foos/1/']'''] = \
|
||||
'''foo_set: ['<a href="http://api/foos/1/">http://api/foos/1/</a>']'''
|
||||
self._urlize_dict_check(data)
|
||||
|
|
Loading…
Reference in New Issue
Block a user