From 5c2e5e6a3a9709479ad732f96f5a97acda0ef956 Mon Sep 17 00:00:00 2001 From: j <0x006A@0x2620.org> Date: Wed, 4 Jan 2012 01:30:33 +0530 Subject: [PATCH] add ox.django.api --- README | 2 + ox/django/api/__init__.py | 1 + ox/django/api/actions.py | 124 ++++++++++++++++++++++++++++++++++++++ ox/django/api/urls.py | 13 ++++ ox/django/api/views.py | 38 ++++++++++++ 5 files changed, 178 insertions(+) create mode 100644 ox/django/api/__init__.py create mode 100644 ox/django/api/actions.py create mode 100644 ox/django/api/urls.py create mode 100644 ox/django/api/views.py diff --git a/README b/README index eb1af86..3749823 100644 --- a/README +++ b/README @@ -24,6 +24,8 @@ Usage: info['year'] 1999 +For information on ox.django see https://wiki.0x2620.org/wiki/ox.django + Install: python setup.py install diff --git a/ox/django/api/__init__.py b/ox/django/api/__init__.py new file mode 100644 index 0000000..cf410f8 --- /dev/null +++ b/ox/django/api/__init__.py @@ -0,0 +1 @@ +from actions import actions diff --git a/ox/django/api/actions.py b/ox/django/api/actions.py new file mode 100644 index 0000000..085b7fe --- /dev/null +++ b/ox/django/api/actions.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- +# vi:si:et:sw=4:sts=4:ts=4 +from __future__ import division, with_statement +import inspect +import sys + +from django.conf import settings + +from ..shortcuts import render_to_json_response, json_response +from ...utils import json + +def autodiscover(): + #register api actions from all installed apps + from django.utils.importlib import import_module + from django.utils.module_loading import module_has_submodule + for app in settings.INSTALLED_APPS: + if app != 'api': + mod = import_module(app) + try: + import_module('%s.views'%app) + except: + if module_has_submodule(mod, 'views'): + raise + +def trim(docstring): + if not docstring: + return '' + # Convert tabs to spaces (following the normal Python rules) + # and split into a list of lines: + lines = docstring.expandtabs().splitlines() + # Determine minimum indentation (first line doesn't count): + indent = sys.maxint + for line in lines[1:]: + stripped = line.lstrip() + if stripped: + indent = min(indent, len(line) - len(stripped)) + # Remove indentation (first line is special): + trimmed = [lines[0].strip()] + if indent < sys.maxint: + for line in lines[1:]: + trimmed.append(line[indent:].rstrip()) + # Strip off trailing and leading blank lines: + while trimmed and not trimmed[-1]: + trimmed.pop() + while trimmed and not trimmed[0]: + trimmed.pop(0) + # Return a single string: + return '\n'.join(trimmed) + + +class ApiActions(dict): + properties = {} + def __init__(self): + + def api(request): + ''' + returns list of all known api actions + param data { + docs: bool + } + if docs is true, action properties contain docstrings + return { + status: {'code': int, 'text': string}, + data: { + actions: { + 'api': { + cache: true, + doc: 'recursion' + }, + 'hello': { + cache: true, + .. + } + ... + } + } + } + ''' + data = json.loads(request.POST.get('data', '{}')) + docs = data.get('docs', False) + code = data.get('code', False) + _actions = self.keys() + _actions.sort() + actions = {} + for a in _actions: + actions[a] = self.properties[a] + if docs: + actions[a]['doc'] = self.doc(a) + if code: + actions[a]['code'] = self.code(a) + response = json_response({'actions': actions}) + return render_to_json_response(response) + self.register(api) + + def doc(self, f): + return trim(self[f].__doc__) + + def code(self, name): + f = self[name] + if name != 'api' and hasattr(f, 'func_closure') and f.func_closure: + f = f.func_closure[0].cell_contents + info = f.func_code.co_filename[len(settings.PROJECT_ROOT)+1:] + info = u'%s:%s' % (info, f.func_code.co_firstlineno) + return info, trim(inspect.getsource(f)) + + def register(self, method, action=None, cache=True): + if not action: + action = method.func_name + self[action] = method + self.properties[action] = {'cache': cache} + + def unregister(self, action): + if action in self: + del self[action] + +actions = ApiActions() + +def error(request): + ''' + this action is used to test api error codes, it should return a 503 error + ''' + success = error_is_success + return render_to_json_response({}) +actions.register(error) diff --git a/ox/django/api/urls.py b/ox/django/api/urls.py new file mode 100644 index 0000000..33b8552 --- /dev/null +++ b/ox/django/api/urls.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# vi:si:et:sw=4:sts=4:ts=4 + +from django.conf.urls.defaults import * + +import views + +import actions +actions.autodiscover() + +urlpatterns = patterns("", + (r'^$', views.api), +) diff --git a/ox/django/api/views.py b/ox/django/api/views.py new file mode 100644 index 0000000..edfe1dd --- /dev/null +++ b/ox/django/api/views.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# vi:si:et:sw=4:sts=4:ts=4 +from __future__ import division, with_statement + +from django.shortcuts import render_to_response +from django.template import RequestContext +from django.conf import settings + +from ..shortcuts import render_to_json_response, json_response + +from actions import actions + +def api(request): + if request.META['REQUEST_METHOD'] == "OPTIONS": + response = render_to_json_response({'status': {'code': 200, + 'text': 'use POST'}}) + response['Access-Control-Allow-Origin'] = '*' + return response + if not 'action' in request.POST: + methods = actions.keys() + api = [] + for f in sorted(methods): + api.append({'name': f, + 'doc': actions.doc(f).replace('\n', '
\n')}) + context = RequestContext(request, {'api': api, + 'sitename': settings.SITENAME}) + return render_to_response('api.html', context) + function = request.POST['action'] + + f = actions.get(function) + if f: + response = f(request) + else: + response = render_to_json_response(json_response(status=400, + text='Unknown function %s' % function)) + response['Access-Control-Allow-Origin'] = '*' + return response +