Richard Jones 64f7bad172 Alter verb usage for keystone REST and add Angular service
This patch adds an Angular service to isolate client-side code from REST API
changes, and also changes the REST API to use PATCH instead of PUT as the
update verb. Except the one new PUT method for granting a role.

Multiple DELETE is removed from the overloaded POST actions, and the role
grant is moved to a PUT action.

The new angular API service implements error handling by default.

The request Python API JSON body test was made more sane. Thanks to all the
reviewers who brought that up, I finally saw the light!

Change-Id: Ifd58fbf4fac3c3f53dc4769fd76cddfa3101d9d2
2015-02-04 04:59:05 +00:00

133 lines
4.6 KiB
Python

# Copyright 2014, Rackspace, US, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import functools
import json
import logging
from django.conf import settings
from django import http
from django.utils import decorators
from oslo_serialization import jsonutils
from horizon import exceptions
log = logging.getLogger(__name__)
class AjaxError(Exception):
def __init__(self, http_status, msg):
self.http_status = http_status
super(AjaxError, self).__init__(msg)
http_errors = exceptions.UNAUTHORIZED + exceptions.NOT_FOUND + \
exceptions.RECOVERABLE + (AjaxError, )
class CreatedResponse(http.HttpResponse):
def __init__(self, location, data=None):
if data is not None:
content = jsonutils.dumps(data, sort_keys=settings.DEBUG)
content_type = 'application/json'
else:
content = ''
content_type = None
super(CreatedResponse, self).__init__(status=201, content=content,
content_type=content_type)
self['Location'] = location
class JSONResponse(http.HttpResponse):
def __init__(self, data, status=200):
if status == 204:
content = ''
else:
content = jsonutils.dumps(data, sort_keys=settings.DEBUG)
super(JSONResponse, self).__init__(
status=status,
content=content,
content_type='application/json',
)
def ajax(authenticated=True, data_required=False):
'''Provide a decorator to wrap a view method so that it may exist in an
entirely AJAX environment:
- data decoded from JSON as input and data coded as JSON as output
- result status is coded in the HTTP status code; any non-2xx response
data will be coded as a JSON string, otherwise the response type (always
JSON) is specific to the method called.
if authenticated is true then we'll make sure the current user is
authenticated.
If data_required is true then we'll assert that there is a JSON body
present.
The wrapped view method should return either:
- JSON serialisable data
- an object of the django http.HttpResponse subclass (one of JSONResponse
or CreatedResponse is suggested)
- nothing
Methods returning nothing (or None explicitly) will result in a 204 "NO
CONTENT" being returned to the caller.
'''
def decorator(function, authenticated=authenticated,
data_required=data_required):
@functools.wraps(function,
assigned=decorators.available_attrs(function))
def _wrapped(self, request, *args, **kw):
if authenticated and not request.user.is_authenticated():
return JSONResponse('not logged in', 401)
if not request.is_ajax():
return JSONResponse('request must be AJAX', 400)
# decode the JSON body if present
request.DATA = None
if request.body:
try:
request.DATA = json.loads(request.body)
except (TypeError, ValueError) as e:
return JSONResponse('malformed JSON request: %s' % e, 400)
if data_required:
if not request.DATA:
return JSONResponse('request requires JSON body', 400)
# invoke the wrapped function, handling exceptions sanely
try:
data = function(self, request, *args, **kw)
if isinstance(data, http.HttpResponse):
return data
elif data is None:
return JSONResponse('', status=204)
return JSONResponse(data)
except http_errors as e:
# exception was raised with a specific HTTP status
if hasattr(e, 'http_status'):
http_status = e.http_status
else:
http_status = e.code
return JSONResponse(str(e), http_status)
except Exception as e:
log.exception('error invoking apiclient')
return JSONResponse(str(e), 500)
return _wrapped
return decorator