Skip to content

Commit

Permalink
Merge pull request #2962 from aptivate/override-feed-class
Browse files Browse the repository at this point in the history
Allow RSS feed to be extended
  • Loading branch information
amercader committed Oct 10, 2016
2 parents 7e3e181 + 22dda1d commit dbf5173
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 5 deletions.
26 changes: 21 additions & 5 deletions ckan/controllers/feed.py
Expand Up @@ -31,6 +31,7 @@
import ckan.lib.base as base
import ckan.lib.helpers as h
import ckan.logic as logic
import ckan.plugins as plugins

from ckan.common import _, g, c, request, response, json

Expand Down Expand Up @@ -361,10 +362,18 @@ def output_feed(self, results, feed_title, feed_description,
config.get('ckan.site_url', '').strip()

# TODO language
feed = _FixedAtom1Feed(
title=feed_title,
link=feed_link,
description=feed_description,
feed_class = None
for plugin in plugins.PluginImplementations(plugins.IFeed):
if hasattr(plugin, 'get_feed_class'):
feed_class = plugin.get_feed_class()

if not feed_class:
feed_class = _FixedAtom1Feed

feed = feed_class(
feed_title,
feed_link,
feed_description,
language=u'en',
author_name=author_name,
author_link=author_link,
Expand All @@ -377,6 +386,12 @@ def output_feed(self, results, feed_title, feed_description,
)

for pkg in results:
additional_fields = {}

for plugin in plugins.PluginImplementations(plugins.IFeed):
if hasattr(plugin, 'get_item_additional_fields'):
additional_fields = plugin.get_item_additional_fields(pkg)

feed.add_item(
title=pkg.get('title', ''),
link=self.base_url + h.url_for(controller='package',
Expand All @@ -396,7 +411,8 @@ def output_feed(self, results, feed_title, feed_description,
id=pkg['name'],
ver='2'),
unicode(len(json.dumps(pkg))), # TODO fix this
u'application/json')
u'application/json'),
**additional_fields
)
response.content_type = feed.mime_type
return feed.writeString('utf-8')
Expand Down
46 changes: 46 additions & 0 deletions ckan/plugins/interfaces.py
Expand Up @@ -12,6 +12,7 @@
u'IMiddleware',
u'IAuthFunctions',
u'IDomainObjectModification',
u'IFeed',
u'IGroupController',
u'IOrganizationController',
u'IPackageController',
Expand Down Expand Up @@ -206,6 +207,51 @@ def notify_after_commit(self, entity, operation):
pass


class IFeed(Interface):
"""
Allows extension of the default Atom feeds
"""

def get_feed_class(self):
"""
Allows plugins to provide a custom class to generate feed items.
:returns: feed class
:rtype: type
The feed item generator's constructor is called as follows::
feed_class(
feed_title, # Mandatory
feed_link, # Mandatory
feed_description, # Mandatory
language, # Optional, always set to 'en'
author_name, # Optional
author_link, # Optional
feed_guid, # Optional
feed_url, # Optional
previous_page, # Optional, url of previous page of feed
next_page, # Optional, url of next page of feed
first_page, # Optional, url of first page of feed
last_page, # Optional, url of last page of feed
)
"""

pass

def get_item_additional_fields(self, dataset_dict):
"""
Allows plugins to set additional fields on a feed item.
:param dataset_dict: the dataset metadata
:type dataset_dict: dictionary
:returns: the fields to set
:rtype: dictionary
"""
pass


class IResourceUrlChange(Interface):
u'''
Receives notification of changed urls.
Expand Down
58 changes: 58 additions & 0 deletions ckan/tests/controllers/test_feed.py
@@ -1,6 +1,9 @@
# encoding: utf-8

from routes import url_for
from webhelpers.feedgenerator import GeoAtom1Feed

import ckan.plugins as plugins

import ckan.tests.helpers as helpers
import ckan.tests.factories as factories
Expand Down Expand Up @@ -81,3 +84,58 @@ def test_custom_atom_feed_works(self):
assert '<title>{0}</title>'.format(dataset1['title']) in res.body

assert '<title>{0}</title>'.format(dataset2['title']) not in res.body


class TestFeedInterface(helpers.FunctionalTestBase):
@classmethod
def setup_class(cls):
super(TestFeedInterface, cls).setup_class()

if not plugins.plugin_loaded('test_feed_plugin'):
plugins.load('test_feed_plugin')

@classmethod
def teardown_class(cls):
helpers.reset_db()
plugins.unload('test_feed_plugin')

def test_custom_class_used(self):
offset = url_for(controller='feed', action='general')
app = self._get_test_app()
res = app.get(offset)

assert 'xmlns:georss="http://www.georss.org/georss"' in res.body, res.body

def test_additional_fields_added(self):
metadata = {
'ymin': '-2373790',
'xmin': '2937940',
'ymax': '-1681290',
'xmax': '3567770',
}

extras = [
{'key': k, 'value': v} for (k, v) in
metadata.items()
]

factories.Dataset(extras=extras)
offset = url_for(controller='feed', action='general')
app = self._get_test_app()
res = app.get(offset)

assert '<georss:box>-2373790.000000 2937940.000000 -1681290.000000 3567770.000000</georss:box>' in res.body, res.body


class MockFeedPlugin(plugins.SingletonPlugin):
plugins.implements(plugins.IFeed)

def get_feed_class(self):
return GeoAtom1Feed

def get_item_additional_fields(self, dataset_dict):
extras = {e['key']: e['value'] for e in dataset_dict['extras']}

box = tuple(float(extras.get(n))
for n in ('ymin', 'xmin', 'ymax', 'xmax'))
return {'geometry': box}
1 change: 1 addition & 0 deletions setup.py
Expand Up @@ -166,6 +166,7 @@
'test_datapusher_plugin = ckanext.datapusher.tests.test_interfaces:FakeDataPusherPlugin',
'test_routing_plugin = ckan.tests.config.test_middleware:MockRoutingPlugin',
'test_helpers_plugin = ckan.tests.lib.test_helpers:TestHelpersPlugin',
'test_feed_plugin = ckan.tests.controllers.test_feed:MockFeedPlugin',
],
'babel.extractors': [
'ckan = ckan.lib.extract:extract_ckan',
Expand Down

0 comments on commit dbf5173

Please sign in to comment.