diff --git a/piston/authentication.py b/piston/authentication.py index ac85fb0..d6b5953 100644 --- a/piston/authentication.py +++ b/piston/authentication.py @@ -1,13 +1,15 @@ import binascii + from django.conf import settings from django.contrib.auth import authenticate from django.contrib.auth.decorators import login_required from django.contrib.auth.models import AnonymousUser, User from django.core.exceptions import ImproperlyConfigured -from django.core.urlresolvers import get_callable from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import render_to_response -from django.template import loader, RequestContext +from django.template import RequestContext, loader +from django.urls import get_callable + from piston import forms from . import oauth @@ -39,12 +41,12 @@ class HttpBasicAuthentication(object): some kind of challenge headers and 401 code on it. """ - def __init__(self, auth_func=authenticate, realm='API'): + def __init__(self, auth_func=authenticate, realm="API"): self.auth_func = auth_func self.realm = realm def is_authenticated(self, request): - auth_string = request.META.get('HTTP_AUTHORIZATION', None) + auth_string = request.META.get("HTTP_AUTHORIZATION", None) if not auth_string: return False @@ -52,26 +54,28 @@ def is_authenticated(self, request): try: (authmeth, auth) = auth_string.split(" ", 1) - if not authmeth.lower() == 'basic': + if not authmeth.lower() == "basic": return False - auth = auth.strip().decode('base64') - (username, password) = auth.split(':', 1) + auth = auth.strip().decode("base64") + (username, password) = auth.split(":", 1) except (ValueError, binascii.Error): return False - request.user = self.auth_func(username=username, password=password) or AnonymousUser() + request.user = ( + self.auth_func(username=username, password=password) or AnonymousUser() + ) return not request.user in (False, None, AnonymousUser()) def challenge(self, request=None): resp = HttpResponse("Authorization Required") - resp['WWW-Authenticate'] = 'Basic realm="%s"' % self.realm + resp["WWW-Authenticate"] = 'Basic realm="%s"' % self.realm resp.status_code = 401 return resp def __repr__(self): - return '' % self.realm + return "" % self.realm class HttpBasicSimple(HttpBasicAuthentication): @@ -87,22 +91,26 @@ def hash(self, username, password): def load_data_store(): - '''Load data store for OAuth Consumers, Tokens, Nonces and Resources''' - path = getattr(settings, 'OAUTH_DATA_STORE', 'piston.store.DataStore') + """Load data store for OAuth Consumers, Tokens, Nonces and Resources""" + path = getattr(settings, "OAUTH_DATA_STORE", "piston.store.DataStore") # stolen from django.contrib.auth.load_backend - i = path.rfind('.') + i = path.rfind(".") module, attr = path[:i], path[i + 1 :] try: mod = __import__(module, {}, {}, attr) except ImportError as e: - raise ImproperlyConfigured('Error importing OAuth data store %s: "%s"' % (module, e)) + raise ImproperlyConfigured( + 'Error importing OAuth data store %s: "%s"' % (module, e) + ) try: cls = getattr(mod, attr) except AttributeError: - raise ImproperlyConfigured('Module %s does not define a "%s" OAuth data store' % (module, attr)) + raise ImproperlyConfigured( + 'Module %s does not define a "%s" OAuth data store' % (module, attr) + ) return cls @@ -116,21 +124,21 @@ def initialize_server_request(request): Shortcut for initialization. """ # c.f. http://www.mail-archive.com/oauth@googlegroups.com/msg01556.html - if request.method == 'POST' and request.FILES == {}: + if request.method == "POST" and request.FILES == {}: params = dict(list(request.REQUEST.items())) else: params = {} # Seems that we want to put HTTP_AUTHORIZATION into 'Authorization' # for oauth.py to understand. Lovely. - request.META['Authorization'] = request.META.get('HTTP_AUTHORIZATION', '') + request.META["Authorization"] = request.META.get("HTTP_AUTHORIZATION", "") oauth_request = oauth.OAuthRequest.from_request( request.method, request.build_absolute_uri(), headers=request.META, parameters=params, - query_string=request.environ.get('QUERY_STRING', ''), + query_string=request.environ.get("QUERY_STRING", ""), ) if oauth_request: @@ -147,10 +155,10 @@ def send_oauth_error(err=None): """ Shortcut for sending an error. """ - response = HttpResponse(err.message.encode('utf-8')) + response = HttpResponse(err.message.encode("utf-8")) response.status_code = 401 - realm = 'OAuth' + realm = "OAuth" header = oauth.build_authenticate_header(realm=realm) for k, v in list(header.items()): @@ -177,13 +185,15 @@ def oauth_request_token(request): def oauth_auth_view(request, token, callback, params): form = forms.OAuthAuthenticationForm( initial={ - 'oauth_token': token.key, - 'oauth_callback': token.get_callback_url() or callback, + "oauth_token": token.key, + "oauth_callback": token.get_callback_url() or callback, } ) context = dict(form=form, token=token) - return render_to_response('piston/authorize_token.html', context, RequestContext(request)) + return render_to_response( + "piston/authorize_token.html", context, RequestContext(request) + ) @login_required @@ -206,7 +216,7 @@ def oauth_user_auth(request): if request.method == "GET": params = oauth_request.get_normalized_parameters() - oauth_view = getattr(settings, 'OAUTH_AUTH_VIEW', None) + oauth_view = getattr(settings, "OAUTH_AUTH_VIEW", None) if oauth_view is None: return oauth_auth_view(request, token, callback, params) else: @@ -216,12 +226,12 @@ def oauth_user_auth(request): form = forms.OAuthAuthenticationForm(request.POST) if form.is_valid(): token = oauth_server.authorize_token(token, request.user) - args = '?' + token.to_string(only_key=True) + args = "?" + token.to_string(only_key=True) else: - args = '?error=%s' % 'Access not granted by user.' + args = "?error=%s" % "Access not granted by user." if not callback: - callback = getattr(settings, 'OAUTH_CALLBACK_VIEW') + callback = getattr(settings, "OAUTH_CALLBACK_VIEW") return get_callable(callback)(request, token) response = HttpResponseRedirect(callback + args) @@ -229,7 +239,7 @@ def oauth_user_auth(request): except oauth.OAuthError as err: response = send_oauth_error(err) else: - response = HttpResponse('Action not allowed.') + response = HttpResponse("Action not allowed.") return response @@ -247,7 +257,9 @@ def oauth_access_token(request): return send_oauth_error(err) -INVALID_PARAMS_RESPONSE = send_oauth_error(oauth.OAuthError('Invalid request parameters.')) +INVALID_PARAMS_RESPONSE = send_oauth_error( + oauth.OAuthError("Invalid request parameters.") +) class OAuthAuthentication(object): @@ -255,7 +267,7 @@ class OAuthAuthentication(object): OAuth authentication. Based on work by Leah Culver. """ - def __init__(self, realm='API'): + def __init__(self, realm="API"): self.realm = realm self.builder = oauth.build_authenticate_header @@ -295,11 +307,13 @@ def challenge(self, request=None): """ response = HttpResponse() response.status_code = 401 - realm = 'API' + realm = "API" for k, v in list(self.builder(realm=realm).items()): response[k] = v - tmpl = loader.render_to_string('oauth/challenge.html', {'MEDIA_URL': settings.MEDIA_URL}) + tmpl = loader.render_to_string( + "oauth/challenge.html", {"MEDIA_URL": settings.MEDIA_URL} + ) response.content = tmpl return response @@ -313,8 +327,15 @@ def is_valid_request(request): OAuth spec, but otherwise fall back to `GET` and `POST`. """ must_have = [ - 'oauth_' + s - for s in ['consumer_key', 'token', 'signature', 'signature_method', 'timestamp', 'nonce'] + "oauth_" + s + for s in [ + "consumer_key", + "token", + "signature", + "signature_method", + "timestamp", + "nonce", + ] ] is_in = lambda l: all([(p in l) for p in must_have]) diff --git a/piston/doc.py b/piston/doc.py index 1d59279..8c3ed78 100644 --- a/piston/doc.py +++ b/piston/doc.py @@ -1,7 +1,9 @@ import inspect -from django.core.urlresolvers import get_callable, get_resolver, get_script_prefix + from django.shortcuts import render_to_response from django.template import RequestContext +from django.urls import get_callable, get_resolver, get_script_prefix + from piston.handler import handler_tracker, typemapper from . import handler @@ -28,7 +30,7 @@ def iter_args(self): args, _, _, defaults = inspect.getargspec(self.method) for idx, arg in enumerate(args): - if arg in ('self', 'request', 'form'): + if arg in ("self", "request", "form"): continue didx = len(args) - idx @@ -46,9 +48,9 @@ def signature(self, parse_optional=True): spec += argn if argdef: - spec += '=%s' % argdef + spec += "=%s" % argdef - spec += ', ' + spec += ", " spec = spec.rstrip(", ") @@ -67,14 +69,14 @@ def name(self): @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' + 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 @@ -91,13 +93,19 @@ def get_methods(self, include_default=False): if not met: continue - stale = inspect.getmodule(met.im_func) is not inspect.getmodule(self.handler) + stale = inspect.getmodule(met.im_func) is not inspect.getmodule( + self.handler + ) if not self.handler.is_anonymous: if met and (not stale or include_default): yield HandlerMethod(met, stale) else: - if not stale or met.__name__ == "read" and 'GET' in self.allowed_methods: + if ( + not stale + or met.__name__ == "read" + and "GET" in self.allowed_methods + ): yield HandlerMethod(met, stale) def get_all_methods(self): @@ -108,7 +116,7 @@ def is_anonymous(self): return self.handler.is_anonymous def get_model(self): - return getattr(self, 'model', None) + return getattr(self, "model", None) @property def has_anonymous(self): @@ -141,7 +149,7 @@ def get_resource_uri_template(self): def _convert(template, params=[]): """URI template converter""" paths = template % dict([p, "{%s}" % p] for p in params) - return u'%s%s' % (get_script_prefix(), paths) + return "%s%s" % (get_script_prefix(), paths) try: resource_uri = self.handler.resource_uri() @@ -172,7 +180,7 @@ def _convert(template, params=[]): resource_uri_template = property(get_resource_uri_template) def __repr__(self): - return u'' % self.name + return '' % self.name def documentation_view(request): @@ -193,4 +201,6 @@ def _compare(doc1, doc2): docs.sort(_compare) - return render_to_response('documentation.html', {'docs': docs}, RequestContext(request)) + return render_to_response( + "documentation.html", {"docs": docs}, RequestContext(request) + ) diff --git a/piston/emitters.py b/piston/emitters.py index 855d340..274d402 100644 --- a/piston/emitters.py +++ b/piston/emitters.py @@ -1,5 +1,3 @@ - - import copy import decimal import inspect @@ -27,10 +25,10 @@ def any(iterable): from django.core import serializers from django.core.serializers.json import DjangoJSONEncoder -from django.core.urlresolvers import NoReverseMatch, reverse -from django.db.models import Model, permalink +from django.db.models import Model from django.db.models.query import QuerySet from django.http import HttpResponse +from django.urls import NoReverseMatch, reverse from django.utils.encoding import smart_str from django.utils.xmlutils import SimplerXMLGenerator @@ -47,9 +45,6 @@ def any(iterable): except ImportError: import pickle -# Allow people to change the reverser (default `permalink`). -reverser = permalink - class Emitter(object): """ @@ -66,7 +61,17 @@ class Emitter(object): EMITTERS = {} RESERVED_FIELDS = set( - ['read', 'update', 'create', 'delete', 'model', 'anonymous', 'allowed_methods', 'fields', 'exclude'] + [ + "read", + "update", + "create", + "delete", + "model", + "anonymous", + "allowed_methods", + "fields", + "exclude", + ] ) def __init__(self, payload, typemapper, handler, fields=(), anonymous=True): @@ -123,11 +128,13 @@ def _any(thing, fields=()): elif inspect.isfunction(thing): if not inspect.getargspec(thing)[0]: ret = _any(thing()) - elif hasattr(thing, '__emittable__'): + elif hasattr(thing, "__emittable__"): f = thing.__emittable__ if inspect.ismethod(f) and len(inspect.getargspec(f)[0]) == 1: ret = _any(f()) - elif repr(thing).startswith("= max_requests and expiration > now: t = rc.THROTTLED wait = int(expiration - now) - t.content = 'Throttled, wait %d seconds.' % wait - t['Retry-After'] = wait + t.content = "Throttled, wait %d seconds." % wait + t["Retry-After"] = wait return t cache.set(ident, (count + 1, expiration), (expiration - now)) @@ -188,7 +196,7 @@ def coerce_put_post(request): # the first time _load_post_and_files is called (both by wsgi.py and # modpython.py). If it's set, the request has to be 'reset' to redo # the query value parsing in POST mode. - if hasattr(request, '_post'): + if hasattr(request, "_post"): del request._post del request._files @@ -197,9 +205,9 @@ def coerce_put_post(request): request._load_post_and_files() request.method = "PUT" except AttributeError: - request.META['REQUEST_METHOD'] = 'POST' + request.META["REQUEST_METHOD"] = "POST" request._load_post_and_files() - request.META['REQUEST_METHOD'] = 'PUT' + request.META["REQUEST_METHOD"] = "PUT" request.PUT = request.POST @@ -222,7 +230,7 @@ def is_multipart(self): content_type = self.content_type() if content_type is not None: - return content_type.lstrip().startswith('multipart') + return content_type.lstrip().startswith("multipart") return False @@ -243,7 +251,7 @@ def content_type(self): """ type_formencoded = "application/x-www-form-urlencoded" - ctype = self.request.META.get('CONTENT_TYPE', type_formencoded) + ctype = self.request.META.get("CONTENT_TYPE", type_formencoded) if type_formencoded in ctype: return None @@ -272,12 +280,12 @@ def translate(self): if loadee: try: incoming_data = self.request.body - if 'application/json' in ctype and type(incoming_data) is bytes: + if "application/json" in ctype and type(incoming_data) is bytes: # Covers 'application/json' and 'application/json; charset=utf-8' equally # If by some cursed miracle, we're dealing with a bytestring, # then decode it back to a normal string, because json.loads # will *not* handle binary data - incoming_data = incoming_data.decode('utf-8') + incoming_data = incoming_data.decode("utf-8") self.request.data = loadee(incoming_data) @@ -318,10 +326,10 @@ def wrap(f, self, request, *args, **kwargs): realmimes = set() rewrite = { - 'json': 'application/json', - 'yaml': 'application/x-yaml', - 'xml': 'text/xml', - 'pickle': 'application/python-pickle', + "json": "application/json", + "yaml": "application/x-yaml", + "xml": "text/xml", + "pickle": "application/python-pickle", } for idx, mime in enumerate(mimes): @@ -335,7 +343,7 @@ def wrap(f, self, request, *args, **kwargs): return wrap -require_extended = require_mime('json', 'yaml', 'xml', 'pickle') +require_extended = require_mime("json", "yaml", "xml", "pickle") def send_consumer_mail(consumer): @@ -350,7 +358,9 @@ def send_consumer_mail(consumer): template = "piston/mails/consumer_%s.txt" % consumer.status try: - body = loader.render_to_string(template, {'consumer': consumer, 'user': consumer.user}) + body = loader.render_to_string( + template, {"consumer": consumer, "user": consumer.user} + ) except TemplateDoesNotExist: """ They haven't set up the templates, which means they might not want @@ -366,7 +376,7 @@ def send_consumer_mail(consumer): if consumer.user: send_mail(_(subject), body, sender, [consumer.user.email], fail_silently=True) - if consumer.status == 'pending' and len(settings.ADMINS): + if consumer.status == "pending" and len(settings.ADMINS): mail_admins(_(subject), body, fail_silently=True) if settings.DEBUG and consumer.user: diff --git a/tests/test_project/apps/testapp/models.py b/tests/test_project/apps/testapp/models.py index 7a0b9d0..fb7d1f0 100644 --- a/tests/test_project/apps/testapp/models.py +++ b/tests/test_project/apps/testapp/models.py @@ -1,40 +1,49 @@ from django.db import models + class TestModel(models.Model): test1 = models.CharField(max_length=1, blank=True, null=True) test2 = models.CharField(max_length=1, blank=True, null=True) - + + class ExpressiveTestModel(models.Model): title = models.CharField(max_length=255) content = models.TextField() never_shown = models.TextField() - + + class Comment(models.Model): - parent = models.ForeignKey(ExpressiveTestModel, related_name='comments') + parent = models.ForeignKey( + ExpressiveTestModel, related_name="comments", on_delete=models.CASCADE + ) content = models.TextField() + class AbstractModel(models.Model): - some_field = models.CharField(max_length=32, default='something here') - + some_field = models.CharField(max_length=32, default="something here") + class Meta: abstract = True - + + class InheritedModel(AbstractModel): - some_other = models.CharField(max_length=32, default='something else') - + some_other = models.CharField(max_length=32, default="something else") + class Meta: - db_table = 'testing_abstracts' + db_table = "testing_abstracts" + class PlainOldObject(object): def __emittable__(self): - return {'type': 'plain', - 'field': 'a field'} + 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)