diff --git a/AUTHORS.txt b/AUTHORS.txt index 1fddf95..49c0533 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -16,3 +16,5 @@ 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 +Stephan Preeker for providing some fixes to documentation generation diff --git a/piston/doc.py b/piston/doc.py index 441702f..16d2f0e 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 @@ -36,7 +37,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 +55,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 +106,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__ @@ -159,8 +176,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/emitters.py b/piston/emitters.py index e82670d..24f6f77 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 @@ -68,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 @@ -171,7 +172,7 @@ class Emitter(object): get_fields = set(fields) 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 ]]): if not f.rel: @@ -191,7 +192,6 @@ class Emitter(object): # try to get the remainder of fields for maybe_field in get_fields: - if isinstance(maybe_field, (list, tuple)): model, fields = maybe_field inst = getattr(data, model, None) 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) diff --git a/piston/handler.py b/piston/handler.py index 5d7b702..bec1d77 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): 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() diff --git a/tests/test_project/apps/testapp/handlers.py b/tests/test_project/apps/testapp/handlers.py index b2f65b0..248948a 100644 --- a/tests/test_project/apps/testapp/handlers.py +++ b/tests/test_project/apps/testapp/handlers.py @@ -3,11 +3,10 @@ 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, ListFieldsModel +from models import TestModel, ExpressiveTestModel, Comment, InheritedModel, PlainOldObject, Issue58Model, ListFieldsModel from forms import EchoForm from test_project.apps.testapp import signals - class EntryHandler(BaseHandler): model = TestModel allowed_methods = ['GET', 'PUT', 'POST'] @@ -82,3 +81,17 @@ class ListFieldsHandler(BaseHandler): fields = ('id','kind','variety','color') list_fields = ('id','variety') +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'], model=data['model']) + 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 b6631a1..7a0b9d0 100644 --- a/tests/test_project/apps/testapp/models.py +++ b/tests/test_project/apps/testapp/models.py @@ -30,8 +30,11 @@ class PlainOldObject(object): return {'type': 'plain', 'field': 'a field'} - class ListFieldsModel(models.Model): kind = models.CharField(max_length=15) variety = models.CharField(max_length=15) color = models.CharField(max_length=15) + +class Issue58Model(models.Model): + read = models.BooleanField(default=False) + 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 4a71c0d..30e62a6 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, ListFieldsModel +from test_project.apps.testapp.models import TestModel, ExpressiveTestModel, Comment, InheritedModel, Issue58Model, ListFieldsModel from test_project.apps.testapp import signals class MainTests(TestCase): @@ -370,8 +370,6 @@ 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 ListFieldsTest(MainTests): def init_delegate(self): @@ -410,3 +408,39 @@ class ListFieldsTest(MainTests): self.assertEquals(resp.status_code, 200) self.assertEquals(resp.content, expect) +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,model='t') + m1.save() + m2 = Issue58Model(read=False,model='f') + m2.save() + + def test_incoming_json(self): + outgoing = simplejson.dumps({ 'read': True, 'model': 'T'}) + + expected = """[ + { + "read": true, + "model": "t" + }, + { + "read": false, + "model": "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) + \ No newline at end of file diff --git a/tests/test_project/apps/testapp/urls.py b/tests/test_project/apps/testapp/urls.py index 9464275..5beafbd 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, ListFieldsHandler +from test_project.apps.testapp.handlers import EntryHandler, ExpressiveHandler, AbstractHandler, EchoHandler, PlainOldObjectHandler, Issue58Handler, ListFieldsHandler auth = HttpBasicAuthentication(realm='TestApplication') @@ -12,7 +12,7 @@ abstract = Resource(handler=AbstractHandler, authentication=auth) echo = Resource(handler=EchoHandler) popo = Resource(handler=PlainOldObjectHandler) list_fields = Resource(handler=ListFieldsHandler) - +issue58 = Resource(handler=Issue58Handler) urlpatterns = patterns('', url(r'^entries/$', entries), @@ -20,6 +20,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),