resolving merge between ephelon's fork and the rest, all tests pass
This commit is contained in:
Коммит
15202e9b15
|
@ -16,3 +16,5 @@ Brian McMurray for contributing a patch for #41
|
||||||
James Emerton for making the OAuth parts more usable/friendly
|
James Emerton for making the OAuth parts more usable/friendly
|
||||||
Anton Tsigularov for providing a patch for incorrect multipart detection
|
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
|
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
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import inspect, handler
|
import inspect, handler
|
||||||
|
|
||||||
from piston.handler import typemapper
|
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.core.urlresolvers import get_resolver, get_callable, get_script_prefix
|
||||||
from django.shortcuts import render_to_response
|
from django.shortcuts import render_to_response
|
||||||
|
@ -36,7 +37,8 @@ class HandlerMethod(object):
|
||||||
else:
|
else:
|
||||||
yield (arg, None)
|
yield (arg, None)
|
||||||
|
|
||||||
def get_signature(self, parse_optional=True):
|
@property
|
||||||
|
def signature(self, parse_optional=True):
|
||||||
spec = ""
|
spec = ""
|
||||||
|
|
||||||
for argn, argdef in self.iter_args():
|
for argn, argdef in self.iter_args():
|
||||||
|
@ -53,18 +55,25 @@ class HandlerMethod(object):
|
||||||
return spec.replace("=None", "=<optional>")
|
return spec.replace("=None", "=<optional>")
|
||||||
|
|
||||||
return spec
|
return spec
|
||||||
|
|
||||||
signature = property(get_signature)
|
|
||||||
|
|
||||||
def get_doc(self):
|
@property
|
||||||
|
def doc(self):
|
||||||
return inspect.getdoc(self.method)
|
return inspect.getdoc(self.method)
|
||||||
|
|
||||||
doc = property(get_doc)
|
@property
|
||||||
|
def name(self):
|
||||||
def get_name(self):
|
|
||||||
return self.method.__name__
|
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):
|
def __repr__(self):
|
||||||
return "<Method: %s>" % self.name
|
return "<Method: %s>" % self.name
|
||||||
|
@ -97,11 +106,19 @@ class HandlerDocumentation(object):
|
||||||
def get_model(self):
|
def get_model(self):
|
||||||
return getattr(self, 'model', None)
|
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__
|
return self.handler.__doc__
|
||||||
|
|
||||||
doc = property(get_doc)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
return self.handler.__name__
|
return self.handler.__name__
|
||||||
|
@ -159,8 +176,16 @@ def documentation_view(request):
|
||||||
"""
|
"""
|
||||||
docs = [ ]
|
docs = [ ]
|
||||||
|
|
||||||
for handler, (model, anonymous) in typemapper.iteritems():
|
for handler in handler_tracker:
|
||||||
docs.append(generate_doc(handler))
|
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',
|
return render_to_response('documentation.html',
|
||||||
{ 'docs': docs }, RequestContext(request))
|
{ 'docs': docs }, RequestContext(request))
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from __future__ import generators
|
from __future__ import generators
|
||||||
|
|
||||||
import decimal, re, inspect
|
import decimal, re, inspect
|
||||||
|
import copy
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# yaml isn't standard with python. It shouldn't be required if it
|
# yaml isn't standard with python. It shouldn't be required if it
|
||||||
|
@ -68,7 +69,7 @@ class Emitter(object):
|
||||||
ret = dict()
|
ret = dict()
|
||||||
|
|
||||||
for field in fields:
|
for field in fields:
|
||||||
if field in has:
|
if field in has and callable(field):
|
||||||
ret[field] = getattr(data, field)
|
ret[field] = getattr(data, field)
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
@ -171,7 +172,7 @@ class Emitter(object):
|
||||||
get_fields = set(fields)
|
get_fields = set(fields)
|
||||||
|
|
||||||
met_fields = self.method_fields(handler, get_fields)
|
met_fields = self.method_fields(handler, get_fields)
|
||||||
|
|
||||||
for f in data._meta.local_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 f.serialize and not any([ p in met_fields for p in [ f.attname, f.name ]]):
|
||||||
if not f.rel:
|
if not f.rel:
|
||||||
|
@ -191,7 +192,6 @@ class Emitter(object):
|
||||||
|
|
||||||
# try to get the remainder of fields
|
# try to get the remainder of fields
|
||||||
for maybe_field in get_fields:
|
for maybe_field in get_fields:
|
||||||
|
|
||||||
if isinstance(maybe_field, (list, tuple)):
|
if isinstance(maybe_field, (list, tuple)):
|
||||||
model, fields = maybe_field
|
model, fields = maybe_field
|
||||||
inst = getattr(data, model, None)
|
inst = getattr(data, model, None)
|
||||||
|
|
|
@ -23,7 +23,7 @@ class ModelForm(forms.ModelForm):
|
||||||
|
|
||||||
class OAuthAuthenticationForm(forms.Form):
|
class OAuthAuthenticationForm(forms.Form):
|
||||||
oauth_token = forms.CharField(widget=forms.HiddenInput)
|
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)
|
authorize_access = forms.BooleanField(required=True)
|
||||||
csrf_signature = forms.CharField(widget=forms.HiddenInput)
|
csrf_signature = forms.CharField(widget=forms.HiddenInput)
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ from utils import rc
|
||||||
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
|
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
|
||||||
|
|
||||||
typemapper = { }
|
typemapper = { }
|
||||||
|
handler_tracker = [ ]
|
||||||
|
|
||||||
class HandlerMetaClass(type):
|
class HandlerMetaClass(type):
|
||||||
"""
|
"""
|
||||||
|
@ -14,6 +15,9 @@ class HandlerMetaClass(type):
|
||||||
if hasattr(new_cls, 'model'):
|
if hasattr(new_cls, 'model'):
|
||||||
typemapper[new_cls] = (new_cls.model, new_cls.is_anonymous)
|
typemapper[new_cls] = (new_cls.model, new_cls.is_anonymous)
|
||||||
|
|
||||||
|
if name not in ('BaseHandler', 'AnonymousBaseHandler'):
|
||||||
|
handler_tracker.append(new_cls)
|
||||||
|
|
||||||
return new_cls
|
return new_cls
|
||||||
|
|
||||||
class BaseHandler(object):
|
class BaseHandler(object):
|
||||||
|
|
|
@ -247,10 +247,11 @@ class OAuthRequest(object):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _split_header(header):
|
def _split_header(header):
|
||||||
params = {}
|
params = {}
|
||||||
|
header = header.replace('OAuth ', '', 1)
|
||||||
parts = header.split(',')
|
parts = header.split(',')
|
||||||
for param in parts:
|
for param in parts:
|
||||||
# ignore realm parameter
|
# ignore realm parameter
|
||||||
if param.find('OAuth realm') > -1:
|
if param.find('realm') > -1:
|
||||||
continue
|
continue
|
||||||
# remove whitespace
|
# remove whitespace
|
||||||
param = param.strip()
|
param = param.strip()
|
||||||
|
|
|
@ -3,11 +3,10 @@ from django.core.paginator import Paginator
|
||||||
from piston.handler import BaseHandler
|
from piston.handler import BaseHandler
|
||||||
from piston.utils import rc, validate
|
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 forms import EchoForm
|
||||||
from test_project.apps.testapp import signals
|
from test_project.apps.testapp import signals
|
||||||
|
|
||||||
|
|
||||||
class EntryHandler(BaseHandler):
|
class EntryHandler(BaseHandler):
|
||||||
model = TestModel
|
model = TestModel
|
||||||
allowed_methods = ['GET', 'PUT', 'POST']
|
allowed_methods = ['GET', 'PUT', 'POST']
|
||||||
|
@ -82,3 +81,17 @@ class ListFieldsHandler(BaseHandler):
|
||||||
fields = ('id','kind','variety','color')
|
fields = ('id','kind','variety','color')
|
||||||
list_fields = ('id','variety')
|
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)
|
||||||
|
|
|
@ -30,8 +30,11 @@ class PlainOldObject(object):
|
||||||
return {'type': 'plain',
|
return {'type': 'plain',
|
||||||
'field': 'a field'}
|
'field': 'a field'}
|
||||||
|
|
||||||
|
|
||||||
class ListFieldsModel(models.Model):
|
class ListFieldsModel(models.Model):
|
||||||
kind = models.CharField(max_length=15)
|
kind = models.CharField(max_length=15)
|
||||||
variety = models.CharField(max_length=15)
|
variety = models.CharField(max_length=15)
|
||||||
color = 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)
|
||||||
|
|
|
@ -15,7 +15,7 @@ except ImportError:
|
||||||
|
|
||||||
import urllib, base64
|
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
|
from test_project.apps.testapp import signals
|
||||||
|
|
||||||
class MainTests(TestCase):
|
class MainTests(TestCase):
|
||||||
|
@ -370,8 +370,6 @@ class PlainOldObject(MainTests):
|
||||||
resp = self.client.get('/api/popo')
|
resp = self.client.get('/api/popo')
|
||||||
self.assertEquals(resp.status_code, 200)
|
self.assertEquals(resp.status_code, 200)
|
||||||
self.assertEquals({'type': 'plain', 'field': 'a field'}, simplejson.loads(resp.content))
|
self.assertEquals({'type': 'plain', 'field': 'a field'}, simplejson.loads(resp.content))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ListFieldsTest(MainTests):
|
class ListFieldsTest(MainTests):
|
||||||
def init_delegate(self):
|
def init_delegate(self):
|
||||||
|
@ -410,3 +408,39 @@ class ListFieldsTest(MainTests):
|
||||||
self.assertEquals(resp.status_code, 200)
|
self.assertEquals(resp.status_code, 200)
|
||||||
self.assertEquals(resp.content, expect)
|
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)
|
||||||
|
|
|
@ -2,7 +2,7 @@ from django.conf.urls.defaults import *
|
||||||
from piston.resource import Resource
|
from piston.resource import Resource
|
||||||
from piston.authentication import HttpBasicAuthentication
|
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')
|
auth = HttpBasicAuthentication(realm='TestApplication')
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ abstract = Resource(handler=AbstractHandler, authentication=auth)
|
||||||
echo = Resource(handler=EchoHandler)
|
echo = Resource(handler=EchoHandler)
|
||||||
popo = Resource(handler=PlainOldObjectHandler)
|
popo = Resource(handler=PlainOldObjectHandler)
|
||||||
list_fields = Resource(handler=ListFieldsHandler)
|
list_fields = Resource(handler=ListFieldsHandler)
|
||||||
|
issue58 = Resource(handler=Issue58Handler)
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
url(r'^entries/$', entries),
|
url(r'^entries/$', entries),
|
||||||
|
@ -20,6 +20,8 @@ urlpatterns = patterns('',
|
||||||
url(r'^entries\.(?P<emitter_format>.+)', entries),
|
url(r'^entries\.(?P<emitter_format>.+)', entries),
|
||||||
url(r'^entry-(?P<pk>.+)\.(?P<emitter_format>.+)', entries),
|
url(r'^entry-(?P<pk>.+)\.(?P<emitter_format>.+)', entries),
|
||||||
|
|
||||||
|
url(r'^issue58\.(?P<emitter_format>.+)$', issue58),
|
||||||
|
|
||||||
url(r'^expressive\.(?P<emitter_format>.+)$', expressive),
|
url(r'^expressive\.(?P<emitter_format>.+)$', expressive),
|
||||||
|
|
||||||
url(r'^abstract\.(?P<emitter_format>.+)$', abstract),
|
url(r'^abstract\.(?P<emitter_format>.+)$', abstract),
|
||||||
|
|
Загрузка…
Ссылка в новой задаче