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 | 
