diff --git a/features/news_api_item.feature b/features/news_api_item.feature
index 1c005e97c..06beef0d0 100644
--- a/features/news_api_item.feature
+++ b/features/news_api_item.feature
@@ -20,7 +20,7 @@ Feature: News API Item
"headline": "Headline of the story"
}]
"""
- When we get "v1/news/item/#items._id#"
+ When we get "/news/item/#items._id#"
Then we get OK response
Scenario: Attempt to Retrieve an item with unknown format
@@ -32,7 +32,7 @@ Feature: News API Item
"headline": "Headline of the story"
}]
"""
- When we get "v1/news/item/#items._id#?format=bogus"
+ When we get "/news/item/#items._id#?format=bogus"
Then we get response code 404
Scenario: Retrieve an item that does not exist
@@ -44,7 +44,7 @@ Feature: News API Item
"headline": "Headline of the story"
}]
"""
- When we get "v1/news/item/999"
+ When we get "/news/item/999"
Then we get response code 404
Scenario: Retrieve a version of an item
@@ -57,7 +57,7 @@ Feature: News API Item
"version" : "5"
}]
"""
- When we get "v1/news/item/111?version=5"
+ When we get "/news/item/111?version=5"
Then we get OK response
Scenario: Retrieve an item in ninjs
@@ -69,7 +69,7 @@ Feature: News API Item
"headline": "Headline of the story"
}]
"""
- When we get "v1/news/item/#items._id#?format=NINJSFormatter"
+ When we get "/news/item/#items._id#?format=NINJSFormatter"
Then we get existing resource
"""
{"guid": "111",
@@ -86,7 +86,7 @@ Feature: News API Item
"versioncreated": "2018-11-01T03:01:40.000Z"
}]
"""
- When we get "v1/news/item/#items._id#?format=NINJSFormatter"
+ When we get "/news/item/#items._id#?format=NINJSFormatter"
Then we get response code 404
Scenario: Retrieve an item in text format
@@ -99,7 +99,46 @@ Feature: News API Item
"body_html": "
test test
"
}]
"""
- When we get "v1/news/item/#items._id#?format=TextFormatter"
+ When we get "/news/item/#items._id#?format=TextFormatter"
Then we get OK response
Then we get "test test" in text response
+ Scenario: Retrieve an item with associations
+ Given "items"
+ """
+ [{
+ "_id": "111",
+ "pubstatus": "usable",
+ "headline": "Headline of the story",
+ "body_html": "test test
",
+ "associations": {
+ "featuremedia": {
+ "renditions": {
+ "16-9": {
+ "href": "/assets/1234567"
+ },
+ "_newsroom_thumbnail": {
+ "href": "/assets/987654"
+ }
+ }
+ }
+ }
+ }]
+ """
+ When we get "/news/item/#items._id#?format=NINJSFormatter2"
+ Then we get existing resource
+ """
+ {
+ "guid": "111",
+ "headline": "Headline of the story",
+ "associations": {
+ "featuremedia": {
+ "renditions": {
+ "16-9": {
+ "href": "/assets/1234567"
+ }
+ }
+ }
+ }
+ }
+ """
\ No newline at end of file
diff --git a/newsroom/news_api/news/assets/assets.py b/newsroom/news_api/news/assets/assets.py
new file mode 100644
index 000000000..cf5785e33
--- /dev/null
+++ b/newsroom/news_api/news/assets/assets.py
@@ -0,0 +1,41 @@
+import superdesk
+import flask
+from newsroom.news_api.api_tokens import CompanyTokenAuth
+from flask import abort
+from newsroom.upload import ASSETS_RESOURCE
+from flask_babel import gettext
+import bson.errors
+from werkzeug.wsgi import wrap_file
+from newsroom.news_api.utils import post_api_audit
+
+blueprint = superdesk.Blueprint('assets', __name__)
+
+
+def init_app(app):
+ superdesk.blueprint(blueprint, app)
+
+
+@blueprint.route('/assets/', methods=['GET'])
+def get_item(asset_id):
+ if CompanyTokenAuth().check_auth(flask.request.headers.get('Authorization'), None, None, 'GET'):
+ try:
+ media_file = flask.current_app.media.get(asset_id, ASSETS_RESOURCE)
+ except bson.errors.InvalidId:
+ media_file = None
+ if not media_file:
+ flask.abort(404)
+
+ data = wrap_file(flask.request.environ, media_file, buffer_size=1024 * 256)
+ response = flask.current_app.response_class(
+ data,
+ mimetype=media_file.content_type,
+ direct_passthrough=True)
+ response.content_length = media_file.length
+ response.last_modified = media_file.upload_date
+ response.set_etag(media_file.md5)
+ response.make_conditional(flask.request)
+ response.headers['Content-Disposition'] = 'inline'
+ post_api_audit({'_items': [{'_id': asset_id}]})
+ return response
+ else:
+ abort(401, gettext('Invalid token'))
diff --git a/newsroom/news_api/news/item/item.py b/newsroom/news_api/news/item/item.py
index 3a4dddaf7..4fda13bb1 100644
--- a/newsroom/news_api/news/item/item.py
+++ b/newsroom/news_api/news/item/item.py
@@ -1,5 +1,4 @@
import superdesk
-from newsroom.news_api.settings import URL_PREFIX
import flask
from superdesk import get_resource_service
from flask import current_app as app, abort
@@ -10,7 +9,11 @@
blueprint = superdesk.Blueprint('news/item', __name__)
-@blueprint.route('/{}/news/item/'.format(URL_PREFIX), methods=['GET'])
+def init_app(app):
+ superdesk.blueprint(blueprint, app)
+
+
+@blueprint.route('/news/item/', methods=['GET'])
def get_item(item_id):
if CompanyTokenAuth().check_auth(flask.request.headers.get('Authorization'), None, None, 'GET'):
_format = flask.request.args.get('format', 'NINJSFormatter')
diff --git a/newsroom/news_api/news/search_service.py b/newsroom/news_api/news/search_service.py
index e0bf80d37..22f2a2d21 100644
--- a/newsroom/news_api/news/search_service.py
+++ b/newsroom/news_api/news/search_service.py
@@ -16,7 +16,7 @@
from content_api.errors import BadParameterValueError, UnexpectedParameterError
from newsroom.news_api.settings import ELASTIC_DATETIME_FORMAT
-from newsroom.news_api.utils import post_api_audit
+from newsroom.news_api.utils import post_api_audit, remove_internal_renditions
from newsroom.search import BaseSearchService, query_string
from newsroom.products.products import get_products_by_company
@@ -40,7 +40,7 @@ class NewsAPINewsService(BaseSearchService):
# set of fields that can be specified in the include_fields parameter
allowed_include_fields = {'type', 'urgency', 'priority', 'language', 'description_html', 'located', 'keywords',
'source', 'subject', 'place', 'wordcount', 'charcount', 'body_html', 'readtime',
- 'profile', 'service', 'genre'}
+ 'profile', 'service', 'genre', 'associations'}
default_fields = {
'_id', 'uri', 'embargoed', 'pubstatus', 'ednote', 'signal', 'copyrightnotice', 'copyrightholder',
@@ -49,7 +49,7 @@ class NewsAPINewsService(BaseSearchService):
# set of fields that will be removed from all responses, we are not currently supporting associations and
# the products embedded in the items are the superdesk products
- mandatory_exclude_fields = {'associations', '_current_version', 'products'}
+ mandatory_exclude_fields = {'_current_version', 'products'}
section = 'news_api'
limit_days_setting = 'news_api_time_limit_days'
@@ -63,10 +63,14 @@ def get(self, req, lookup):
exclude_fields = self.mandatory_exclude_fields.union(
set(orig_request_params.get('exclude_fields').split(','))) if orig_request_params.get(
'exclude_fields') else self.mandatory_exclude_fields
+
for doc in resp.docs:
for field in exclude_fields:
doc.pop(field, None)
+ if 'associations' in orig_request_params.get('include_fields', ''):
+ remove_internal_renditions(doc)
+
return resp
def prefill_search_query(self, search, req=None, lookup=None):
diff --git a/newsroom/news_api/settings.py b/newsroom/news_api/settings.py
index 69a8e2272..c6a7081c2 100644
--- a/newsroom/news_api/settings.py
+++ b/newsroom/news_api/settings.py
@@ -14,19 +14,17 @@
'newsroom.news_api.products',
'newsroom.news_api.formatters',
'newsroom.news_api.news',
- 'newsroom.news_api.news.item',
+ 'newsroom.news_api.news.item.item',
'newsroom.news_api.news.search',
'newsroom.news_api.news.feed',
'newsroom.products',
'newsroom.news_api.api_audit',
+ 'newsroom.news_api.news.assets.assets',
+ 'newsroom.upload',
]
INSTALLED_APPS = []
-BLUEPRINTS = [
- 'newsroom.news_api.news.item.item'
-]
-
#: mongo db name, only used when mongo_uri is not set
MONGO_DBNAME = env('MONGO_DBNAME', 'newsroom')
diff --git a/newsroom/news_api/utils.py b/newsroom/news_api/utils.py
index c1c10c389..6b6781e68 100644
--- a/newsroom/news_api/utils.py
+++ b/newsroom/news_api/utils.py
@@ -33,3 +33,17 @@ def format_report_results(search_result, unique_endpoints, companies):
unique_endpoints.append(endpoint_bucket['key'])
return results
+
+
+def remove_internal_renditions(item):
+ clean_renditions = dict()
+
+ # associations featuremedia will contain the internal newsroom renditions, we need to remove these.
+ if ((item.get('associations') or {}).get('featuremedia') or {}).get('renditions'):
+ for key, rendition in\
+ item['associations']['featuremedia']['renditions'].items():
+ if not key.startswith('_newsroom'):
+ clean_renditions[key] = rendition
+ item['associations']['featuremedia']['renditions'] = clean_renditions
+
+ return item
diff --git a/newsroom/upload.py b/newsroom/upload.py
index 13140c302..2fb25f41a 100644
--- a/newsroom/upload.py
+++ b/newsroom/upload.py
@@ -64,4 +64,5 @@ def init_app(app):
app.config['DOMAIN'].setdefault('upload', {
'authentication': None,
'mongo_prefix': newsroom.MONGO_PREFIX,
+ 'internal_resource': True
})
diff --git a/newsroom/wire/formatters/__init__.py b/newsroom/wire/formatters/__init__.py
index 25c95560a..35fdc261a 100644
--- a/newsroom/wire/formatters/__init__.py
+++ b/newsroom/wire/formatters/__init__.py
@@ -23,3 +23,4 @@ def __init__(cls, name, bases, attrs):
from .json import JsonFormatter # noqa
from .ninjs import NINJSFormatter # noqa
from .picture import PictureFormatter # noqa
+from .ninjs2 import NINJSFormatter2 # noqa
diff --git a/newsroom/wire/formatters/ninjs2.py b/newsroom/wire/formatters/ninjs2.py
new file mode 100644
index 000000000..71ac784da
--- /dev/null
+++ b/newsroom/wire/formatters/ninjs2.py
@@ -0,0 +1,14 @@
+from .ninjs import NINJSFormatter
+from newsroom.news_api.utils import remove_internal_renditions
+
+
+class NINJSFormatter2(NINJSFormatter):
+ """
+ Overload the NINJSFormatter and add the associations as a field to copy
+ """
+
+ def __init__(self):
+ self.direct_copy_properties += ('associations',)
+
+ def _transform_to_ninjs(self, item):
+ return remove_internal_renditions(super()._transform_to_ninjs(item))
diff --git a/requirements.txt b/requirements.txt
index f65a78de8..1049d8a9d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,4 +5,4 @@ xhtml2pdf
werkzeug>=0.9.4,<=0.11.15
-e .
git+git://github.com/superdesk/superdesk-planning.git@1.10#egg=superdesk-planning
-git+git://github.com/superdesk/superdesk-core.git@master#egg=superdesk-core
+git+git://github.com/superdesk/superdesk-core.git@v1.33.7#egg=superdesk-core
diff --git a/tests/news_api/test_api_assets.py b/tests/news_api/test_api_assets.py
new file mode 100644
index 000000000..a11a6bf01
--- /dev/null
+++ b/tests/news_api/test_api_assets.py
@@ -0,0 +1,29 @@
+import os
+from superdesk.storage.desk_media_storage import SuperdeskGridFSMediaStorage
+from tests.news_api.test_api_audit import audit_check
+
+
+def get_fixture_path(fixture):
+ return os.path.join(os.path.dirname(__file__), '../fixtures', fixture)
+
+
+def setup_image(app):
+ with open(get_fixture_path('picture.jpg'), 'rb') as f:
+ res = SuperdeskGridFSMediaStorage(app=app).put(f, 'picture.jpg', content_type='image/jpg')
+ return res
+
+
+def test_get_asset(client, app):
+ app.data.insert('companies', [{"_id": "company_123", "name": "Test Company", "is_enabled": True}])
+ app.data.insert('news_api_tokens', [{"company": "company_123", "enabled": True}])
+ token = app.data.find_one('news_api_tokens', req=None, company='company_123')
+
+ id = setup_image(app)
+ response = client.get('api/v1/assets/{}'.format(id), headers={'Authorization': token.get('token')})
+ assert response.status_code == 200
+ audit_check(str(id))
+
+
+def test_authorization_get_asset(client, app):
+ response = client.get('api/v1/assets/{}'.format(id), headers={'Authorization': 'xxxxxxxx'})
+ assert response.status_code == 401