Commits

Joshua Ginsberg committed c0b6130

Added support for HTTP Accept header; explicit format in request string overrides Accept header; if settings.PISTON_STRICT_ACCEPT_HANDLING is set to True, a HTTP status 406 is returned; requires mimeparse library to enable; Fixes #182

  • Participants
  • Parent commits 8744863

Comments (0)

Files changed (3)

File piston/resource.py

 from django.db.models.query import QuerySet
 from django.http import Http404
 
+try:
+    import mimeparse
+except ImportError:
+    mimeparse = None
+
 from emitters import Emitter
 from handler import typemapper
 from doc import HandlerMethod
         self.email_errors = getattr(settings, 'PISTON_EMAIL_ERRORS', True)
         self.display_errors = getattr(settings, 'PISTON_DISPLAY_ERRORS', True)
         self.stream = getattr(settings, 'PISTON_STREAM_OUTPUT', False)
+        # Emitter selection
+        self.strict_accept = getattr(settings, 'PISTON_STRICT_ACCEPT_HANDLING',
+                                     False)
+        self.default_emitter = getattr(settings, 'PISTON_DEFAULT_EMITTER',
+                                       'json')
 
     def determine_emitter(self, request, *args, **kwargs):
         """
         Function for determening which emitter to use
         for output. It lives here so you can easily subclass
         `Resource` in order to change how emission is detected.
-
-        You could also check for the `Accept` HTTP header here,
-        since that pretty much makes sense. Refer to `Mimer` for
-        that as well.
         """
-        em = kwargs.pop('emitter_format', None)
-
-        if not em:
-            em = request.GET.get('format', 'json')
-
-        return em
+        try:
+            return kwargs['emitter_format']
+        except KeyError:
+            pass
+        if 'format' in request.GET:
+            return request.GET.get('format')
+        if mimeparse and 'HTTP_ACCEPT' in request.META:
+            supported_mime_types = set()
+            emitter_map = {}
+            for name, (klass, content_type) in Emitter.EMITTERS.items():
+                content_type_without_encoding = content_type.split(';')[0]
+                supported_mime_types.add(content_type_without_encoding)
+                emitter_map[content_type_without_encoding] = name
+            preferred_content_type = mimeparse.best_match(
+                list(supported_mime_types),
+                request.META['HTTP_ACCEPT'])
+            return emitter_map.get(preferred_content_type, None)
 
     def form_validation_response(self, e):
         """
         if not meth:
             raise Http404
 
-        # Support emitter both through (?P<emitter_format>) and ?format=emitter.
+        # Support emitter through (?P<emitter_format>) and ?format=emitter
+        # and lastly Accept: header processing
         em_format = self.determine_emitter(request, *args, **kwargs)
+        if not em_format:
+            request_has_accept = 'HTTP_ACCEPT' in request.META
+            if request_has_accept and self.strict_accept:
+                return rc.NOT_ACCEPTABLE
+            em_format = self.default_emitter
 
         kwargs.pop('emitter_format', None)
 

File piston/utils.py

                  BAD_REQUEST = ('Bad Request', 400),
                  FORBIDDEN = ('Forbidden', 401),
                  NOT_FOUND = ('Not Found', 404),
+                 NOT_ACCEPTABLE = ('Not acceptable', 406),
                  DUPLICATE_ENTRY = ('Conflict/Duplicate', 409),
                  NOT_HERE = ('Gone', 410),
                  INTERNAL_ERROR = ('Internal Error', 500),

File tests/test_project/apps/testapp/tests.py

                           {'chaff': 'pewpewpew',
                            'file_size': len(content)})
 
-                    
+class EmitterFormat(MainTests):
+    def test_format_in_url(self):
+        resp = self.client.get('/api/entries.json',
+                               HTTP_AUTHORIZATION=self.auth_string)
+        self.assertEquals(resp.status_code, 200)
+        self.assertEquals(resp['Content-type'],
+                          'application/json; charset=utf-8')
+        resp = self.client.get('/api/entries.xml',
+                               HTTP_AUTHORIZATION=self.auth_string)
+        self.assertEquals(resp.status_code, 200)
+        self.assertEquals(resp['Content-type'],
+                          'text/xml; charset=utf-8')
+        resp = self.client.get('/api/entries.yaml',
+                               HTTP_AUTHORIZATION=self.auth_string)
+        self.assertEquals(resp.status_code, 200)
+        self.assertEquals(resp['Content-type'],
+                          'application/x-yaml; charset=utf-8')
+
+    def test_format_in_get_data(self):
+        resp = self.client.get('/api/entries/?format=json',
+                               HTTP_AUTHORIZATION=self.auth_string)
+        self.assertEquals(resp.status_code, 200)
+        self.assertEquals(resp['Content-type'],
+                          'application/json; charset=utf-8')
+        resp = self.client.get('/api/entries/?format=xml',
+                               HTTP_AUTHORIZATION=self.auth_string)
+        self.assertEquals(resp.status_code, 200)
+        self.assertEquals(resp['Content-type'],
+                          'text/xml; charset=utf-8')
+        resp = self.client.get('/api/entries/?format=yaml',
+                               HTTP_AUTHORIZATION=self.auth_string)
+        self.assertEquals(resp.status_code, 200)
+        self.assertEquals(resp['Content-type'],
+                          'application/x-yaml; charset=utf-8')
+        
+    def test_format_in_accept_headers(self):
+        resp = self.client.get('/api/entries/',
+                               HTTP_AUTHORIZATION=self.auth_string,
+                               HTTP_ACCEPT='application/json')
+        self.assertEquals(resp.status_code, 200)
+        self.assertEquals(resp['Content-type'],
+                          'application/json; charset=utf-8')
+        resp = self.client.get('/api/entries/',
+                               HTTP_AUTHORIZATION=self.auth_string,
+                               HTTP_ACCEPT='text/xml')
+        self.assertEquals(resp.status_code, 200)
+        self.assertEquals(resp['Content-type'],
+                          'text/xml; charset=utf-8')
+        resp = self.client.get('/api/entries/',
+                               HTTP_AUTHORIZATION=self.auth_string,
+                               HTTP_ACCEPT='application/x-yaml')
+        self.assertEquals(resp.status_code, 200)
+        self.assertEquals(resp['Content-type'],
+                          'application/x-yaml; charset=utf-8')
+    
+    def test_strict_accept_headers(self):
+        import urls
+        self.assertFalse(urls.entries.strict_accept)
+        self.assertEquals(urls.entries.default_emitter, 'json')
+        resp = self.client.get('/api/entries/',
+                               HTTP_AUTHORIZATION=self.auth_string,
+                               HTTP_ACCEPT='text/html')
+        self.assertEquals(resp.status_code, 200)
+        self.assertEquals(resp['Content-type'],
+                          'application/json; charset=utf-8')
+        
+        urls.entries.strict_accept = True
+        resp = self.client.get('/api/entries/',
+                               HTTP_AUTHORIZATION=self.auth_string,
+                               HTTP_ACCEPT='text/html')
+        self.assertEquals(resp.status_code, 406)
+
+