Merge branch 'dev' into t48_blog_pull_in_rss
Conflicts:
Allura/allura/command/__init__.py
Allura/allura/tests/test_commands.py
Allura/setup.py
diff --git a/Allura/allura/command/__init__.py b/Allura/allura/command/__init__.py
index 88ddebc..0f7d0a7 100644
--- a/Allura/allura/command/__init__.py
+++ b/Allura/allura/command/__init__.py
@@ -4,4 +4,5 @@
from smtp_server import SMTPServerCommand
from create_neighborhood import CreateNeighborhoodCommand
from create_trove_categories import CreateTroveCategoriesCommand
+from rssfeeds import RssFeedsCommand
from set_neighborhood_features import SetNeighborhoodFeaturesCommand
diff --git a/Allura/allura/command/rssfeeds.py b/Allura/allura/command/rssfeeds.py
new file mode 100644
index 0000000..335ede9
--- /dev/null
+++ b/Allura/allura/command/rssfeeds.py
@@ -0,0 +1,84 @@
+import feedparser
+import html2text
+from bson import ObjectId
+
+import base
+
+from pylons import c
+
+from allura import model as M
+from forgeblog import model as BM
+from forgeblog import version
+from forgeblog.main import ForgeBlogApp
+from allura.lib import exceptions
+
+
+class RssFeedsCommand(base.Command):
+ summary = 'Rss feed client'
+ parser = base.Command.standard_parser(verbose=True)
+ parser.add_option('-a', '--appid', dest='appid', default='',
+ help='application id')
+ parser.add_option('-u', '--username', dest='username', default='root',
+ help='poster username')
+
+ def command(self):
+ self.basic_setup()
+
+ user = M.User.query.get(username=self.options.username)
+ c.user = user
+
+ self.prepare_feeds()
+ for appid in self.feed_dict:
+ for feed_url in self.feed_dict[appid]:
+ self.process_feed(appid, feed_url)
+
+ def prepare_feeds(self):
+ feed_dict = {}
+ if self.options.appid != '':
+ gl_app = BM.Globals.query.get(app_config_id=ObjectId(self.options.appid))
+ if not gl_app:
+ raise exceptions.NoSuchGlobalsError("The globals %s " \
+ "could not be found in the database" % self.options.appid)
+ if len(gl_app.external_feeds) > 0:
+ feed_dict[gl_app.app_config_id] = gl_app.external_feeds
+ else:
+ for gl_app in BM.Globals.query.find().all():
+ if len(gl_app.external_feeds) > 0:
+ feed_dict[gl_app.app_config_id] = gl_app.external_feeds
+ self.feed_dict = feed_dict
+
+ def process_feed(self, appid, feed_url):
+ appconf = M.AppConfig.query.get(_id=appid)
+ if not appconf:
+ return
+
+ c.project = appconf.project
+ app = ForgeBlogApp(c.project, appconf)
+ c.app = app
+
+ base.log.info("Get feed: %s" % feed_url)
+ f = feedparser.parse(feed_url)
+ if f.bozo:
+ base.log.exception("%s: %s" % (feed_url, f.bozo_exception))
+ return
+ for e in f.entries:
+ title = e.title
+ if 'content' in e:
+ content = u''
+ for ct in e.content:
+ if ct.type != 'text/html':
+ content = u"%s<p>%s</p>" % (content, ct.value)
+ else:
+ content = content + ct.value
+ else:
+ content = e.summary
+
+ content = u'%s <a href="%s">link</a>' % (content, e.link)
+ content = html2text.html2text(content, e.link)
+
+ post = BM.BlogPost(title=title, text=content, app_config_id=appid,
+ tool_version={'blog': version.__version__},
+ state='draft')
+ post.neighborhood_id=c.project.neighborhood_id
+ post.make_slug()
+ post.commit()
diff --git a/Allura/allura/lib/exceptions.py b/Allura/allura/lib/exceptions.py
index fa9f4a0..30250f7 100644
--- a/Allura/allura/lib/exceptions.py
+++ b/Allura/allura/lib/exceptions.py
@@ -4,6 +4,7 @@
class ToolError(ForgeError): pass
class NoSuchProjectError(ForgeError): pass
class NoSuchNeighborhoodError(ForgeError): pass
+class NoSuchGlobalsError(ForgeError): pass
class MailError(ForgeError): pass
class AddressException(MailError): pass
class NoSuchNBFeatureError(ForgeError): pass
diff --git a/Allura/allura/tests/test_commands.py b/Allura/allura/tests/test_commands.py
index 6bcdb8a..6d2ef99 100644
--- a/Allura/allura/tests/test_commands.py
+++ b/Allura/allura/tests/test_commands.py
@@ -1,26 +1,32 @@
from nose.tools import assert_raises
+from ming.orm.ormsession import ThreadLocalORMSession
+
from alluratest.controller import setup_basic_test, setup_global_objects
-from allura.command import script, set_neighborhood_features
+from allura.command import script, set_neighborhood_features, rssfeeds
from allura import model as M
from allura.lib.exceptions import InvalidNBFeatureValueError
+from forgeblog import model as BM
test_config = 'test.ini#main'
class EmptyClass(object): pass
+
def setUp(self):
"""Method called by nose before running each test"""
#setup_basic_test(app_name='main_with_amqp')
setup_basic_test()
setup_global_objects()
+
def test_script():
cmd = script.ScriptCommand('script')
- cmd.run([test_config, 'allura/tests/tscript.py' ])
+ cmd.run([test_config, 'allura/tests/tscript.py'])
cmd.command()
- assert_raises(ValueError, cmd.run, [test_config, 'allura/tests/tscript_error.py' ])
+ assert_raises(ValueError, cmd.run, [test_config, 'allura/tests/tscript_error.py'])
+
def test_set_neighborhood_max_projects():
neighborhood = M.Neighborhood.query.find().first()
@@ -43,6 +49,7 @@
assert_raises(InvalidNBFeatureValueError, cmd.run, [test_config, str(n_id), 'max_projects', 'string'])
assert_raises(InvalidNBFeatureValueError, cmd.run, [test_config, str(n_id), 'max_projects', '2.8'])
+
def test_set_neighborhood_private():
neighborhood = M.Neighborhood.query.find().first()
n_id = neighborhood._id
@@ -65,6 +72,7 @@
assert_raises(InvalidNBFeatureValueError, cmd.run, [test_config, str(n_id), 'private_projects', '1'])
assert_raises(InvalidNBFeatureValueError, cmd.run, [test_config, str(n_id), 'private_projects', '2.8'])
+
def test_set_neighborhood_google_analytics():
neighborhood = M.Neighborhood.query.find().first()
n_id = neighborhood._id
@@ -80,6 +88,7 @@
cmd.run([test_config, str(n_id), 'google_analytics', 'False'])
cmd.command()
neighborhood = M.Neighborhood.query.get(_id=n_id)
+
assert not neighborhood.features['google_analytics']
# check validation
@@ -87,6 +96,7 @@
assert_raises(InvalidNBFeatureValueError, cmd.run, [test_config, str(n_id), 'google_analytics', '1'])
assert_raises(InvalidNBFeatureValueError, cmd.run, [test_config, str(n_id), 'google_analytics', '2.8'])
+
def test_set_neighborhood_css():
neighborhood = M.Neighborhood.query.find().first()
n_id = neighborhood._id
@@ -116,3 +126,22 @@
assert_raises(InvalidNBFeatureValueError, cmd.run, [test_config, str(n_id), 'css', '2.8'])
assert_raises(InvalidNBFeatureValueError, cmd.run, [test_config, str(n_id), 'css', 'None'])
assert_raises(InvalidNBFeatureValueError, cmd.run, [test_config, str(n_id), 'css', 'True'])
+
+
+def test_pull_rss_feeds():
+ base_app = M.AppConfig.query.find().all()[0]
+ tmp_app = M.AppConfig(tool_name=u'Blog', discussion_id=base_app.discussion_id,
+ project_id=base_app.project_id,
+ options={u'ordinal': 0, u'show_right_bar': True,
+ u'project_name': base_app.project.name,
+ u'mount_point': u'blog',
+ u'mount_label': u'Blog'})
+ new_external_feeds = ['http://wordpress.org/news/feed/']
+ BM.Globals(app_config_id=tmp_app._id, external_feeds=new_external_feeds)
+ ThreadLocalORMSession.flush_all()
+
+ cmd = rssfeeds.RssFeedsCommand('pull-rss-feeds')
+ cmd.run([test_config, '-a', tmp_app._id])
+ cmd.command()
+
+ assert len(BM.BlogPost.query.find({'app_config_id': tmp_app._id}).all()) > 0
diff --git a/Allura/setup.py b/Allura/setup.py
index 6420acd..9b4f5a0 100644
--- a/Allura/setup.py
+++ b/Allura/setup.py
@@ -113,6 +113,7 @@
create-neighborhood = allura.command:CreateNeighborhoodCommand
create-trove-categories = allura.command:CreateTroveCategoriesCommand
set-neighborhood-features = allura.command:SetNeighborhoodFeaturesCommand
+ pull-rss-feeds = allura.command.rssfeeds:RssFeedsCommand
[easy_widgets.resources]
ew_resources=allura.config.resources:register_ew_resources
diff --git a/ForgeBlog/forgeblog/main.py b/ForgeBlog/forgeblog/main.py
index bacc532..f7e1d0a 100644
--- a/ForgeBlog/forgeblog/main.py
+++ b/ForgeBlog/forgeblog/main.py
@@ -12,12 +12,14 @@
from formencode import validators
from webob import exc
+from ming.orm import session
+
# Pyforge-specific imports
from allura.app import Application, ConfigOption, SitemapEntry
from allura.app import DefaultAdminController
from allura.lib import helpers as h
from allura.lib.search import search
-from allura.lib.decorators import require_post
+from allura.lib.decorators import require_post, Property
from allura.lib.security import has_access, require_access
from allura.lib import widgets as w
from allura.lib.widgets.subscriptions import SubscribeForm
@@ -56,6 +58,7 @@
ordinal=14
installable=True
config_options = Application.config_options
+ default_external_feeds = []
icons={
24:'images/blog_24.png',
32:'images/blog_32.png',
@@ -67,6 +70,24 @@
self.root = RootController()
self.admin = BlogAdminController(self)
+ @Property
+ def external_feeds_list():
+ def fget(self):
+ globals = BM.Globals.query.get(app_config_id=self.config._id)
+ if globals is not None:
+ external_feeds = globals.external_feeds
+ else:
+ external_feeds = self.default_external_feeds
+ return external_feeds
+ def fset(self, new_external_feeds):
+ globals = BM.Globals.query.get(app_config_id=self.config._id)
+ if globals is not None:
+ globals.external_feeds = new_external_feeds
+ elif len(new_external_feeds) > 0:
+ globals = BM.Globals(app_config_id=self.config._id, external_feeds=new_external_feeds)
+ if globals is not None:
+ session(globals).flush()
+
@property
@h.exceptionless([], log)
def sitemap(self):
@@ -94,7 +115,11 @@
return links
def admin_menu(self):
- return super(ForgeBlogApp, self).admin_menu(force_options=True)
+ admin_url = c.project.url() + 'admin/' + self.config.options.mount_point + '/'
+ links = [SitemapEntry('External feeds', admin_url + 'exfeed', className='admin_modal')]
+ links += super(ForgeBlogApp, self).admin_menu(force_options=True)
+ return links
+ #return super(ForgeBlogApp, self).admin_menu(force_options=True)
def install(self, project):
'Set up any default permissions and roles here'
@@ -359,3 +384,34 @@
self.app.config.options['show_discussion'] = show_discussion and True or False
flash('Blog options updated')
redirect(h.really_unicode(c.project.url()+'admin/tools').encode('utf-8'))
+
+ @without_trailing_slash
+ @expose('jinja:forgeblog:templates/blog/admin_exfeed.html')
+ def exfeed(self):
+ #self.app.external_feeds_list = ['feed1', 'feed2']
+ #log.info("EXFEED: %s" % self.app.external_feeds_list)
+ feeds_list = []
+ for feed in self.app.external_feeds_list:
+ feeds_list.append(feed)
+ return dict(app=self.app,
+ feeds_list=feeds_list,
+ allow_config=has_access(self.app, 'configure')())
+
+ @without_trailing_slash
+ @expose()
+ @require_post()
+ def set_exfeed(self, **kw):
+ new_exfeed = kw.get('new_exfeed', None)
+ exfeed_val = kw.get('exfeed', [])
+ if type(exfeed_val) == unicode:
+ exfeed_list = []
+ exfeed_list.append(exfeed_val)
+ else:
+ exfeed_list = exfeed_val
+
+ if new_exfeed is not None and new_exfeed != '':
+ exfeed_list.append(new_exfeed)
+
+ self.app.external_feeds_list = exfeed_list
+ flash('External feeds updated')
+ redirect(c.project.url()+'admin/tools')
diff --git a/ForgeBlog/forgeblog/model/__init__.py b/ForgeBlog/forgeblog/model/__init__.py
index 3f6c73e..3a7399f 100644
--- a/ForgeBlog/forgeblog/model/__init__.py
+++ b/ForgeBlog/forgeblog/model/__init__.py
@@ -1 +1 @@
-from blog import BlogPost, Attachment, BlogPostSnapshot
+from blog import Globals, BlogPost, Attachment, BlogPostSnapshot
diff --git a/ForgeBlog/forgeblog/model/blog.py b/ForgeBlog/forgeblog/model/blog.py
index 938b408..0909108 100644
--- a/ForgeBlog/forgeblog/model/blog.py
+++ b/ForgeBlog/forgeblog/model/blog.py
@@ -9,6 +9,8 @@
from ming import schema
from ming.orm import FieldProperty, ForeignIdProperty, Mapper, session, state
+from ming.orm.declarative import MappedClass
+
from allura import model as M
from allura.lib import helpers as h
from allura.lib import utils, patience
@@ -16,6 +18,19 @@
config = utils.ConfigProxy(
common_suffix='forgemail.domain')
+class Globals(MappedClass):
+
+ class __mongometa__:
+ name = 'blog-globals'
+ session = M.project_orm_session
+ indexes = [ 'app_config_id' ]
+
+ type_s = 'BlogGlobals'
+ _id = FieldProperty(schema.ObjectId)
+ app_config_id = ForeignIdProperty('AppConfig', if_missing=lambda:c.app.config._id)
+ external_feeds=FieldProperty([str])
+
+
class BlogPostSnapshot(M.Snapshot):
class __mongometa__:
name='blog_post_snapshot'
diff --git a/ForgeBlog/forgeblog/templates/blog/admin_exfeed.html b/ForgeBlog/forgeblog/templates/blog/admin_exfeed.html
new file mode 100644
index 0000000..2c337bc
--- /dev/null
+++ b/ForgeBlog/forgeblog/templates/blog/admin_exfeed.html
@@ -0,0 +1,27 @@
+<form method="POST" action="{{c.project.url()}}admin/{{app.config.options.mount_point}}/set_exfeed">
+ <label class="grid-13">Existing external feeds:</label>
+ <div class="grid-13">
+ <ul>
+ {% if allow_config %}
+ {% for feed in feeds_list %}
+ <li><input type="checkbox" name="exfeed" value="{{ feed }}" checked="checked"><span>{{ feed }}</span></li>
+ {% endfor %}
+ {% else %}
+ {% for feed in feeds_list %}
+ <li><span>{{ feed.value }}</span></li>
+ {% endfor %}
+ {% endif %}
+ </div>
+ {% if allow_config %}
+ <div class="grid-13"> </div>
+ <div class="grid-13">
+ <input type="text" name="new_exfeed" id="new_exfeed" value=""/>
+ </div>
+ <div class="grid-13"> </div>
+ <hr>
+ <div class="grid-13"> </div>
+ <div class="grid-13">
+ <input type="submit" value="Save"/>
+ </div>
+ {% endif %}
+</form>
diff --git a/requirements-common.txt b/requirements-common.txt
index c6b1a0f..57157c1 100644
--- a/requirements-common.txt
+++ b/requirements-common.txt
@@ -15,6 +15,7 @@
# dep of Creoleparser
Genshi==0.6
# dep of oauth2
+html2text==3.200.3
httplib2==0.7.4
iso8601==0.1.4
Jinja2==2.6