diff options
| -rw-r--r-- | djangorestframework/compat.py | 7 | ||||
| -rw-r--r-- | djangorestframework/parsers.py | 24 | ||||
| -rw-r--r-- | djangorestframework/renderers.py | 33 | ||||
| -rw-r--r-- | djangorestframework/templates/renderer.html | 8 | ||||
| -rw-r--r-- | djangorestframework/tests/renderers.py | 39 | ||||
| -rw-r--r-- | djangorestframework/views.py | 3 | ||||
| -rw-r--r-- | examples/permissionsexample/fixtures/initial_data.yaml | 12 | ||||
| -rw-r--r-- | examples/permissionsexample/models.py | 1 | ||||
| -rw-r--r-- | examples/permissionsexample/urls.py | 6 | ||||
| -rw-r--r-- | examples/permissionsexample/views.py | 22 | ||||
| -rw-r--r-- | examples/requirements.txt | 1 | ||||
| -rw-r--r-- | examples/sandbox/views.py | 3 | ||||
| -rw-r--r-- | examples/settings.py | 7 | ||||
| -rw-r--r-- | requirements.txt | 2 | ||||
| -rw-r--r-- | tox.ini | 32 |
15 files changed, 172 insertions, 28 deletions
diff --git a/djangorestframework/compat.py b/djangorestframework/compat.py index 827b4adf..230172c3 100644 --- a/djangorestframework/compat.py +++ b/djangorestframework/compat.py @@ -156,6 +156,7 @@ except ImportError: def head(self, request, *args, **kwargs): return self.get(request, *args, **kwargs) +# Markdown is optional try: import markdown import re @@ -204,3 +205,9 @@ try: except ImportError: apply_markdown = None + +# Yaml is optional +try: + import yaml +except ImportError: + yaml = None diff --git a/djangorestframework/parsers.py b/djangorestframework/parsers.py index 37882984..5f19c521 100644 --- a/djangorestframework/parsers.py +++ b/djangorestframework/parsers.py @@ -16,15 +16,18 @@ from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser from django.http.multipartparser import MultiPartParserError from django.utils import simplejson as json from djangorestframework import status +from djangorestframework.compat import yaml from djangorestframework.response import ErrorResponse from djangorestframework.utils.mediatypes import media_type_matches + __all__ = ( 'BaseParser', 'JSONParser', 'PlainTextParser', 'FormParser', 'MultiPartParser', + 'YAMLParser', ) @@ -85,6 +88,27 @@ class JSONParser(BaseParser): {'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): """ diff --git a/djangorestframework/renderers.py b/djangorestframework/renderers.py index e09e2abc..aae2cab2 100644 --- a/djangorestframework/renderers.py +++ b/djangorestframework/renderers.py @@ -11,16 +11,14 @@ from django.core.serializers.json import DateTimeAwareJSONEncoder from django.template import RequestContext, loader 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.breadcrumbs import get_breadcrumbs 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 import VERSION -from decimal import Decimal -import re import string from urllib import quote_plus @@ -31,7 +29,8 @@ __all__ = ( 'DocumentingHTMLRenderer', 'DocumentingXHTMLRenderer', 'DocumentingPlainTextRenderer', - 'XMLRenderer' + 'XMLRenderer', + 'YAMLRenderer' ) @@ -131,6 +130,27 @@ class XMLRenderer(BaseRenderer): 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): """ A Base class provided for convenience. @@ -361,4 +381,5 @@ DEFAULT_RENDERERS = ( JSONRenderer, DocumentingPlainTextRenderer, XMLRenderer ) - +if YAMLRenderer: + DEFAULT_RENDERERS += (YAMLRenderer,) diff --git a/djangorestframework/templates/renderer.html b/djangorestframework/templates/renderer.html index 5b32d1ec..3dd5faf3 100644 --- a/djangorestframework/templates/renderer.html +++ b/djangorestframework/templates/renderer.html @@ -8,6 +8,8 @@ #site-name a {color: #F4F379 !important;} .errorlist {display: inline !important} .errorlist li {display: inline !important; background: white !important; color: black !important; border: 0 !important;} + /* Custom styles */ + .version{font-size:8px;} </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/forms.css'/> @@ -18,7 +20,7 @@ <div id="header"> <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 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 %} @@ -58,8 +60,8 @@ </form> {% endif %} - {# Only display the POST/PUT/DELETE forms if method tunneling via POST forms is enabled. #} - {% if METHOD_PARAM %} + {# 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 and response.status != 403 %} {% if 'POST' in view.allowed_methods %} <form action="{{ request.get_full_path }}" method="post" {% if post_form.is_multipart %}enctype="multipart/form-data"{% endif %}> diff --git a/djangorestframework/tests/renderers.py b/djangorestframework/tests/renderers.py index bf135e55..d2046212 100644 --- a/djangorestframework/tests/renderers.py +++ b/djangorestframework/tests/renderers.py @@ -4,8 +4,8 @@ from django.test import TestCase from djangorestframework import status from djangorestframework.compat import View as DjangoView -from djangorestframework.renderers import BaseRenderer, JSONRenderer -from djangorestframework.parsers import JSONParser +from djangorestframework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer +from djangorestframework.parsers import JSONParser, YAMLParser from djangorestframework.mixins import ResponseMixin from djangorestframework.response import Response from djangorestframework.utils.mediatypes import add_media_type_param @@ -189,3 +189,38 @@ class JSONRendererTests(TestCase): content = renderer.render(obj, 'application/json') (data, files) = parser.parse(StringIO(content)) 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 diff --git a/djangorestframework/views.py b/djangorestframework/views.py index 18d064e1..757d89db 100644 --- a/djangorestframework/views.py +++ b/djangorestframework/views.py @@ -44,7 +44,8 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView): renderers.DocumentingHTMLRenderer, renderers.DocumentingXHTMLRenderer, renderers.DocumentingPlainTextRenderer, - renderers.XMLRenderer ) + renderers.XMLRenderer, + renderers.YAMLRenderer ) """ List of parsers the resource can parse the request with. diff --git a/examples/permissionsexample/fixtures/initial_data.yaml b/examples/permissionsexample/fixtures/initial_data.yaml new file mode 100644 index 00000000..22276ec2 --- /dev/null +++ b/examples/permissionsexample/fixtures/initial_data.yaml @@ -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 diff --git a/examples/permissionsexample/models.py b/examples/permissionsexample/models.py new file mode 100644 index 00000000..232085ad --- /dev/null +++ b/examples/permissionsexample/models.py @@ -0,0 +1 @@ +#for fixture loading
\ No newline at end of file diff --git a/examples/permissionsexample/urls.py b/examples/permissionsexample/urls.py index d17f5159..33cb9b5f 100644 --- a/examples/permissionsexample/urls.py +++ b/examples/permissionsexample/urls.py @@ -1,6 +1,8 @@ from django.conf.urls.defaults import patterns, url -from permissionsexample.views import ThrottlingExampleView +from permissionsexample.views import PermissionsExampleView, ThrottlingExampleView, LoggedInExampleView 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'), ) diff --git a/examples/permissionsexample/views.py b/examples/permissionsexample/views.py index 20e7cba7..f95c2c84 100644 --- a/examples/permissionsexample/views.py +++ b/examples/permissionsexample/views.py @@ -1,6 +1,16 @@ 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): """ @@ -17,4 +27,12 @@ class ThrottlingExampleView(View): """ Handle GET requests. """ - return "Successful response to GET request because throttle is not yet active."
\ No newline at end of file + 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?'
\ No newline at end of file diff --git a/examples/requirements.txt b/examples/requirements.txt index 0bcd8d43..70371574 100644 --- a/examples/requirements.txt +++ b/examples/requirements.txt @@ -4,3 +4,4 @@ Pygments==1.4 Markdown==2.0.3 + diff --git a/examples/sandbox/views.py b/examples/sandbox/views.py index 1e326f43..29f2e3ce 100644 --- a/examples/sandbox/views.py +++ b/examples/sandbox/views.py @@ -22,6 +22,7 @@ class Sandbox(View): 4. A generic object store API. 5. A code highlighting 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.""" @@ -32,5 +33,5 @@ class Sandbox(View): {'name': 'Object store API', 'url': reverse('object-store-root')}, {'name': 'Code highlighting API', 'url': reverse('pygments-root')}, {'name': 'Blog posts API', 'url': reverse('blog-posts-root')}, - {'name': 'Permissions example', 'url': reverse('throttled-resource')} + {'name': 'Permissions example', 'url': reverse('permissions-example')} ] diff --git a/examples/settings.py b/examples/settings.py index 1537c5f8..2eab2cfa 100644 --- a/examples/settings.py +++ b/examples/settings.py @@ -89,6 +89,12 @@ TEMPLATE_DIRS = ( # Don't forget to use absolute paths, not relative paths. ) +# for loading initial data +##SERIALIZATION_MODULES = { + # 'yml': "django.core.serializers.pyyaml" + +#} + INSTALLED_APPS = ( 'django.contrib.auth', @@ -104,6 +110,7 @@ INSTALLED_APPS = ( 'objectstore', 'pygments_api', 'blogpost', + 'permissionsexample', ) import os diff --git a/requirements.txt b/requirements.txt index 7ae0ee45..da076b79 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ # We need Django. Duh. +# coverage isn't strictly a requirement, but it's useful. Django==1.2.4 wsgiref==0.1.2 coverage==3.4 - @@ -29,6 +29,7 @@ deps= django==1.2.4 coverage==3.4 unittest-xml-reporting==1.2 + Pyyaml==3.10 [testenv:py26-django12] basepython=python2.6 @@ -36,6 +37,7 @@ deps= django==1.2.4 coverage==3.4 unittest-xml-reporting==1.2 + Pyyaml==3.10 [testenv:py27-django12] basepython=python2.7 @@ -43,28 +45,32 @@ deps= django==1.2.4 coverage==3.4 unittest-xml-reporting==1.2 - + Pyyaml==3.10 + [testenv:py25-django13] basepython=python2.5 deps= django==1.3 coverage==3.4 unittest-xml-reporting==1.2 - + Pyyaml==3.10 + [testenv:py26-django13] basepython=python2.6 deps= django==1.3 coverage==3.4 unittest-xml-reporting==1.2 - + Pyyaml==3.10 + [testenv:py27-django13] basepython=python2.7 deps= django==1.3 coverage==3.4 unittest-xml-reporting==1.2 - + Pyyaml==3.10 + ####################################### EXAMPLES ################################################ [testenv:py25-django12e] @@ -79,7 +85,8 @@ deps= httplib2==0.6.0 Markdown==2.0.3 unittest-xml-reporting==1.2 - + Pyyaml==3.10 + [testenv:py26-django12e] basepython=python2.6 commands= @@ -92,7 +99,8 @@ deps= httplib2==0.6.0 Markdown==2.0.3 unittest-xml-reporting==1.2 - + Pyyaml==3.10 + [testenv:py27-django12e] basepython=python2.7 commands= @@ -105,7 +113,8 @@ deps= httplib2==0.6.0 Markdown==2.0.3 unittest-xml-reporting==1.2 - + Pyyaml==3.10 + [testenv:py25-django13e] basepython=python2.5 commands= @@ -118,7 +127,8 @@ deps= httplib2==0.6.0 Markdown==2.0.3 unittest-xml-reporting==1.2 - + Pyyaml==3.10 + [testenv:py26-django13e] basepython=python2.6 commands= @@ -131,7 +141,8 @@ deps= httplib2==0.6.0 Markdown==2.0.3 unittest-xml-reporting==1.2 - + Pyyaml==3.10 + [testenv:py27-django13e] basepython=python2.7 commands= @@ -143,4 +154,5 @@ deps= Pygments==1.4 httplib2==0.6.0 Markdown==2.0.3 - unittest-xml-reporting==1.2
\ No newline at end of file + unittest-xml-reporting==1.2 + Pyyaml==3.10
\ No newline at end of file |
