Source code for djangorpc.router

from __future__ import unicode_literals

import json
from inspect import getargspec

from django.conf.urls import patterns, url
from django.core.urlresolvers import reverse
from django.http import HttpResponse

from djangorpc.datastructures import RpcMultiValueDict
from djangorpc.exceptions import RpcExceptionEvent
from djangorpc.settings import JS_API_URL_NAME, ROUTER_URL_NAME
from djangorpc.responses import RpcHttpResponse


[docs]class RpcRouter(object): """ Router class for RPC. """ def __init__(self, actions={}, url_namespace=None, enable_buffer=True): """ Router class for RPC. :param url_namespace: URL pattern namespace where router insance is included. Used to get URL which router is connected. :param actions: Action classes router should add to RPC API :param enable_buffer: Define client should send requests in a batch """ self.url_namespace = url_namespace self.actions = actions self.enable_buffer = enable_buffer def get_urls(self): return ( url(r'^jsapi/$', self.api, name=JS_API_URL_NAME), url(r'^router/$', self.dispatch, name=ROUTER_URL_NAME) ) @property def urls(self): if not hasattr(self, '_urls'): self._urls = patterns('', *self.get_urls()) return self._urls def dispatch(self, request, *args, **kwargs): if request.POST.get('rpcAction'): requests = { 'action': request.POST.get('rpcAction'), 'method': request.POST.get('rpcMethod'), 'data': [dict(request.POST)], 'tid': request.POST.get('rpcTID'), 'upload': request.POST.get('rpcUpload') == 'true' } if requests['upload']: requests['files'] = request.FILES output = json.dumps(self.call_action(requests, request, *args, **kwargs)) return HttpResponse('<textarea>%s</textarea>' % output) else: try: requests = json.loads(request.body) except (ValueError, KeyError, IndexError): # TODO: add loagging and return error event requests = [] if not isinstance(requests, list): requests = [requests] response = HttpResponse('', content_type="application/json") output = [] for rd in requests: mr = self.call_action(rd, request, *args, **kwargs) # FIXME: This looks like a little ugly if 'result' in mr and isinstance(mr['result'], RpcHttpResponse): for key, val in mr['result'].cookies.items(): response.set_cookie( key, val.value, val['max-age'] or None, val['expires'], val['path'], val['domain'], val['secure']) mr['result'] = dict(mr['result']) output.append(mr) response.content = json.dumps(output) return response
[docs] def action_extra_kwargs(self, action, request, *args, **kwargs): """ Check maybe this action get some extra arguments from request """ if hasattr(action, '_extra_kwargs'): return action._extra_kwargs(request, *args, **kwargs) return {}
[docs] def method_extra_kwargs(self, method, request, *args, **kwargs): """ Check maybe this method get some extra arguments from request """ if hasattr(method, '_extra_kwargs'): return method._extra_kwargs(request, *args, **kwargs) return {}
[docs] def extra_kwargs(self, request, *args, **kwargs): """ For all method in ALL actions we add request.user to arguments. You can add something else, request for example. For adding extra arguments for one action use action_extra_kwargs. """ return { 'user': request.user }
[docs] def api(self, request, *args, **kwargs): """ This method is view that send js for provider initialization. Just set this in template after RPC scripts including: <script src="{% url api_url_name %}"></script> """ obj = json.dumps(self, cls=RpcRouterJSONEncoder, url_args=args, url_kwargs=kwargs) return HttpResponse( 'jQuery.Rpc.addProvider(%s)' % obj, content_type='application/x-javascript')
def execute_func(self, func, *args, **kwargs): if hasattr(func, '_pre_execute'): result = func._pre_execute(func, *args, **kwargs) if result is not None: return result return func(*args, **kwargs)
[docs] def call_action(self, rd, request, *args, **kwargs): """ This method checks parameters of RPC request and call method of action. It checks arguments number, method existing, handle RpcExceptionEvent and send exception event for RPC. """ method = rd['method'] if not rd['action'] in self.actions: return { 'tid': rd['tid'], 'type': 'exception', 'action': rd['action'], 'method': method, 'message': 'Undefined action' } action = self.actions[rd['action']] if not hasattr(action, method): return { 'tid': rd['tid'], 'type': 'exception', 'action': rd['action'], 'method': method, 'message': 'Undefined method' } func = getattr(action, method) args = [] for val in (rd.get('data') or []): if isinstance(val, dict) and not isinstance(val, RpcMultiValueDict): val = RpcMultiValueDict(val) args.append(val) if 'files' in rd: args.append(rd.get('files')) extra_kwargs = self.extra_kwargs(request, *args, **kwargs) extra_kwargs.update(self.action_extra_kwargs(action, request, *args, **kwargs)) extra_kwargs.update(self.method_extra_kwargs(func, request, *args, **kwargs)) func_args, varargs, varkw, func_defaults = getargspec(func) func_args.remove('self') # TODO: or cls for classmethod for name in extra_kwargs.keys(): if name in func_args: func_args.remove(name) required_args_count = len(func_args) - len(func_defaults or []) if (required_args_count - len(args)) > 0 or (not varargs and len(args) > len(func_args)): return { 'tid': rd['tid'], 'type': 'exception', 'action': rd['action'], 'method': method, 'message': 'Incorrect arguments number' } try: return { 'tid': rd['tid'], 'type': 'rpc', 'action': rd['action'], 'method': method, 'result': self.execute_func(func, *args, **extra_kwargs) } except RpcExceptionEvent, e: return { 'tid': rd['tid'], 'type': 'exception', 'action': rd['action'], 'method': method, 'message': unicode(e) }
[docs]class RpcRouterJSONEncoder(json.JSONEncoder): """ JSON Encoder for RpcRouter """ def __init__(self, url_args, url_kwargs, *args, **kwargs): self.url_args = url_args self.url_kwargs = url_kwargs super(RpcRouterJSONEncoder, self).__init__(*args, **kwargs) def _encode_action(self, o): output = [] for method in dir(o): if not method.startswith('_'): data = dict(name=method) f = getattr(o, method) if hasattr(f, '_form_handler'): data['formHandler'] = getattr(f, '_form_handler') output.append(data) return output def default(self, obj): if isinstance(obj, RpcRouter): if obj.url_namespace: url_name = '%s:%s' % (obj.url_namespace, ROUTER_URL_NAME) else: url_name = ROUTER_URL_NAME output = { 'url': reverse(url_name, args=self.url_args, kwargs=self.url_kwargs), 'enableBuffer': obj.enable_buffer, 'actions': {} } for name, action in obj.actions.items(): output['actions'][name] = self._encode_action(action) return output else: return super(RpcRouterJSONEncoder, self).default(obj)