Commits

Jesper Nøhr committed 62a14d3 Merge

Comments (0)

Files changed (9)

 django-piston was originally written by Jesper Noehr, but since
 its release, many people have contributed invaluable feedback and patches:
 
-Alberto Donacko provided a fix for #26
+Alberto Donato provided a fix for #26, contributed #35, #37 and #38
 Matthew Marshall provided fixes for #25 and #24
 Pete Karl reported #20
 Travis Jensen provided a fix for #24

piston/emitters.py

+from __future__ import generators
+
 import types, decimal, types, re, inspect
 
 try:
                         if inst:
                             if hasattr(inst, 'all'):
                                 ret[model] = _related(inst, fields)
+                            elif callable(inst):
+                                if len(inspect.getargspec(inst)[0]) == 1:
+                                    ret[model] = _any(inst(), fields)
                             else:
                                 ret[model] = _model(inst, fields)
 
                         if maybe:
                             if isinstance(maybe, (int, basestring)):
                                 ret[maybe_field] = _any(maybe)
+                            elif callable(maybe):
+                                if len(inspect.getargspec(maybe)[0]) == 1:
+                                    ret[maybe_field] = _any(maybe())
                         else:
                             handler_f = getattr(handler or self.handler, maybe_field, None)
 
         """
         raise NotImplementedError("Please implement render.")
         
+    def stream_render(self, request, stream=True):
+        """
+        Tells our patched middleware not to look
+        at the contents, and returns a generator
+        rather than the buffered string. Should be
+        more memory friendly for large datasets.
+        """
+        yield self.render(request)
+        
     @classmethod
     def get(cls, format):
         """

piston/handler.py

 from piston.utils import rc
+from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
 
 typemapper = { }
 
     def read(self, request, *args, **kwargs):
         if not self.has_model():
             return rc.NOT_IMPLEMENTED
-        
-        return self.model.objects.filter(*args, **kwargs)
+
+        pkfield = self.model._meta.pk.name
+
+        if pkfield in kwargs:
+            try:
+                return self.model.objects.get(pk=kwargs.get(pkfield))
+            except ObjectDoesNotExist:
+                return rc.NOT_FOUND
+            except MultipleObjectsReturned: # should never happen, since we're using a PK
+                return rc.BAD_REQUEST
+        else:
+            return self.model.objects.filter(*args, **kwargs)
     
     def create(self, request, *args, **kwargs):
         if not self.has_model():

piston/middleware.py

+from django.middleware.http import ConditionalGetMiddleware
+from django.middleware.common import CommonMiddleware
+
+def compat_middleware_factory(klass):
+    """
+    Class wrapper that only executes `process_response`
+    if `streaming` is not set on the `HttpResponse` object.
+    Django has a bad habbit of looking at the content,
+    which will prematurely exhaust the data source if we're
+    using generators or buffers.
+    """
+    class compatwrapper(klass):
+        def process_response(self, req, resp):
+            if not hasattr(resp, 'streaming'):
+                return klass.process_response(self, req, resp)
+            return resp
+    return compatwrapper
+
+ConditionalMiddlewareCompatProxy = compat_middleware_factory(ConditionalGetMiddleware)
+CommonMiddlewareCompatProxy = compat_middleware_factory(CommonMiddleware)

piston/resource.py

         # Erroring
         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)
+
     def determine_emitter(self, request, *args, **kwargs):
         """
         Function for determening which emitter to use
         srl = emitter(result, typemapper, handler, handler.fields, anonymous)
         
         try:
-            return HttpResponse(srl.render(request), mimetype=ct)
+            """
+            Decide whether or not we want a generator here,
+            or we just want to buffer up the entire result
+            before sending it to the client. Won't matter for
+            smaller datasets, but larger will have an impact.
+            """
+            if self.stream: stream = srl.stream_render(request)
+            else: stream = srl.render(request)
+
+            resp = HttpResponse(stream, mimetype=ct)
+
+            resp.streaming = self.stream
+
+            return resp
         except HttpStatusCode, e:
             return HttpResponse(e.message, status=e.code)
 
                  DELETED = ('', 204), # 204 says "Don't send a body!"
                  BAD_REQUEST = ('Bad Request', 400),
                  FORBIDDEN = ('Forbidden', 401),
+                 NOT_FOUND = ('Not Found', 404),
                  DUPLICATE_ENTRY = ('Conflict/Duplicate', 409),
                  NOT_HERE = ('Gone', 410),
                  NOT_IMPLEMENTED = ('Not Implemented', 501),
         except TypeError:
             raise AttributeError(attr)
 
-        return HttpResponse(r, c)
+        return HttpResponse(r, content_type='text/plain', status=c)
     
 rc = rc_factory()
     
     return wrap
 
 require_extended = require_mime('json', 'yaml', 'xml', 'pickle')
-    
+    

tests/buildout.cfg

 [buildout]
-parts = django 
+parts = django-1.1 django-1.0
 develop = ..
 eggs = django-piston
 
-[django]
+[django-1.1]
+recipe = djangorecipe
+version = trunk
+project = test_project
+settings = settings
+test = testapp
+eggs = ${buildout:eggs}
+testrunner = test-1.1
+
+[django-1.0]
 recipe = djangorecipe
 version = 1.0.2
 project = test_project
 settings = settings
 test = testapp
 eggs = ${buildout:eggs}
+testrunner = test-1.0

tests/test_project/apps/testapp/tests.py

         resp = self.client.post('/api/expressive.json', outgoing, content_type='application/json',
             HTTP_AUTHORIZATION=self.auth_string)
             
-        self.assertEquals(resp.status_code, 200)
+        self.assertEquals(resp.status_code, 201)
         
         expected = '[{"content": "bar", "comments": [], "title": "foo"}, {"content": "bar2", "comments": [], "title": "foo2"}, {"content": "test", "comments": [{"content": "test1"}, {"content": "test2"}], "title": "test"}]'
         
         resp = self.client.post('/api/expressive.json', outgoing, content_type='application/x-yaml',
             HTTP_AUTHORIZATION=self.auth_string)
         
-        self.assertEquals(resp.status_code, 200)
+        self.assertEquals(resp.status_code, 201)
         
         expected = """- comments: []
   content: bar

tests/test_project/settings.py

     os.path.join(os.path.dirname(__file__), 'templates'),
 )
 ROOT_URLCONF = 'test_project.urls'
+
+MIDDLEWARE_CLASSES = (
+    'piston.middleware.ConditionalMiddlewareCompatProxy',
+    'piston.middleware.CommonMiddlewareCompatProxy',
+)