You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by br...@apache.org on 2013/07/31 23:12:52 UTC
[2/3] git commit: [#3154] ticket:391 ForgeBlog REST API
[#3154] ticket:391 ForgeBlog REST API
Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/af5c8e68
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/af5c8e68
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/af5c8e68
Branch: refs/heads/master
Commit: af5c8e683f96007b64108a0e89846c949e96ad3e
Parents: 0065ba1
Author: Yuriy Arhipov <yu...@yandex.ru>
Authored: Wed Jul 10 14:57:48 2013 +0400
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Wed Jul 31 21:00:24 2013 +0000
----------------------------------------------------------------------
ForgeBlog/forgeblog/main.py | 87 +++++++++-
ForgeBlog/forgeblog/model/blog.py | 9 +
.../forgeblog/tests/functional/test_rest.py | 169 +++++++++++++++++++
3 files changed, 264 insertions(+), 1 deletion(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/af5c8e68/ForgeBlog/forgeblog/main.py
----------------------------------------------------------------------
diff --git a/ForgeBlog/forgeblog/main.py b/ForgeBlog/forgeblog/main.py
index 5f54a38..0f4e7e6 100644
--- a/ForgeBlog/forgeblog/main.py
+++ b/ForgeBlog/forgeblog/main.py
@@ -30,6 +30,7 @@ from paste.deploy.converters import asbool
import formencode
from formencode import validators
from webob import exc
+from urllib import unquote
from ming.orm import session
@@ -45,7 +46,7 @@ from allura.lib.widgets.subscriptions import SubscribeForm
from allura.lib.widgets import form_fields as ffw
from allura.lib.widgets.search import SearchResults, SearchHelp
from allura import model as M
-from allura.controllers import BaseController, AppDiscussionController
+from allura.controllers import BaseController, AppDiscussionController, AppDiscussionRestController
from allura.controllers.feed import FeedArgs, FeedController
# Local imports
@@ -101,6 +102,7 @@ class ForgeBlogApp(Application):
Application.__init__(self, project, config)
self.root = RootController()
self.admin = BlogAdminController(self)
+ self.api_root = RootRestController()
@Property
def external_feeds_list():
@@ -439,3 +441,86 @@ class BlogAdminController(DefaultAdminController):
flash('Invalid link(s): %s' % ','.join(link for link in invalid_list), 'error')
redirect(c.project.url()+'admin/tools')
+
+
+class RootRestController(BaseController):
+ def __init__(self):
+ self._discuss = AppDiscussionRestController()
+
+ def _check_security(self):
+ require_access(c.app, 'read')
+
+ @expose('json:')
+ def index(self, title='', text='', state='draft', labels='', **kw):
+ if request.method == 'POST':
+ require_access(c.app, 'write')
+ post = BM.BlogPost()
+ post.title = title
+ post.state = state
+ post.text = text
+ post.labels = labels.split(',')
+ post.neighborhood_id = c.project.neighborhood_id
+ post.make_slug()
+ M.Thread.new(discussion_id=post.app_config.discussion_id,
+ ref_id=post.index_id(),
+ subject='%s discussion' % post.title)
+
+ post.viewable_by = ['all']
+ post.commit()
+ return post.__json__()
+ else:
+ post_titles = []
+ query_filter = dict(app_config_id=c.app.config._id, deleted=False)
+ if not has_access(c.app, 'write')():
+ query_filter['state'] = 'published'
+ posts = BM.BlogPost.query.find(query_filter)
+ for post in posts:
+ if has_access(post, 'read')():
+ post_titles.append({'title': post.title, 'url': h.absurl('/rest' + post.url())})
+ return dict(posts=post_titles)
+
+ @expose()
+ def _lookup(self, year=None, month=None, title=None, *rest):
+ if not (year and month and title):
+ raise exc.HTTPNotFound()
+ slug = '/'.join((year, month, urllib2.unquote(title).decode('utf-8')))
+ post = BM.BlogPost.query.get(slug=slug, app_config_id=c.app.config._id)
+ if not post:
+ raise exc.HTTPNotFound()
+ return PostRestController(post), rest
+
+
+class PostRestController(BaseController):
+
+ def __init__(self, post):
+ self.post = post
+
+ def _check_security(self):
+ if self.post:
+ require_access(self.post, 'read')
+
+ @h.vardec
+ @expose('json:')
+ def index(self, **kw):
+ if request.method == 'POST':
+ return self._update_post(**kw)
+ else:
+ if self.post.state == 'draft':
+ require_access(self.post, 'write')
+ return self.post.__json__()
+
+ def _update_post(self, **post_data):
+ require_access(self.post, 'write')
+ if 'delete' in post_data:
+ self.post.delete()
+ return {}
+ if 'title' in post_data:
+ self.post.title = post_data['title']
+ if 'text' in post_data:
+ self.post.text = post_data['text']
+ if 'state' in post_data:
+ self.post.state = post_data['state']
+ if 'labels' in post_data:
+ self.post.labels = post_data['labels'].split(',')
+ self.post.commit()
+ return self.post.__json__()
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/af5c8e68/ForgeBlog/forgeblog/model/blog.py
----------------------------------------------------------------------
diff --git a/ForgeBlog/forgeblog/model/blog.py b/ForgeBlog/forgeblog/model/blog.py
index b43cb8e..07983a0 100644
--- a/ForgeBlog/forgeblog/model/blog.py
+++ b/ForgeBlog/forgeblog/model/blog.py
@@ -256,6 +256,15 @@ class BlogPost(M.VersionedArtifact, ActivityObject):
M.Notification.post(
artifact=self, topic='metadata', text=description, subject=subject)
+ def __json__(self):
+ return dict(super(BlogPost, self).__json__(),
+ title=self.title,
+ url=h.absurl('/rest' + self.url()),
+ text=self.text,
+ labels=self.labels,
+ state=self.state)
+
+
class Attachment(M.BaseAttachment):
ArtifactClass=BlogPost
class __mongometa__:
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/af5c8e68/ForgeBlog/forgeblog/tests/functional/test_rest.py
----------------------------------------------------------------------
diff --git a/ForgeBlog/forgeblog/tests/functional/test_rest.py b/ForgeBlog/forgeblog/tests/functional/test_rest.py
new file mode 100644
index 0000000..5addcde
--- /dev/null
+++ b/ForgeBlog/forgeblog/tests/functional/test_rest.py
@@ -0,0 +1,169 @@
+# coding: utf-8
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+
+from nose.tools import assert_equal
+from allura.lib import helpers as h
+from allura.tests import decorators as td
+from allura import model as M
+from alluratest.controller import TestRestApiBase
+from forgeblog import model as BM
+
+
+class TestBlogApi(TestRestApiBase):
+
+ def setUp(self):
+ super(TestBlogApi, self).setUp()
+ self.setup_with_tools()
+
+ @td.with_tool('test', 'Blog', 'blog')
+ def setup_with_tools(self):
+ h.set_context('test', 'blog', neighborhood='Projects')
+
+ def test_create_post(self):
+ data = {
+ 'title': 'test',
+ 'text': 'test text',
+ 'state': 'published',
+ 'labels': 'label1, label2'
+ }
+ r = self.api_post('/rest/p/test/blog/', **data)
+ assert_equal(r.status_int, 200)
+ url = '/rest' + BM.BlogPost.query.find().first().url()
+ r = self.api_get('/rest/p/test/blog/')
+ assert_equal(r.json['posts'][0]['title'], 'test')
+ assert_equal(r.json['posts'][0]['url'], h.absurl(url))
+
+ r = self.api_get(url)
+ assert_equal(r.json['title'], 'test')
+ assert_equal(r.json['text'], data['text'])
+ assert_equal(r.json['state'], data['state'])
+ assert_equal(r.json['labels'], data['labels'].split(','))
+
+ def test_update_post(self):
+ data = {
+ 'title': 'test',
+ 'text': 'test text',
+ 'state': 'published',
+ 'labels': 'label1, label2'
+ }
+ r = self.api_post('/rest/p/test/blog/', **data)
+ assert_equal(r.status_int, 200)
+ url = '/rest' + BM.BlogPost.query.find().first().url()
+ data = {
+ 'text': 'test text2',
+ 'state': 'draft',
+ 'labels': 'label3'
+ }
+ self.api_post(url, **data)
+ r = self.api_get(url)
+ assert_equal(r.json['title'], 'test')
+ assert_equal(r.json['text'], data['text'])
+ assert_equal(r.json['state'], data['state'])
+ assert_equal(r.json['labels'], data['labels'].split(','))
+
+ def test_delete_post(self):
+ data = {
+ 'title': 'test',
+ 'state': 'published',
+ 'labels': 'label1, label2'
+ }
+ r = self.api_post('/rest/p/test/blog/', **data)
+ assert_equal(r.status_int, 200)
+ url = '/rest' + BM.BlogPost.query.find().first().url()
+ self.api_post(url, delete='')
+ r = self.api_get(url)
+ assert_equal(r.status_int, 404)
+
+ def test_post_does_not_exist(self):
+ r = self.api_get('/rest/p/test/blog/2013/07/fake/')
+ assert_equal(r.status_int, 404)
+
+ def test_read_permissons(self):
+ self.api_post('/rest/p/test/blog/', title='test', text='test text', state='published')
+ self.app.get('/rest/p/test/blog/', extra_environ={'username': '*anonymous'}, status=200)
+ p = M.Project.query.get(shortname='test')
+ acl = p.app_instance('blog').config.acl
+ anon = M.ProjectRole.by_name('*anonymous')._id
+ anon_read = M.ACE.allow(anon, 'read')
+ acl.remove(anon_read)
+ self.app.get('/rest/p/test/blog/',
+ extra_environ={'username': '*anonymous'},
+ status=401)
+
+ def test_new_post_permissons(self):
+ self.app.post('/rest/p/test/blog/',
+ params=dict(title='test', text='test text', state='published'),
+ extra_environ={'username': '*anonymous'},
+ status=401)
+ p = M.Project.query.get(shortname='test')
+ acl = p.app_instance('blog').config.acl
+ anon = M.ProjectRole.by_name('*anonymous')._id
+ anon_write = M.ACE.allow(anon, 'write')
+ acl.append(anon_write)
+ self.app.post('/rest/p/test/blog/',
+ params=dict(title='test', text='test text', state='published'),
+ extra_environ={'username': '*anonymous'},
+ status=200)
+
+ def test_update_post_permissons(self):
+ self.api_post('/rest/p/test/blog/', title='test', text='test text', state='published')
+ url = '/rest' + BM.BlogPost.query.find().first().url()
+ self.app.post(url.encode('utf-8'),
+ params=dict(title='test2', text='test text2', state='published'),
+ extra_environ={'username': '*anonymous'},
+ status=401)
+ p = M.Project.query.get(shortname='test')
+ acl = p.app_instance('blog').config.acl
+ anon = M.ProjectRole.by_name('*anonymous')._id
+ anon_write = M.ACE.allow(anon, 'write')
+ acl.append(anon_write)
+ self.app.post(url.encode('utf-8'),
+ params=dict(title='test2', text='test text2', state='published'),
+ extra_environ={'username': '*anonymous'},
+ status=200)
+ r = self.api_get(url)
+ assert_equal(r.json['title'], 'test2')
+ assert_equal(r.json['text'], 'test text2')
+ assert_equal(r.json['state'], 'published')
+
+ def test_permission_draft_post(self):
+ self.api_post('/rest/p/test/blog/', title='test', text='test text', state='draft')
+ r = self.app.get('/rest/p/test/blog/', extra_environ={'username': '*anonymous'})
+ assert_equal(r.json, {'posts': []})
+ url = '/rest' + BM.BlogPost.query.find().first().url()
+ self.app.post(url.encode('utf-8'),
+ params=dict(title='test2', text='test text2', state='published'),
+ extra_environ={'username': '*anonymous'},
+ status=401)
+ p = M.Project.query.get(shortname='test')
+ acl = p.app_instance('blog').config.acl
+ anon = M.ProjectRole.by_name('*anonymous')._id
+ anon_write = M.ACE.allow(anon, 'write')
+ acl.append(anon_write)
+ r = self.app.get('/rest/p/test/blog/', extra_environ={'username': '*anonymous'})
+ assert_equal(r.json['posts'][0]['title'], 'test')
+
+ def test_draft_post(self):
+ self.api_post('/rest/p/test/blog/', title='test', text='test text', state='draft')
+ r = self.app.get('/rest/p/test/blog/', extra_environ={'username': '*anonymous'})
+ assert_equal(r.json, {'posts': []})
+ url = '/rest' + BM.BlogPost.query.find().first().url()
+ self.api_post(url, state='published')
+ r = self.app.get('/rest/p/test/blog/', extra_environ={'username': '*anonymous'})
+ assert_equal(r.json['posts'][0]['title'], 'test')