diff --git a/src/plone/app/angularjs/api/api.pt b/src/plone/app/angularjs/api/api.pt
index 4160044..384328f 100644
--- a/src/plone/app/angularjs/api/api.pt
+++ b/src/plone/app/angularjs/api/api.pt
@@ -1,14 +1,29 @@
-
-
REST API
-
Root Methods
-
-
RESTish Methods
- soon...
-
+
+
+
+ REST API
+
+
+
+ Above you can see all IRestApi providing views.
+
+
+
+
+
+
diff --git a/src/plone/app/angularjs/api/api.py b/src/plone/app/angularjs/api/api.py
index 622eff4..25bd81d 100644
--- a/src/plone/app/angularjs/api/api.py
+++ b/src/plone/app/angularjs/api/api.py
@@ -1,120 +1,83 @@
# -*- coding: utf-8 -*-
-from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
-from Products.Five.browser import BrowserView
-from zope.component.hooks import getSite
-from Products.CMFCore.utils import getToolByName
-from zope.interface import implements
-from plone.app.angularjs.interfaces import IRestApi
-
import json
+from Products.Five.browser import BrowserView
+from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
+from plone.app.angularjs.interfaces import IRestApi
+from zope.component import (
+ getMultiAdapter, getGlobalSiteManager)
+from zope.component.interfaces import ComponentLookupError
+from zope.component.hooks import getSite
-class ApiOverview(BrowserView):
+import logging
+log = logging.getLogger("plone.app.angularjs.api")
- template = ViewPageTemplateFile('api.pt')
- def __call__(self):
- return self.template()
-
- def api_methods(self):
- portal_url = getSite().absolute_url()
- return [
- {
- 'id': x,
- 'description': IRestApi.get(x).getDoc(),
- 'url': '%s/++api++v1/%s' % (portal_url, x),
- } for x in IRestApi.names()
- ]
+def json_api_call(func):
+ """ decorator to return all values as json
+ """
+ def decorator(*args, **kwargs):
+ instance = args[0]
+ request = getattr(instance, 'request', None)
+ request.response.setHeader(
+ 'Content-Type',
+ 'application/json; charset=utf-8'
+ )
+ result = func(*args, **kwargs)
+ return json.dumps(result, indent=2, sort_keys=True)
+ return decorator
-class RestApi(object):
- implements(IRestApi)
- def traversal(self, request):
- portal = getSite()
- path = request.get('path')
- if not path:
- return
- path = '/'.join(portal.getPhysicalPath()) + '/' + path
+class ApiDispatcherView(BrowserView):
+ """ dispatch api calls to api views via lookup on view name and IRestApi
+ """
+ def __call__(self, context=None, request=None):
+ api_params = self.request.get('api_params')
+ if api_params:
+ name = api_params[0]
+ else:
+ name = ''
try:
- obj = portal.restrictedTraverse(path)
- except KeyError:
- return json.dumps({'title': 'Object not found.'})
- try:
- text = obj.getText()
- except AttributeError:
- text = ''
- request.response.setHeader("Content-Type", "application/json")
+ view = getMultiAdapter(
+ (self.context, self.request),
+ IRestApi,
+ name=name)
+ return view()
+ except ComponentLookupError:
+ log.debug("No API View found with name '%s'" % name)
return json.dumps({
- 'route': path,
- 'id': obj.id,
- 'title': obj.title,
- 'description': obj.Description(),
- 'text': text
+ 'code': '404',
+ 'message': "API method '%s' not found." % name,
+ 'data': ''
})
- def top_navigation(self, request):
- portal = getSite()
- catalog = getToolByName(portal, 'portal_catalog')
- portal_path = '/'.join(portal.getPhysicalPath())
- return json.dumps(
- [
- {
- 'id': brain.id,
- 'title': brain.Title,
- 'description': brain.description,
- 'url': brain.getPath().replace(
- portal_path, ''
- ).lstrip('/')
- }
- for brain in catalog({
- 'path': {
- 'query': '/'.join(portal.getPhysicalPath()),
- 'depth': 1
- },
- 'portal_type': 'Folder',
- 'sort_on': 'getObjPositionInParent'
- }) if brain.exclude_from_nav is not True
- ]
- )
- def navigation_tree(self, request):
- portal = getSite()
- catalog = getToolByName(portal, 'portal_catalog')
- portal_path = '/'.join(portal.getPhysicalPath())
+class ApiOverview(BrowserView):
+ """ Api overview, list all api endpoints.
+ """
+ template = ViewPageTemplateFile('api.pt')
+
+ def __call__(self):
+ return self.template()
- def _get_children(context):
- return [
- {
- 'id': brain.id,
- 'title': brain.Title,
- 'description': brain.description,
- 'url': brain.getPath().replace(
- portal_path, ''
- ).lstrip('/'),
- 'children': []
- } for brain in catalog({
- 'path': {'query': context.getPath(), 'depth': 1},
- 'sort_on': 'getObjPositionInParent',
- }
- ) if brain.exclude_from_nav is not True
- ]
- return json.dumps(
- [
- {
- 'id': brain.id,
- 'title': brain.Title,
- 'description': brain.description,
- 'url': brain.getPath().replace(
- portal_path, ''
- ).lstrip('/'),
- 'children': _get_children(brain)
- }
- for brain in catalog(
- {
- 'path': {'query': portal_path, 'depth': 1},
- 'sort_on': 'getObjPositionInParent',
- }
- ) if brain.exclude_from_nav is not True
- ]
- )
+ def api_views(self):
+ portal_url = getSite().absolute_url()
+ gsm = getGlobalSiteManager()
+ api_views = []
+ for api_adapter in gsm.registeredAdapters():
+ if not api_adapter.provided == IRestApi:
+ continue
+ api_view = {}
+ api_view['id'] = api_adapter.name
+ view = getMultiAdapter(
+ (self.context, self.request),
+ IRestApi,
+ name=api_adapter.name)
+ # XXX this doesn't work because we don't have the original
+ # class here. How we can get the original class?
+ api_view['description'] = view.__doc__
+ api_view['url'] = '%s/++api++v1/%s' % (
+ portal_url, api_adapter.name)
+ api_views.append(api_view)
+ return api_views
diff --git a/src/plone/app/angularjs/api/configure.zcml b/src/plone/app/angularjs/api/configure.zcml
index 7bdad8b..1db07cd 100644
--- a/src/plone/app/angularjs/api/configure.zcml
+++ b/src/plone/app/angularjs/api/configure.zcml
@@ -1,12 +1,9 @@
-
-
+
+
+
+
+
+
diff --git a/src/plone/app/angularjs/api/navigation.py b/src/plone/app/angularjs/api/navigation.py
new file mode 100644
index 0000000..31f3981
--- /dev/null
+++ b/src/plone/app/angularjs/api/navigation.py
@@ -0,0 +1,75 @@
+from Products.CMFCore.utils import getToolByName
+from Products.Five.browser import BrowserView
+from zope.component.hooks import getSite
+
+from .api import json_api_call
+
+
+class TopNavigation(BrowserView):
+
+ @json_api_call
+ def __call__(self):
+ portal = getSite()
+ catalog = getToolByName(portal, 'portal_catalog')
+ portal_path = '/'.join(portal.getPhysicalPath())
+ return [
+ {
+ 'id': brain.id,
+ 'title': brain.Title,
+ 'description': brain.description,
+ 'url': brain.getPath().replace(
+ portal_path, ''
+ ).lstrip('/')
+ }
+ for brain in catalog({
+ 'path': {
+ 'query': '/'.join(portal.getPhysicalPath()),
+ 'depth': 1
+ },
+ 'portal_type': 'Folder',
+ 'sort_on': 'getObjPositionInParent'
+ }) if brain.exclude_from_nav is not True
+ ]
+
+
+class NavigationTree(BrowserView):
+
+ @json_api_call
+ def __call__(self):
+ portal = getSite()
+ catalog = getToolByName(portal, 'portal_catalog')
+ portal_path = '/'.join(portal.getPhysicalPath())
+
+ def _get_children(context):
+ return [
+ {
+ 'id': brain.id,
+ 'title': brain.Title,
+ 'description': brain.description,
+ 'url': brain.getPath().replace(
+ portal_path, ''
+ ).lstrip('/'),
+ 'children': []
+ } for brain in catalog({
+ 'path': {'query': context.getPath(), 'depth': 1},
+ 'sort_on': 'getObjPositionInParent',
+ }
+ ) if brain.exclude_from_nav is not True
+ ]
+ return [
+ {
+ 'id': brain.id,
+ 'title': brain.Title,
+ 'description': brain.description,
+ 'url': brain.getPath().replace(
+ portal_path, ''
+ ).lstrip('/'),
+ 'children': _get_children(brain)
+ }
+ for brain in catalog(
+ {
+ 'path': {'query': portal_path, 'depth': 1},
+ 'sort_on': 'getObjPositionInParent',
+ }
+ ) if brain.exclude_from_nav is not True
+ ]
diff --git a/src/plone/app/angularjs/api/traversal.py b/src/plone/app/angularjs/api/traversal.py
new file mode 100644
index 0000000..1b2f8d6
--- /dev/null
+++ b/src/plone/app/angularjs/api/traversal.py
@@ -0,0 +1,31 @@
+from Products.Five.browser import BrowserView
+from zope.component.hooks import getSite
+
+from .api import json_api_call
+
+
+class TraversalView(BrowserView):
+
+ @json_api_call
+ def __call__(self):
+ portal = getSite()
+ path = self.request.get('path')
+ if not path:
+ return
+ path = '/'.join(portal.getPhysicalPath()) + '/' + path
+ try:
+ obj = portal.restrictedTraverse(path)
+ except KeyError:
+ return {'title': 'Object not found.'}
+ try:
+ text = obj.getText()
+ except AttributeError:
+ text = ''
+ self.request.response.setHeader("Content-Type", "application/json")
+ return {
+ 'route': path,
+ 'id': obj.id,
+ 'title': obj.title,
+ 'description': obj.Description(),
+ 'text': text
+ }
diff --git a/src/plone/app/angularjs/traversal.py b/src/plone/app/angularjs/traversal.py
index 2ad2c2c..2d93b25 100644
--- a/src/plone/app/angularjs/traversal.py
+++ b/src/plone/app/angularjs/traversal.py
@@ -1,5 +1,5 @@
-from zope.component import getUtility
-from plone.app.angularjs.interfaces import IRestApi
+# from zope.component import getUtility
+# from plone.app.angularjs.interfaces import IRestApi
from Products.CMFPlone.interfaces.siteroot import IPloneSiteRoot
from ZPublisher.BaseRequest import DefaultPublishTraverse
from zope.component import adapts
@@ -7,8 +7,7 @@
from zope.publisher.interfaces.browser import IBrowserRequest
from plone.app.angularjs.app.index import AngularAppRootView
from plone.app.angularjs.api.traverser import IAPIRequest
-from plone.app.angularjs.api.api import ApiOverview
-import json
+from plone.app.angularjs.api.api import ApiDispatcherView, ApiOverview
class AngularAppPortalRootTraverser(DefaultPublishTraverse):
@@ -16,17 +15,20 @@ class AngularAppPortalRootTraverser(DefaultPublishTraverse):
def publishTraverse(self, request, name):
if IAPIRequest.providedBy(request):
- if name == '' or name == 'folder_listing' or name == 'front-page':
+ if name in ['', 'folder_listing', 'front-page']:
return ApiOverview(self.context, self.request)
- api = getUtility(IRestApi)
- if getattr(api, name, None):
- return getattr(api, name)(request)
else:
- return json.dumps({
- 'code': '404',
- 'message': "API method '%s' not found." % name,
- 'data': ''
- })
+ parameters = []
+ while self.hasMoreNames():
+ if not name.startswith('@@'):
+ parameters.append(name)
+ name = self.nextName()
+
+ if not name.startswith('@@'):
+ # don't add the last param if it starts with '@@'
+ parameters.append(name)
+ request.set('api_params', parameters)
+ return ApiDispatcherView(self.context, request)
is_front_page = request.URL.endswith('front-page')
no_front_page = \
request.URL.endswith('folder_listing') or \
@@ -42,6 +44,17 @@ def publishTraverse(self, request, name):
)
+ def nextName(self):
+ """ Pop the next name off of the traversal stack.
+ """
+ return self.request['TraversalRequestNameStack'].pop()
+
+ def hasMoreNames(self):
+ """ Are there names left for traversal?
+ """
+ return len(self.request['TraversalRequestNameStack']) > 0
+
+
class AngularAppRedirectorTraverser(DefaultPublishTraverse):
adapts(IDexterityItem, IBrowserRequest)
# XXX: Adapting IContentish works only for Archetypes content objects: