Feature: Add API to retrieve the list of datasets acquired by a user
This commit is contained in:
parent
5b8c87f605
commit
e9a5a50653
|
@ -19,6 +19,7 @@
|
|||
|
||||
import ckan.plugins as plugins
|
||||
import ckanext.privatedatasets.constants as constants
|
||||
import db
|
||||
import importlib
|
||||
import logging
|
||||
import pylons.config as config
|
||||
|
@ -29,6 +30,28 @@ PARSER_CONFIG_PROP = 'ckan.privatedatasets.parser'
|
|||
|
||||
|
||||
def package_acquired(context, request_data):
|
||||
'''
|
||||
API action to be called every time a user acquires a dataset in an external service.
|
||||
|
||||
This API should be called to add the user to the list of allowed users.
|
||||
|
||||
Since each service can provide a different way of pushing the data, the received
|
||||
data will be forwarded to the parser set in the preferences. This parser should
|
||||
return a dict similar to the following one:
|
||||
{'errors': ["...", "...", ...]
|
||||
'users_datasets': [{'user': 'user_name', 'datasets': ['ds1', 'ds2', ...]}, ...]}
|
||||
1) 'errors' contains the list of errors. It should be empty if no errors arised
|
||||
while the notification is parsed
|
||||
2) 'users_datasets' is the lists of datasets available for each user (each element
|
||||
of this list is a dictionary with two fields: user and datasets).
|
||||
|
||||
:parameter request_data: Depends on the parser
|
||||
:type request_data: dict
|
||||
|
||||
:return: A list of warnings or None if the list of warnings is empty
|
||||
:rtype: dict
|
||||
|
||||
'''
|
||||
|
||||
log.info('Notification received: %s' % request_data)
|
||||
|
||||
|
@ -107,3 +130,59 @@ def package_acquired(context, request_data):
|
|||
# Return warnings that inform about non-existing datasets
|
||||
if len(warns) > 0:
|
||||
return {'warns': warns}
|
||||
|
||||
def acquisitions_list(context, data_dict):
|
||||
'''
|
||||
API to retrieve the list of datasets that have been acquired by a certain user
|
||||
|
||||
:parameter user: The user whose acquired dataset you want to retrieve. This parameter
|
||||
is optional. If you don't include this identifier, the system will use the one
|
||||
of the user that is performing the request
|
||||
:type user: string
|
||||
|
||||
:return: The list of datarequest that has been acquired by the specified user
|
||||
:rtype: list
|
||||
'''
|
||||
|
||||
if data_dict is None:
|
||||
data_dict = {}
|
||||
|
||||
if 'user' not in data_dict and 'user' in context:
|
||||
data_dict['user'] = context['user']
|
||||
|
||||
plugins.toolkit.check_access(constants.ACQUISITIONS_LIST, context.copy(), data_dict)
|
||||
|
||||
# Init db
|
||||
db.init_db(context['model'])
|
||||
|
||||
# Init the result array
|
||||
result = []
|
||||
|
||||
# Check that the user exists
|
||||
try:
|
||||
plugins.toolkit.get_validator('user_name_exists')(data_dict['user'], context.copy())
|
||||
except Exception:
|
||||
raise plugins.toolkit.ValidationError('User %s does not exist' % data_dict['user'])
|
||||
|
||||
# Get the datasets acquired by the user
|
||||
query = db.AllowedUser.get(user_name=data_dict['user'])
|
||||
|
||||
# Get the datasets
|
||||
for dataset in query:
|
||||
try:
|
||||
dataset_show_func = 'dataset_show'
|
||||
func_data_dict = {'id': dataset.package_id}
|
||||
internal_context = context.copy()
|
||||
|
||||
# Check that the the dataset can be accessed and get its data
|
||||
# FIX: If the check_access function is not called, an exception is risen.
|
||||
plugins.toolkit.check_access(dataset_show_func, internal_context, func_data_dict)
|
||||
dataset_dict = plugins.toolkit.get_action(dataset_show_func)(internal_context, func_data_dict)
|
||||
|
||||
# Only packages with state == 'active' can be shown
|
||||
if dataset_dict.get('state', None) == 'active':
|
||||
result.append(dataset_dict)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return result
|
||||
|
|
|
@ -133,3 +133,7 @@ def resource_show(context, data_dict):
|
|||
def package_acquired(context, data_dict):
|
||||
# TODO: Improve security
|
||||
return {'success': True}
|
||||
|
||||
def acquisitions_list(context, data_dict):
|
||||
# Users can get only their acquisitions list
|
||||
return {'success': context['user'] == data_dict['user']}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
# along with CKAN Private Dataset Extension. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ALLOWED_USERS = 'allowed_users'
|
||||
ACQUISITIONS_LIST = 'acquisitions_list'
|
||||
ALLOWED_USERS_STR = 'allowed_users_str'
|
||||
SEARCHABLE = 'searchable'
|
||||
ACQUIRE_URL = 'acquire_url'
|
||||
|
|
|
@ -17,10 +17,10 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with CKAN Private Dataset Extension. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ckanext.privatedatasets.constants as constants
|
||||
import ckan.lib.base as base
|
||||
import ckan.model as model
|
||||
import ckan.plugins as plugins
|
||||
import ckanext.privatedatasets.db as db
|
||||
import logging
|
||||
|
||||
from ckan.common import _
|
||||
|
@ -32,8 +32,6 @@ class AcquiredDatasetsControllerUI(base.BaseController):
|
|||
|
||||
def user_acquired_datasets(self):
|
||||
|
||||
db.init_db(model)
|
||||
|
||||
c = plugins.toolkit.c
|
||||
context = {
|
||||
'model': model,
|
||||
|
@ -44,23 +42,10 @@ class AcquiredDatasetsControllerUI(base.BaseController):
|
|||
# Get user information
|
||||
try:
|
||||
c.user_dict = plugins.toolkit.get_action('user_show')(context.copy(), {'user_obj': c.userobj})
|
||||
c.user_dict['acquired_datasets'] = []
|
||||
c.user_dict['acquired_datasets'] = plugins.toolkit.get_action(constants.ACQUISITIONS_LIST)(context.copy(), None)
|
||||
except plugins.toolkit.ObjectNotFound:
|
||||
plugins.toolkit.abort(404, _('User not found'))
|
||||
except plugins.toolkit.NotAuthorized:
|
||||
plugins.toolkit.abort(401, _('Not authorized to see this page'))
|
||||
|
||||
# Get the datasets acquired by the user
|
||||
query = db.AllowedUser.get(user_name=context['user'])
|
||||
|
||||
# Get the datasets
|
||||
for dataset in query:
|
||||
try:
|
||||
dataset_dict = plugins.toolkit.get_action('package_show')(context.copy(), {'id': dataset.package_id})
|
||||
# Only packages with state == 'active' can be shown
|
||||
if dataset_dict.get('state', None) == 'active':
|
||||
c.user_dict['acquired_datasets'].append(dataset_dict)
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
return plugins.toolkit.render('user/dashboard_acquired.html')
|
||||
|
|
|
@ -107,7 +107,8 @@ class PrivateDatasets(p.SingletonPlugin, tk.DefaultDatasetForm):
|
|||
return {'package_show': auth.package_show,
|
||||
'package_update': auth.package_update,
|
||||
'resource_show': auth.resource_show,
|
||||
constants.PACKAGE_ACQUIRED: auth.package_acquired}
|
||||
constants.PACKAGE_ACQUIRED: auth.package_acquired,
|
||||
constants.ACQUISITIONS_LIST: auth.acquisitions_list}
|
||||
|
||||
######################################################################
|
||||
############################ ICONFIGURER #############################
|
||||
|
@ -138,7 +139,10 @@ class PrivateDatasets(p.SingletonPlugin, tk.DefaultDatasetForm):
|
|||
######################################################################
|
||||
|
||||
def get_actions(self):
|
||||
return {constants.PACKAGE_ACQUIRED: actions.package_acquired}
|
||||
return {
|
||||
constants.PACKAGE_ACQUIRED: actions.package_acquired,
|
||||
constants.ACQUISITIONS_LIST: actions.acquisitions_list
|
||||
}
|
||||
|
||||
######################################################################
|
||||
######################### IPACKAGECONTROLLER #########################
|
||||
|
|
|
@ -43,11 +43,15 @@ class ActionsTest(unittest.TestCase):
|
|||
self._plugins = actions.plugins
|
||||
actions.plugins = MagicMock()
|
||||
|
||||
self._db = actions.db
|
||||
actions.db = MagicMock()
|
||||
|
||||
def tearDown(self):
|
||||
# Unmock
|
||||
actions.config = self._config
|
||||
actions.importlib = self._importlib
|
||||
actions.plugins = self._plugins
|
||||
actions.db = self._db
|
||||
|
||||
@parameterized.expand([
|
||||
('', None, False, False, '%s not configured' % PARSER_CONFIG_PROP),
|
||||
|
@ -201,3 +205,82 @@ class ActionsTest(unittest.TestCase):
|
|||
context_update['ignore_auth'] = True
|
||||
|
||||
package_update.assert_any_call(context_update, {'id': dataset_id, 'allowed_users': expected_allowed_users, 'private': True})
|
||||
|
||||
|
||||
@parameterized.expand([
|
||||
(None, {},),
|
||||
({}, {2: actions.plugins.toolkit.ObjectNotFound},),
|
||||
({'user': 'fiware'}, {1: actions.plugins.toolkit.NotAuthorized},),
|
||||
(None, {1: actions.plugins.toolkit.NotAuthorized, 2: actions.plugins.toolkit.ObjectNotFound},),
|
||||
({}, {}, [1]),
|
||||
({'user': 'fiware'}, {}, [3, 2]),
|
||||
(None, {1: actions.plugins.toolkit.NotAuthorized}, [2]),
|
||||
({}, {1: actions.plugins.toolkit.NotAuthorized, 2: actions.plugins.toolkit.ObjectNotFound}, [1, 3]),
|
||||
])
|
||||
def test_acquisitions_list(self, data_dict, package_errors={}, deleted_packages=[]):
|
||||
|
||||
pkgs_ids = [0, 1, 2, 3]
|
||||
user = 'example_user_test'
|
||||
actions.plugins.toolkit.c.user = user
|
||||
|
||||
# get_action mock
|
||||
default_package = {'pkg_id': 0, 'test': 'ok', 'res': 'ta'}
|
||||
|
||||
def _package_show(context, data_dict):
|
||||
if data_dict['id'] in package_errors:
|
||||
raise package_errors[data_dict['id']]('ERROR')
|
||||
else:
|
||||
pkg = default_package.copy()
|
||||
pkg['pkg_id'] = data_dict['id']
|
||||
pkg['state'] = 'deleted' if data_dict['id'] in deleted_packages else 'active'
|
||||
return pkg
|
||||
|
||||
package_show = MagicMock(side_effect=_package_show)
|
||||
actions.plugins.toolkit.get_action.return_value = package_show
|
||||
|
||||
# query mock
|
||||
query_res = []
|
||||
for i in pkgs_ids:
|
||||
pkg = MagicMock()
|
||||
pkg.package_id = i
|
||||
pkg.user_name = user
|
||||
query_res.append(pkg)
|
||||
|
||||
actions.db.AllowedUser.get = MagicMock(return_value=query_res)
|
||||
|
||||
# Context
|
||||
context = {
|
||||
'model': MagicMock(),
|
||||
'user': 'default_user'
|
||||
}
|
||||
|
||||
# Call the function
|
||||
result = actions.acquisitions_list(context, data_dict)
|
||||
|
||||
# Asset that check_access has been called
|
||||
actions.plugins.toolkit.chec_access(actions.constants.ACQUISITIONS_LIST, context, data_dict)
|
||||
|
||||
# Check that the database has been initialized properly
|
||||
actions.db.init_db.assert_called_once_with(context['model'])
|
||||
|
||||
# Set expected user
|
||||
expected_user = data_dict['user'] if data_dict is not None and 'user' in data_dict else context['user']
|
||||
|
||||
# Query called correctry
|
||||
actions.db.AllowedUser.get.assert_called_once_with(user_name=expected_user)
|
||||
|
||||
# Assert that the package_show has been called properly
|
||||
self.assertEquals(len(pkgs_ids), package_show.call_count)
|
||||
for i in pkgs_ids:
|
||||
package_show.assert_any_call(context, {'id': i})
|
||||
|
||||
# Check that the template receives the correct datasets
|
||||
expected_acquired_datasets = []
|
||||
for i in pkgs_ids:
|
||||
if i not in package_errors and i not in deleted_packages:
|
||||
pkg = default_package.copy()
|
||||
pkg['pkg_id'] = i
|
||||
pkg['state'] = 'deleted' if i in deleted_packages else 'active'
|
||||
expected_acquired_datasets.append(pkg)
|
||||
|
||||
self.assertEquals(expected_acquired_datasets, result)
|
||||
|
|
|
@ -249,4 +249,12 @@ class AuthTest(unittest.TestCase):
|
|||
self.assertEquals(authorized_pkg, result['success'])
|
||||
|
||||
def test_package_acquired(self):
|
||||
self.assertTrue(auth.package_acquired({}, {}))
|
||||
self.assertTrue(auth.package_acquired({}, {})['success'])
|
||||
|
||||
@parameterized.expand([
|
||||
({'user': 'user_1'}, {'user': 'user_1'}, True),
|
||||
({'user': 'user_2'}, {'user': 'user_1'}, False),
|
||||
])
|
||||
def test_acquisitions_list(self, context, data_dict, expected_result):
|
||||
self.assertEquals(expected_result, auth.acquisitions_list(context, data_dict)['success'])
|
||||
|
||||
|
|
|
@ -38,9 +38,6 @@ class UIControllerTest(unittest.TestCase):
|
|||
self._model = controller.model
|
||||
controller.model = MagicMock()
|
||||
|
||||
self._db = controller.db
|
||||
controller.db = MagicMock()
|
||||
|
||||
# Set exceptions
|
||||
controller.plugins.toolkit.ObjectNotFound = self._plugins.toolkit.ObjectNotFound
|
||||
controller.plugins.toolkit.NotAuthorized = self._plugins.toolkit.NotAuthorized
|
||||
|
@ -49,7 +46,6 @@ class UIControllerTest(unittest.TestCase):
|
|||
# Unmock
|
||||
controller.plugins = self._plugins
|
||||
controller.model = self._model
|
||||
controller.db = self._db
|
||||
|
||||
@parameterized.expand([
|
||||
(controller.plugins.toolkit.ObjectNotFound, 404),
|
||||
|
@ -74,62 +70,26 @@ class UIControllerTest(unittest.TestCase):
|
|||
user_show.assert_called_once_with(expected_context, {'user_obj': controller.plugins.toolkit.c.userobj})
|
||||
controller.plugins.toolkit.abort.assert_called_once_with(expected_status, ANY)
|
||||
|
||||
@parameterized.expand([
|
||||
({},),
|
||||
({2: controller.plugins.toolkit.ObjectNotFound},),
|
||||
({1: controller.plugins.toolkit.NotAuthorized},),
|
||||
({1: controller.plugins.toolkit.NotAuthorized, 2: controller.plugins.toolkit.ObjectNotFound},),
|
||||
({}, [1]),
|
||||
({}, [3, 2]),
|
||||
({1: controller.plugins.toolkit.NotAuthorized}, [2]),
|
||||
({1: controller.plugins.toolkit.NotAuthorized, 2: controller.plugins.toolkit.ObjectNotFound}, [1, 3]),
|
||||
])
|
||||
def test_no_error_loading_users(self, package_errors={}, deleted_packages=[]):
|
||||
def test_no_error_loading_users(self):
|
||||
|
||||
pkgs_ids = [0, 1, 2, 3]
|
||||
user = 'example_user_test'
|
||||
controller.plugins.toolkit.c.user = user
|
||||
|
||||
# get_action mock
|
||||
default_package = {'pkg_id': 0, 'test': 'ok', 'res': 'ta'}
|
||||
|
||||
def _package_show(context, data_dict):
|
||||
if data_dict['id'] in package_errors:
|
||||
raise package_errors[data_dict['id']]('ERROR')
|
||||
else:
|
||||
pkg = default_package.copy()
|
||||
pkg['pkg_id'] = data_dict['id']
|
||||
pkg['state'] = 'deleted' if data_dict['id'] in deleted_packages else 'active'
|
||||
return pkg
|
||||
|
||||
user_dict = {'user_name': 'test', 'another_val': 'example value'}
|
||||
package_show = MagicMock(side_effect=_package_show)
|
||||
user_show = MagicMock(return_value=user_dict.copy())
|
||||
|
||||
# actions
|
||||
default_user = {'user_name': 'test', 'another_val': 'example value'}
|
||||
user_show = MagicMock(return_value=default_user)
|
||||
acquisitions_list = MagicMock()
|
||||
def _get_action(action):
|
||||
if action == 'package_show':
|
||||
return package_show
|
||||
elif action == 'user_show':
|
||||
if action == 'user_show':
|
||||
return user_show
|
||||
else:
|
||||
return acquisitions_list
|
||||
|
||||
controller.plugins.toolkit.get_action = MagicMock(side_effect=_get_action)
|
||||
|
||||
# query mock
|
||||
query_res = []
|
||||
for i in pkgs_ids:
|
||||
pkg = MagicMock()
|
||||
pkg.package_id = i
|
||||
pkg.user_name = user
|
||||
query_res.append(pkg)
|
||||
|
||||
controller.db.AllowedUser.get = MagicMock(return_value=query_res)
|
||||
|
||||
# Call the function
|
||||
returned = self.instanceUI.user_acquired_datasets()
|
||||
|
||||
# Check that the database has been initialized properly
|
||||
controller.db.init_db.assert_called_once_with(controller.model)
|
||||
|
||||
# User_show called correctly
|
||||
expected_context = {
|
||||
'model': controller.model,
|
||||
|
@ -140,24 +100,10 @@ class UIControllerTest(unittest.TestCase):
|
|||
user_show.assert_called_once_with(expected_context, {'user_obj': controller.plugins.toolkit.c.userobj})
|
||||
|
||||
# Query called correctry
|
||||
controller.db.AllowedUser.get.assert_called_once_with(user_name=user)
|
||||
|
||||
# Assert that the package_show has been called properly
|
||||
self.assertEquals(len(pkgs_ids), package_show.call_count)
|
||||
for i in pkgs_ids:
|
||||
package_show.assert_any_call(expected_context, {'id': i})
|
||||
|
||||
# Check that the template receives the correct datasets
|
||||
expected_user_dict = user_dict.copy()
|
||||
expected_user_dict['acquired_datasets'] = []
|
||||
for i in pkgs_ids:
|
||||
if i not in package_errors and i not in deleted_packages:
|
||||
pkg = default_package.copy()
|
||||
pkg['pkg_id'] = i
|
||||
pkg['state'] = 'deleted' if i in deleted_packages else 'active'
|
||||
expected_user_dict['acquired_datasets'].append(pkg)
|
||||
|
||||
self.assertEquals(expected_user_dict, controller.plugins.toolkit.c.user_dict)
|
||||
expected_user = default_user.copy()
|
||||
expected_user['acquired_datasets'] = acquisitions_list.return_value
|
||||
acquisitions_list.assert_called_with(expected_context, None)
|
||||
self.assertEquals(expected_user, controller.plugins.toolkit.c.user_dict)
|
||||
|
||||
# Check that the render method has been called and that its result has been returned
|
||||
self.assertEquals(controller.plugins.toolkit.render.return_value, returned)
|
||||
|
|
|
@ -58,10 +58,11 @@ class PluginTest(unittest.TestCase):
|
|||
self.assertTrue(interface.implemented_by(plugin.PrivateDatasets))
|
||||
|
||||
@parameterized.expand([
|
||||
('package_show', plugin.auth.package_show),
|
||||
('package_update', plugin.auth.package_update),
|
||||
('package_show', plugin.auth.package_show),
|
||||
('package_acquired', plugin.auth.package_acquired)
|
||||
('package_show', plugin.auth.package_show),
|
||||
('package_update', plugin.auth.package_update),
|
||||
('package_show', plugin.auth.package_show),
|
||||
('package_acquired', plugin.auth.package_acquired),
|
||||
('acquisitions_list', plugin.auth.acquisitions_list)
|
||||
])
|
||||
def test_auth_function(self, function_name, expected_function):
|
||||
auth_functions = self.privateDatasets.get_auth_functions()
|
||||
|
@ -87,7 +88,8 @@ class PluginTest(unittest.TestCase):
|
|||
action='user_acquired_datasets', conditions=dict(method=['GET']))
|
||||
|
||||
@parameterized.expand([
|
||||
('package_acquired', plugin.actions.package_acquired)
|
||||
('package_acquired', plugin.actions.package_acquired),
|
||||
('acquisitions_list', plugin.actions.acquisitions_list)
|
||||
])
|
||||
def test_actions_function(self, function_name, expected_function):
|
||||
actions = self.privateDatasets.get_actions()
|
||||
|
|
Loading…
Reference in New Issue