From 64b17840356dc60cd93b8b0309de6834be876ad7 Mon Sep 17 00:00:00 2001 From: Jesper Noehr Date: Tue, 21 Jul 2009 12:35:35 +0300 Subject: [PATCH 01/10] applying patch from benoitg. since we don't have any tests for oauth, I don't know if this will break anything. if it does, feel free to back out this changeset. --- piston/oauth.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/piston/oauth.py b/piston/oauth.py index 6090800..1e50f1c 100644 --- a/piston/oauth.py +++ b/piston/oauth.py @@ -247,10 +247,11 @@ class OAuthRequest(object): @staticmethod def _split_header(header): params = {} + header = header.replace('OAuth ', '', 1) parts = header.split(',') for param in parts: # ignore realm parameter - if param.find('OAuth realm') > -1: + if param.find('realm') > -1: continue # remove whitespace param = param.strip() From e55176fe3dcd1ca7e8a1431c08e81921f98a4b63 Mon Sep 17 00:00:00 2001 From: Jesper Noehr Date: Tue, 21 Jul 2009 12:36:22 +0300 Subject: [PATCH 02/10] adding benoitg to authors --- AUTHORS.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.txt b/AUTHORS.txt index 1fddf95..5bb422e 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -16,3 +16,4 @@ Brian McMurray for contributing a patch for #41 James Emerton for making the OAuth parts more usable/friendly Anton Tsigularov for providing a patch for incorrect multipart detection Remco Wendt for fixing up the example blog server to conform with 0.2.2, et. al +Benoit Garret for providing a fix for oauth headers, issue #56 From de75bd31a7e07f2cdc2faf1f7fe2db89316af676 Mon Sep 17 00:00:00 2001 From: Jesper Noehr Date: Tue, 21 Jul 2009 16:21:19 +0300 Subject: [PATCH 03/10] using CharField over URLField, seeing as callback is largely arbitrary --- piston/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/piston/forms.py b/piston/forms.py index 5d41031..0cf9d4b 100644 --- a/piston/forms.py +++ b/piston/forms.py @@ -23,7 +23,7 @@ class ModelForm(forms.ModelForm): class OAuthAuthenticationForm(forms.Form): oauth_token = forms.CharField(widget=forms.HiddenInput) - oauth_callback = forms.URLField(widget=forms.HiddenInput) + oauth_callback = forms.CharField(widget=forms.HiddenInput) authorize_access = forms.BooleanField(required=True) csrf_signature = forms.CharField(widget=forms.HiddenInput) From 5b269c0131d60a3703641c7e2f1cf6f034cd7973 Mon Sep 17 00:00:00 2001 From: "dfdeshom@gmail.com" Date: Thu, 23 Jul 2009 21:28:42 -0400 Subject: [PATCH 04/10] make handler process models that have a "read", "update", or "create"a attribute --- piston/emitters.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/piston/emitters.py b/piston/emitters.py index e82670d..b99decb 100644 --- a/piston/emitters.py +++ b/piston/emitters.py @@ -1,6 +1,7 @@ from __future__ import generators import decimal, re, inspect +import copy try: # yaml isn't standard with python. It shouldn't be required if it @@ -169,9 +170,14 @@ class Emitter(object): else: get_fields = set(fields) + + get_fields_copy = copy.deepcopy(get_fields) - met_fields = self.method_fields(handler, get_fields) + for reserved_field in ['read','update','delete','create']: + get_fields_copy.discard(reserved_field) + met_fields = self.method_fields(handler, get_fields_copy) + for f in data._meta.local_fields: if f.serialize and not any([ p in met_fields for p in [ f.attname, f.name ]]): if not f.rel: @@ -191,7 +197,7 @@ class Emitter(object): # try to get the remainder of fields for maybe_field in get_fields: - + print maybe_field, type(maybe_field) if isinstance(maybe_field, (list, tuple)): model, fields = maybe_field inst = getattr(data, model, None) From 1d4fcc4460f0f692dedf4fca8e547cd5564ecfec Mon Sep 17 00:00:00 2001 From: "dfdeshom@gmail.com" Date: Sat, 25 Jul 2009 11:20:47 -0400 Subject: [PATCH 05/10] remove print statement; verify that all tests pass --- piston/emitters.py | 1 - 1 file changed, 1 deletion(-) diff --git a/piston/emitters.py b/piston/emitters.py index b99decb..295de86 100644 --- a/piston/emitters.py +++ b/piston/emitters.py @@ -197,7 +197,6 @@ class Emitter(object): # try to get the remainder of fields for maybe_field in get_fields: - print maybe_field, type(maybe_field) if isinstance(maybe_field, (list, tuple)): model, fields = maybe_field inst = getattr(data, model, None) From e049e009cfaa28f48eefef4b7039942c1a73ddb3 Mon Sep 17 00:00:00 2001 From: "dfdeshom@gmail.com" Date: Sat, 25 Jul 2009 16:26:36 -0400 Subject: [PATCH 06/10] [mq]: issue-58 --- tests/test_project/apps/testapp/handlers.py | 17 ++++++++- tests/test_project/apps/testapp/models.py | 6 +++- tests/test_project/apps/testapp/tests.py | 39 ++++++++++++++++++++- tests/test_project/apps/testapp/urls.py | 6 ++-- 4 files changed, 63 insertions(+), 5 deletions(-) diff --git a/tests/test_project/apps/testapp/handlers.py b/tests/test_project/apps/testapp/handlers.py index 920f411..ece7208 100644 --- a/tests/test_project/apps/testapp/handlers.py +++ b/tests/test_project/apps/testapp/handlers.py @@ -3,7 +3,7 @@ from django.core.paginator import Paginator from piston.handler import BaseHandler from piston.utils import rc, validate -from models import TestModel, ExpressiveTestModel, Comment, InheritedModel, PlainOldObject +from models import TestModel, ExpressiveTestModel, Comment, InheritedModel, PlainOldObject, Issue58Model from forms import EchoForm from test_project.apps.testapp import signals @@ -76,3 +76,18 @@ class EchoHandler(BaseHandler): @validate(EchoForm, 'GET') def read(self, request): return {'msg': request.GET['msg']} + +class Issue58Handler(BaseHandler): + model = Issue58Model + + def read(self, request): + return Issue58Model.objects.all() + + def create(self, request): + if request.content_type: + data = request.data + em = self.model(read=data['read'], create=data['create']) + em.save() + return rc.CREATED + else: + super(Issue58Model, self).create(request) diff --git a/tests/test_project/apps/testapp/models.py b/tests/test_project/apps/testapp/models.py index 665471b..8c27836 100644 --- a/tests/test_project/apps/testapp/models.py +++ b/tests/test_project/apps/testapp/models.py @@ -28,4 +28,8 @@ class InheritedModel(AbstractModel): class PlainOldObject(object): def __emittable__(self): return {'type': 'plain', - 'field': 'a field'} \ No newline at end of file + 'field': 'a field'} + +class Issue58Model(models.Model): + read = models.BooleanField(default=False) + create = models.CharField(max_length=1, blank=True, null=True) diff --git a/tests/test_project/apps/testapp/tests.py b/tests/test_project/apps/testapp/tests.py index e9fae87..50b8dfb 100644 --- a/tests/test_project/apps/testapp/tests.py +++ b/tests/test_project/apps/testapp/tests.py @@ -15,7 +15,7 @@ except ImportError: import urllib, base64 -from test_project.apps.testapp.models import TestModel, ExpressiveTestModel, Comment, InheritedModel +from test_project.apps.testapp.models import TestModel, ExpressiveTestModel, Comment, InheritedModel, Issue58Model from test_project.apps.testapp import signals class MainTests(TestCase): @@ -370,3 +370,40 @@ class PlainOldObject(MainTests): resp = self.client.get('/api/popo') self.assertEquals(resp.status_code, 200) self.assertEquals({'type': 'plain', 'field': 'a field'}, simplejson.loads(resp.content)) + +class Issue58ModelTests(MainTests): + """ + This testcase addresses #58 in django-piston where if a model + has one of the ['read','update','delete','create'] defined + it make piston crash with a `TypeError` + """ + def init_delegate(self): + m1 = Issue58Model(read=True,create='t') + m1.save() + m2 = Issue58Model(read=False,create='f') + m2.save() + + def test_incoming_json(self): + outgoing = simplejson.dumps({ 'read': True, 'create': 'T'}) + + expected = """[ + { + "read": true, + "create": "t" + }, + { + "read": false, + "create": "f" + } +]""" + + # test GET + result = self.client.get('/api/issue58.json', + HTTP_AUTHORIZATION=self.auth_string).content + self.assertEquals(result, expected) + + # test POST + resp = self.client.post('/api/issue58.json', outgoing, content_type='application/json', + HTTP_AUTHORIZATION=self.auth_string) + + self.assertEquals(resp.status_code, 201) diff --git a/tests/test_project/apps/testapp/urls.py b/tests/test_project/apps/testapp/urls.py index 72fc0b8..0f1b12f 100644 --- a/tests/test_project/apps/testapp/urls.py +++ b/tests/test_project/apps/testapp/urls.py @@ -2,7 +2,7 @@ from django.conf.urls.defaults import * from piston.resource import Resource from piston.authentication import HttpBasicAuthentication -from test_project.apps.testapp.handlers import EntryHandler, ExpressiveHandler, AbstractHandler, EchoHandler, PlainOldObjectHandler +from test_project.apps.testapp.handlers import EntryHandler, ExpressiveHandler, AbstractHandler, EchoHandler, PlainOldObjectHandler, Issue58Handler auth = HttpBasicAuthentication(realm='TestApplication') @@ -11,7 +11,7 @@ expressive = Resource(handler=ExpressiveHandler, authentication=auth) abstract = Resource(handler=AbstractHandler, authentication=auth) echo = Resource(handler=EchoHandler) popo = Resource(handler=PlainOldObjectHandler) - +issue58 = Resource(handler=Issue58Handler) urlpatterns = patterns('', url(r'^entries/$', entries), @@ -19,6 +19,8 @@ urlpatterns = patterns('', url(r'^entries\.(?P.+)', entries), url(r'^entry-(?P.+)\.(?P.+)', entries), + url(r'^issue58\.(?P.+)$', issue58), + url(r'^expressive\.(?P.+)$', expressive), url(r'^abstract\.(?P.+)$', abstract), From 2614f80b1bf262f213c3e16cb2661a756b03b608 Mon Sep 17 00:00:00 2001 From: "dfdeshom@gmail.com" Date: Tue, 28 Jul 2009 13:52:18 -0400 Subject: [PATCH 07/10] cleaner solution to issue #58 thanks to Tim at http://groups.google.com/group/django-piston/browse_thread/thread/e2abac8f2c5342c7 --- piston/emitters.py | 9 ++------- tests/test_project/apps/testapp/handlers.py | 2 +- tests/test_project/apps/testapp/models.py | 3 ++- tests/test_project/apps/testapp/tests.py | 12 ++++++------ 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/piston/emitters.py b/piston/emitters.py index 295de86..24f6f77 100644 --- a/piston/emitters.py +++ b/piston/emitters.py @@ -69,7 +69,7 @@ class Emitter(object): ret = dict() for field in fields: - if field in has: + if field in has and callable(field): ret[field] = getattr(data, field) return ret @@ -170,13 +170,8 @@ class Emitter(object): else: get_fields = set(fields) - - get_fields_copy = copy.deepcopy(get_fields) - for reserved_field in ['read','update','delete','create']: - get_fields_copy.discard(reserved_field) - - met_fields = self.method_fields(handler, get_fields_copy) + met_fields = self.method_fields(handler, get_fields) for f in data._meta.local_fields: if f.serialize and not any([ p in met_fields for p in [ f.attname, f.name ]]): diff --git a/tests/test_project/apps/testapp/handlers.py b/tests/test_project/apps/testapp/handlers.py index ece7208..6c3b481 100644 --- a/tests/test_project/apps/testapp/handlers.py +++ b/tests/test_project/apps/testapp/handlers.py @@ -86,7 +86,7 @@ class Issue58Handler(BaseHandler): def create(self, request): if request.content_type: data = request.data - em = self.model(read=data['read'], create=data['create']) + em = self.model(read=data['read'], model=data['model']) em.save() return rc.CREATED else: diff --git a/tests/test_project/apps/testapp/models.py b/tests/test_project/apps/testapp/models.py index 8c27836..1655460 100644 --- a/tests/test_project/apps/testapp/models.py +++ b/tests/test_project/apps/testapp/models.py @@ -32,4 +32,5 @@ class PlainOldObject(object): class Issue58Model(models.Model): read = models.BooleanField(default=False) - create = models.CharField(max_length=1, blank=True, null=True) + model = models.CharField(max_length=1, blank=True, null=True) + diff --git a/tests/test_project/apps/testapp/tests.py b/tests/test_project/apps/testapp/tests.py index 50b8dfb..2e30890 100644 --- a/tests/test_project/apps/testapp/tests.py +++ b/tests/test_project/apps/testapp/tests.py @@ -378,22 +378,22 @@ class Issue58ModelTests(MainTests): it make piston crash with a `TypeError` """ def init_delegate(self): - m1 = Issue58Model(read=True,create='t') + m1 = Issue58Model(read=True,model='t') m1.save() - m2 = Issue58Model(read=False,create='f') + m2 = Issue58Model(read=False,model='f') m2.save() def test_incoming_json(self): - outgoing = simplejson.dumps({ 'read': True, 'create': 'T'}) + outgoing = simplejson.dumps({ 'read': True, 'model': 'T'}) expected = """[ { "read": true, - "create": "t" + "model": "t" }, { "read": false, - "create": "f" + "model": "f" } ]""" @@ -405,5 +405,5 @@ class Issue58ModelTests(MainTests): # test POST resp = self.client.post('/api/issue58.json', outgoing, content_type='application/json', HTTP_AUTHORIZATION=self.auth_string) - + print resp.__dict__ self.assertEquals(resp.status_code, 201) From 0f889aaf492afa15795ffcbdd1e2a80517e967b6 Mon Sep 17 00:00:00 2001 From: "dfdeshom@gmail.com" Date: Thu, 30 Jul 2009 15:09:39 -0400 Subject: [PATCH 08/10] eliminating extraneous print statement --- tests/test_project/apps/testapp/tests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_project/apps/testapp/tests.py b/tests/test_project/apps/testapp/tests.py index 2e30890..43c56c4 100644 --- a/tests/test_project/apps/testapp/tests.py +++ b/tests/test_project/apps/testapp/tests.py @@ -405,5 +405,4 @@ class Issue58ModelTests(MainTests): # test POST resp = self.client.post('/api/issue58.json', outgoing, content_type='application/json', HTTP_AUTHORIZATION=self.auth_string) - print resp.__dict__ self.assertEquals(resp.status_code, 201) From 89fc055e250491b822c42689027a8a36127a54be Mon Sep 17 00:00:00 2001 From: Stephan Preeker Date: Sun, 9 Aug 2009 14:56:48 +0200 Subject: [PATCH 09/10] generated documentation now works for all handlers docs are ordered. --- piston/doc.py | 13 +++++++++++-- piston/handler.py | 4 ++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/piston/doc.py b/piston/doc.py index 441702f..12cde00 100644 --- a/piston/doc.py +++ b/piston/doc.py @@ -1,6 +1,7 @@ import inspect, handler from piston.handler import typemapper +from piston.handler import handler_tracker from django.core.urlresolvers import get_resolver, get_callable, get_script_prefix from django.shortcuts import render_to_response @@ -159,8 +160,16 @@ def documentation_view(request): """ docs = [ ] - for handler, (model, anonymous) in typemapper.iteritems(): + for handler in handler_tracker: docs.append(generate_doc(handler)) - + + def _compare(doc1, doc2): + #handlers and their anonymous counterparts are put next to each other. + name1 = doc1.name.replace("Anonymous", "") + name2 = doc2.name.replace("Anonymous", "") + return cmp(name1, name2) + + docs.sort(_compare) + return render_to_response('documentation.html', { 'docs': docs }, RequestContext(request)) diff --git a/piston/handler.py b/piston/handler.py index 19acbee..0b82649 100644 --- a/piston/handler.py +++ b/piston/handler.py @@ -2,6 +2,7 @@ from utils import rc from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned typemapper = { } +handler_tracker = [ ] class HandlerMetaClass(type): """ @@ -14,6 +15,9 @@ class HandlerMetaClass(type): if hasattr(new_cls, 'model'): typemapper[new_cls] = (new_cls.model, new_cls.is_anonymous) + if name not in ('BaseHandler', 'AnonymousBaseHandler'): + handler_tracker.append(new_cls) + return new_cls class BaseHandler(object): From ddd7b99da7b56bd66dd128c823dd279687a06d12 Mon Sep 17 00:00:00 2001 From: Jesper Noehr Date: Mon, 17 Aug 2009 16:30:54 +0300 Subject: [PATCH 10/10] adding http_name to HandlerMethod and doing a few cosmetic changes w/ decorators on properties --- piston/doc.py | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/piston/doc.py b/piston/doc.py index 441702f..82a8fff 100644 --- a/piston/doc.py +++ b/piston/doc.py @@ -36,7 +36,8 @@ class HandlerMethod(object): else: yield (arg, None) - def get_signature(self, parse_optional=True): + @property + def signature(self, parse_optional=True): spec = "" for argn, argdef in self.iter_args(): @@ -53,18 +54,25 @@ class HandlerMethod(object): return spec.replace("=None", "=") return spec - - signature = property(get_signature) - def get_doc(self): + @property + def doc(self): return inspect.getdoc(self.method) - doc = property(get_doc) - - def get_name(self): + @property + def name(self): return self.method.__name__ - - name = property(get_name) + + @property + def http_name(self): + if self.name == 'read': + return 'GET' + elif self.name == 'create': + return 'POST' + elif self.name == 'delete': + return 'DELETE' + elif self.name == 'update': + return 'PUT' def __repr__(self): return "" % self.name @@ -97,11 +105,19 @@ class HandlerDocumentation(object): def get_model(self): return getattr(self, 'model', None) - def get_doc(self): + @property + def has_anonymous(self): + return self.handler.anonymous + + @property + def anonymous(self): + if self.has_anonymous: + return HandlerDocumentation(self.handler.anonymous) + + @property + def doc(self): return self.handler.__doc__ - doc = property(get_doc) - @property def name(self): return self.handler.__name__