Commit f7b7778a by Tom Christie

pull in markos changes, minor tweaks to yaml stuff

parents c3babe75 f67c0651
...@@ -156,6 +156,7 @@ except ImportError: ...@@ -156,6 +156,7 @@ except ImportError:
def head(self, request, *args, **kwargs): def head(self, request, *args, **kwargs):
return self.get(request, *args, **kwargs) return self.get(request, *args, **kwargs)
# Markdown is optional
try: try:
import markdown import markdown
import re import re
...@@ -204,3 +205,9 @@ try: ...@@ -204,3 +205,9 @@ try:
except ImportError: except ImportError:
apply_markdown = None apply_markdown = None
# Yaml is optional
try:
import yaml
except ImportError:
yaml = None
...@@ -16,15 +16,18 @@ from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser ...@@ -16,15 +16,18 @@ from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser
from django.http.multipartparser import MultiPartParserError from django.http.multipartparser import MultiPartParserError
from django.utils import simplejson as json from django.utils import simplejson as json
from djangorestframework import status from djangorestframework import status
from djangorestframework.compat import yaml
from djangorestframework.response import ErrorResponse from djangorestframework.response import ErrorResponse
from djangorestframework.utils.mediatypes import media_type_matches from djangorestframework.utils.mediatypes import media_type_matches
__all__ = ( __all__ = (
'BaseParser', 'BaseParser',
'JSONParser', 'JSONParser',
'PlainTextParser', 'PlainTextParser',
'FormParser', 'FormParser',
'MultiPartParser', 'MultiPartParser',
'YAMLParser',
) )
...@@ -85,6 +88,27 @@ class JSONParser(BaseParser): ...@@ -85,6 +88,27 @@ class JSONParser(BaseParser):
{'detail': 'JSON parse error - %s' % unicode(exc)}) {'detail': 'JSON parse error - %s' % unicode(exc)})
if yaml:
class YAMLParser(BaseParser):
"""
Parses YAML-serialized data.
"""
media_type = 'application/yaml'
def parse(self, stream):
"""
Returns a 2-tuple of `(data, files)`.
`data` will be an object which is the parsed content of the response.
`files` will always be `None`.
"""
try:
return (yaml.safe_load(stream), None)
except ValueError, exc:
raise ErrorResponse(status.HTTP_400_BAD_REQUEST,
{'detail': 'YAML parse error - %s' % unicode(exc)})
class PlainTextParser(BaseParser): class PlainTextParser(BaseParser):
""" """
......
...@@ -11,16 +11,14 @@ from django.core.serializers.json import DateTimeAwareJSONEncoder ...@@ -11,16 +11,14 @@ from django.core.serializers.json import DateTimeAwareJSONEncoder
from django.template import RequestContext, loader from django.template import RequestContext, loader
from django.utils import simplejson as json from django.utils import simplejson as json
from djangorestframework import status
from djangorestframework.compat import apply_markdown from djangorestframework.compat import apply_markdown, yaml
from djangorestframework.utils import dict2xml, url_resolves from djangorestframework.utils import dict2xml, url_resolves
from djangorestframework.utils.breadcrumbs import get_breadcrumbs from djangorestframework.utils.breadcrumbs import get_breadcrumbs
from djangorestframework.utils.description import get_name, get_description from djangorestframework.utils.description import get_name, get_description
from djangorestframework.utils.mediatypes import get_media_type_params, add_media_type_param, media_type_matches from djangorestframework.utils.mediatypes import get_media_type_params, add_media_type_param, media_type_matches
from djangorestframework import VERSION from djangorestframework import VERSION
from decimal import Decimal
import re
import string import string
from urllib import quote_plus from urllib import quote_plus
...@@ -31,7 +29,8 @@ __all__ = ( ...@@ -31,7 +29,8 @@ __all__ = (
'DocumentingHTMLRenderer', 'DocumentingHTMLRenderer',
'DocumentingXHTMLRenderer', 'DocumentingXHTMLRenderer',
'DocumentingPlainTextRenderer', 'DocumentingPlainTextRenderer',
'XMLRenderer' 'XMLRenderer',
'YAMLRenderer'
) )
...@@ -131,6 +130,27 @@ class XMLRenderer(BaseRenderer): ...@@ -131,6 +130,27 @@ class XMLRenderer(BaseRenderer):
return dict2xml(obj) return dict2xml(obj)
if yaml:
class YAMLRenderer(BaseRenderer):
"""
Renderer which serializes to YAML.
"""
media_type = 'application/yaml'
format = 'yaml'
def render(self, obj=None, media_type=None):
"""
Renders *obj* into serialized YAML.
"""
if obj is None:
return ''
return yaml.dump(obj)
else:
YAMLRenderer = None
class TemplateRenderer(BaseRenderer): class TemplateRenderer(BaseRenderer):
""" """
A Base class provided for convenience. A Base class provided for convenience.
...@@ -361,4 +381,5 @@ DEFAULT_RENDERERS = ( JSONRenderer, ...@@ -361,4 +381,5 @@ DEFAULT_RENDERERS = ( JSONRenderer,
DocumentingPlainTextRenderer, DocumentingPlainTextRenderer,
XMLRenderer ) XMLRenderer )
if YAMLRenderer:
DEFAULT_RENDERERS += (YAMLRenderer,)
...@@ -8,6 +8,8 @@ ...@@ -8,6 +8,8 @@
#site-name a {color: #F4F379 !important;} #site-name a {color: #F4F379 !important;}
.errorlist {display: inline !important} .errorlist {display: inline !important}
.errorlist li {display: inline !important; background: white !important; color: black !important; border: 0 !important;} .errorlist li {display: inline !important; background: white !important; color: black !important; border: 0 !important;}
/* Custom styles */
.version{font-size:8px;}
</style> </style>
<link rel="stylesheet" type="text/css" href='{{ADMIN_MEDIA_PREFIX}}css/base.css'/> <link rel="stylesheet" type="text/css" href='{{ADMIN_MEDIA_PREFIX}}css/base.css'/>
<link rel="stylesheet" type="text/css" href='{{ADMIN_MEDIA_PREFIX}}css/forms.css'/> <link rel="stylesheet" type="text/css" href='{{ADMIN_MEDIA_PREFIX}}css/forms.css'/>
...@@ -18,7 +20,7 @@ ...@@ -18,7 +20,7 @@
<div id="header"> <div id="header">
<div id="branding"> <div id="branding">
<h1 id="site-name"><a href='http://django-rest-framework.org'>Django REST framework</a> <small>{{ version }}</small></h1> <h1 id="site-name"><a href='http://django-rest-framework.org'>Django REST framework</a> <span class="version"> v {{ version }}</span></h1>
</div> </div>
<div id="user-tools"> <div id="user-tools">
{% if user.is_active %}Welcome, {{ user }}.{% if logout_url %} <a href='{{ logout_url }}'>Log out</a>{% endif %}{% else %}Anonymous {% if login_url %}<a href='{{ login_url }}'>Log in</a>{% endif %}{% endif %} {% if user.is_active %}Welcome, {{ user }}.{% if logout_url %} <a href='{{ logout_url }}'>Log out</a>{% endif %}{% else %}Anonymous {% if login_url %}<a href='{{ login_url }}'>Log in</a>{% endif %}{% endif %}
...@@ -58,8 +60,8 @@ ...@@ -58,8 +60,8 @@
</form> </form>
{% endif %} {% endif %}
{# Only display the POST/PUT/DELETE forms if method tunneling via POST forms is enabled. #} {# Only display the POST/PUT/DELETE forms if method tunneling via POST forms is enabled and the user has permissions on this view. #}
{% if METHOD_PARAM %} {% if METHOD_PARAM and response.status != 403 %}
{% if 'POST' in view.allowed_methods %} {% if 'POST' in view.allowed_methods %}
<form action="{{ request.get_full_path }}" method="post" {% if post_form.is_multipart %}enctype="multipart/form-data"{% endif %}> <form action="{{ request.get_full_path }}" method="post" {% if post_form.is_multipart %}enctype="multipart/form-data"{% endif %}>
......
...@@ -4,8 +4,8 @@ from django.test import TestCase ...@@ -4,8 +4,8 @@ from django.test import TestCase
from djangorestframework import status from djangorestframework import status
from djangorestframework.compat import View as DjangoView from djangorestframework.compat import View as DjangoView
from djangorestframework.renderers import BaseRenderer, JSONRenderer from djangorestframework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer
from djangorestframework.parsers import JSONParser from djangorestframework.parsers import JSONParser, YAMLParser
from djangorestframework.mixins import ResponseMixin from djangorestframework.mixins import ResponseMixin
from djangorestframework.response import Response from djangorestframework.response import Response
from djangorestframework.utils.mediatypes import add_media_type_param from djangorestframework.utils.mediatypes import add_media_type_param
...@@ -189,3 +189,38 @@ class JSONRendererTests(TestCase): ...@@ -189,3 +189,38 @@ class JSONRendererTests(TestCase):
content = renderer.render(obj, 'application/json') content = renderer.render(obj, 'application/json')
(data, files) = parser.parse(StringIO(content)) (data, files) = parser.parse(StringIO(content))
self.assertEquals(obj, data) self.assertEquals(obj, data)
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)
\ No newline at end of file
...@@ -44,7 +44,8 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView): ...@@ -44,7 +44,8 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
renderers.DocumentingHTMLRenderer, renderers.DocumentingHTMLRenderer,
renderers.DocumentingXHTMLRenderer, renderers.DocumentingXHTMLRenderer,
renderers.DocumentingPlainTextRenderer, renderers.DocumentingPlainTextRenderer,
renderers.XMLRenderer ) renderers.XMLRenderer,
renderers.YAMLRenderer )
""" """
List of parsers the resource can parse the request with. List of parsers the resource can parse the request with.
......
- fields:
first_name: ''
groups: []
is_active: true
is_staff: true
is_superuser: true
last_name: ''
password: sha1$b3dff$671b4ab97f2714446da32670d27576614e176758
user_permissions: []
username: test
model: auth.user
pk: 2
#for fixture loading
\ No newline at end of file
from django.conf.urls.defaults import patterns, url from django.conf.urls.defaults import patterns, url
from permissionsexample.views import ThrottlingExampleView from permissionsexample.views import PermissionsExampleView, ThrottlingExampleView, LoggedInExampleView
urlpatterns = patterns('', urlpatterns = patterns('',
url(r'^$', ThrottlingExampleView.as_view(), name='throttled-resource'), url(r'^$', PermissionsExampleView.as_view(), name='permissions-example'),
url(r'^throttling$', ThrottlingExampleView.as_view(), name='throttled-resource'),
url(r'^loggedin$', LoggedInExampleView.as_view(), name='loggedin-resource'),
) )
from djangorestframework.views import View from djangorestframework.views import View
from djangorestframework.permissions import PerUserThrottling from djangorestframework.permissions import PerUserThrottling, IsAuthenticated
from django.core.urlresolvers import reverse
class PermissionsExampleView(View):
"""
A container view for permissions examples.
"""
def get(self, request):
return [{'name': 'Throttling Example', 'url': reverse('throttled-resource')},
{'name': 'Logged in example', 'url': reverse('loggedin-resource')},]
class ThrottlingExampleView(View): class ThrottlingExampleView(View):
""" """
...@@ -17,4 +27,12 @@ class ThrottlingExampleView(View): ...@@ -17,4 +27,12 @@ class ThrottlingExampleView(View):
""" """
Handle GET requests. Handle GET requests.
""" """
return "Successful response to GET request because throttle is not yet active." return "Successful response to GET request because throttle is not yet active."
\ No newline at end of file
class LoggedInExampleView(View):
"""
You can login with **'test', 'test'.**
"""
permissions = (IsAuthenticated, )
def get(self, request):
return 'Logged in or not?'
\ No newline at end of file
...@@ -4,3 +4,4 @@ ...@@ -4,3 +4,4 @@
Pygments==1.4 Pygments==1.4
Markdown==2.0.3 Markdown==2.0.3
...@@ -22,6 +22,7 @@ class Sandbox(View): ...@@ -22,6 +22,7 @@ class Sandbox(View):
4. A generic object store API. 4. A generic object store API.
5. A code highlighting API. 5. A code highlighting API.
6. A blog posts and comments API. 6. A blog posts and comments API.
7. A basic example using permissions.
Please feel free to browse, create, edit and delete the resources in these examples.""" Please feel free to browse, create, edit and delete the resources in these examples."""
...@@ -32,5 +33,5 @@ class Sandbox(View): ...@@ -32,5 +33,5 @@ class Sandbox(View):
{'name': 'Object store API', 'url': reverse('object-store-root')}, {'name': 'Object store API', 'url': reverse('object-store-root')},
{'name': 'Code highlighting API', 'url': reverse('pygments-root')}, {'name': 'Code highlighting API', 'url': reverse('pygments-root')},
{'name': 'Blog posts API', 'url': reverse('blog-posts-root')}, {'name': 'Blog posts API', 'url': reverse('blog-posts-root')},
{'name': 'Permissions example', 'url': reverse('throttled-resource')} {'name': 'Permissions example', 'url': reverse('permissions-example')}
] ]
...@@ -89,6 +89,12 @@ TEMPLATE_DIRS = ( ...@@ -89,6 +89,12 @@ TEMPLATE_DIRS = (
# Don't forget to use absolute paths, not relative paths. # Don't forget to use absolute paths, not relative paths.
) )
# for loading initial data
##SERIALIZATION_MODULES = {
# 'yml': "django.core.serializers.pyyaml"
#}
INSTALLED_APPS = ( INSTALLED_APPS = (
'django.contrib.auth', 'django.contrib.auth',
...@@ -104,6 +110,7 @@ INSTALLED_APPS = ( ...@@ -104,6 +110,7 @@ INSTALLED_APPS = (
'objectstore', 'objectstore',
'pygments_api', 'pygments_api',
'blogpost', 'blogpost',
'permissionsexample',
) )
import os import os
......
# We need Django. Duh. # We need Django. Duh.
# coverage isn't strictly a requirement, but it's useful.
Django==1.2.4 Django==1.2.4
wsgiref==0.1.2 wsgiref==0.1.2
coverage==3.4 coverage==3.4
...@@ -29,6 +29,7 @@ deps= ...@@ -29,6 +29,7 @@ deps=
django==1.2.4 django==1.2.4
coverage==3.4 coverage==3.4
unittest-xml-reporting==1.2 unittest-xml-reporting==1.2
Pyyaml==3.10
[testenv:py26-django12] [testenv:py26-django12]
basepython=python2.6 basepython=python2.6
...@@ -36,6 +37,7 @@ deps= ...@@ -36,6 +37,7 @@ deps=
django==1.2.4 django==1.2.4
coverage==3.4 coverage==3.4
unittest-xml-reporting==1.2 unittest-xml-reporting==1.2
Pyyaml==3.10
[testenv:py27-django12] [testenv:py27-django12]
basepython=python2.7 basepython=python2.7
...@@ -43,28 +45,32 @@ deps= ...@@ -43,28 +45,32 @@ deps=
django==1.2.4 django==1.2.4
coverage==3.4 coverage==3.4
unittest-xml-reporting==1.2 unittest-xml-reporting==1.2
Pyyaml==3.10
[testenv:py25-django13] [testenv:py25-django13]
basepython=python2.5 basepython=python2.5
deps= deps=
django==1.3 django==1.3
coverage==3.4 coverage==3.4
unittest-xml-reporting==1.2 unittest-xml-reporting==1.2
Pyyaml==3.10
[testenv:py26-django13] [testenv:py26-django13]
basepython=python2.6 basepython=python2.6
deps= deps=
django==1.3 django==1.3
coverage==3.4 coverage==3.4
unittest-xml-reporting==1.2 unittest-xml-reporting==1.2
Pyyaml==3.10
[testenv:py27-django13] [testenv:py27-django13]
basepython=python2.7 basepython=python2.7
deps= deps=
django==1.3 django==1.3
coverage==3.4 coverage==3.4
unittest-xml-reporting==1.2 unittest-xml-reporting==1.2
Pyyaml==3.10
####################################### EXAMPLES ################################################ ####################################### EXAMPLES ################################################
[testenv:py25-django12e] [testenv:py25-django12e]
...@@ -79,7 +85,8 @@ deps= ...@@ -79,7 +85,8 @@ deps=
httplib2==0.6.0 httplib2==0.6.0
Markdown==2.0.3 Markdown==2.0.3
unittest-xml-reporting==1.2 unittest-xml-reporting==1.2
Pyyaml==3.10
[testenv:py26-django12e] [testenv:py26-django12e]
basepython=python2.6 basepython=python2.6
commands= commands=
...@@ -92,7 +99,8 @@ deps= ...@@ -92,7 +99,8 @@ deps=
httplib2==0.6.0 httplib2==0.6.0
Markdown==2.0.3 Markdown==2.0.3
unittest-xml-reporting==1.2 unittest-xml-reporting==1.2
Pyyaml==3.10
[testenv:py27-django12e] [testenv:py27-django12e]
basepython=python2.7 basepython=python2.7
commands= commands=
...@@ -105,7 +113,8 @@ deps= ...@@ -105,7 +113,8 @@ deps=
httplib2==0.6.0 httplib2==0.6.0
Markdown==2.0.3 Markdown==2.0.3
unittest-xml-reporting==1.2 unittest-xml-reporting==1.2
Pyyaml==3.10
[testenv:py25-django13e] [testenv:py25-django13e]
basepython=python2.5 basepython=python2.5
commands= commands=
...@@ -118,7 +127,8 @@ deps= ...@@ -118,7 +127,8 @@ deps=
httplib2==0.6.0 httplib2==0.6.0
Markdown==2.0.3 Markdown==2.0.3
unittest-xml-reporting==1.2 unittest-xml-reporting==1.2
Pyyaml==3.10
[testenv:py26-django13e] [testenv:py26-django13e]
basepython=python2.6 basepython=python2.6
commands= commands=
...@@ -131,7 +141,8 @@ deps= ...@@ -131,7 +141,8 @@ deps=
httplib2==0.6.0 httplib2==0.6.0
Markdown==2.0.3 Markdown==2.0.3
unittest-xml-reporting==1.2 unittest-xml-reporting==1.2
Pyyaml==3.10
[testenv:py27-django13e] [testenv:py27-django13e]
basepython=python2.7 basepython=python2.7
commands= commands=
...@@ -143,4 +154,5 @@ deps= ...@@ -143,4 +154,5 @@ deps=
Pygments==1.4 Pygments==1.4
httplib2==0.6.0 httplib2==0.6.0
Markdown==2.0.3 Markdown==2.0.3
unittest-xml-reporting==1.2 unittest-xml-reporting==1.2
\ No newline at end of file Pyyaml==3.10
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment