pull in markos changes, minor tweaks to yaml stuff

This commit is contained in:
Tom Christie 2011-07-01 17:44:08 +01:00
commit f7b7778a79
15 changed files with 172 additions and 28 deletions

View File

@ -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:
except ImportError: except ImportError:
apply_markdown = None apply_markdown = None
# Yaml is optional
try:
import yaml
except ImportError:
yaml = None

View File

@ -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):
{'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):
""" """

View File

@ -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__ = (
'DocumentingHTMLRenderer', 'DocumentingHTMLRenderer',
'DocumentingXHTMLRenderer', 'DocumentingXHTMLRenderer',
'DocumentingPlainTextRenderer', 'DocumentingPlainTextRenderer',
'XMLRenderer' 'XMLRenderer',
'YAMLRenderer'
) )
@ -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,
DocumentingPlainTextRenderer, DocumentingPlainTextRenderer,
XMLRenderer ) XMLRenderer )
if YAMLRenderer:
DEFAULT_RENDERERS += (YAMLRenderer,)

View File

@ -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 @@
<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 @@
</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 %}>

View File

@ -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):
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)

View File

@ -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.

View File

@ -0,0 +1,12 @@
- 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

View File

@ -0,0 +1 @@
#for fixture loading

View File

@ -1,6 +1,8 @@
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'),
) )

View File

@ -1,6 +1,16 @@
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):
""" """
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."
class LoggedInExampleView(View):
"""
You can login with **'test', 'test'.**
"""
permissions = (IsAuthenticated, )
def get(self, request):
return 'Logged in or not?'

View File

@ -4,3 +4,4 @@
Pygments==1.4 Pygments==1.4
Markdown==2.0.3 Markdown==2.0.3

View File

@ -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):
{'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')}
] ]

View File

@ -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 = (
'objectstore', 'objectstore',
'pygments_api', 'pygments_api',
'blogpost', 'blogpost',
'permissionsexample',
) )
import os import os

View File

@ -1,6 +1,6 @@
# 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

32
tox.ini
View File

@ -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=
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=
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=
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=
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=
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=
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=
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=
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
Pyyaml==3.10