Move the notification callback to an action

This commit is contained in:
Aitor Magán 2014-07-14 11:05:56 +02:00
parent 4bdd121dd9
commit 9a49f06fff
5 changed files with 89 additions and 114 deletions

View File

@ -21,14 +21,12 @@ Since each service can send notifications in a different way, the extension allo
If you want to create your own parser, you have to:
1. Create a class with a method called `parse_notification`
2. Import `request` from `ckan.common` in order to be able to read the notification: `from ckan.common import request`.
3. Parse the notification as you like. You can read the body by accesing `request.body`.
4. Return a dictionary with the following structure. The `errors` field contains the list of errors arised when the notification was parsed while the `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`). If the `error` field is present and it is **not** empty, the `users_datasets` field will **not** be processed.
1. Create a class with a method called `parse_notification`. This method will recieve one argument that will include the notification body.
2. Parse the notification as you like. You can raise a CKAN's default exception (`DataError`, `ValidationError`, `ObjectNotFound`, `NotAuthorized`, `ValidationError`, `SearchError`, `SearchQueryError` or `SearchIndexError`) if you find an error parsing the notification.
3. Return a dictionary with the structure attached below. The `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`).
```
{'errors': ['...', '...', '...']
'users_datasets': [{'user': 'user_name', 'datasets': ['ds1', 'ds2', ...]},
{'users_datasets': [{'user': 'user_name', 'datasets': ['ds1', 'ds2', ...]},
{'user': 'user_name2', 'datasets': ['ds1', 'ds4', ...] }]}
```

View File

@ -0,0 +1,68 @@
import ckan.plugins as plugins
import ckanext.privatedatasets.constants as constants
import importlib
import logging
import pylons.config as config
log = logging.getLogger(__name__)
PARSER_CONFIG_PROP = 'ckan.privatedatasets.parser'
def package_adquired(context, request_data):
log.info('Notification Request received')
# Get the parser from the configuration
class_path = config.get(PARSER_CONFIG_PROP, '')
if class_path != '':
try:
class_package = class_path.split(':')[0]
class_name = class_path.split(':')[1]
parser_cls = getattr(importlib.import_module(class_package), class_name)
parser = parser_cls()
except Exception as e:
raise plugins.toolkit.ValidationError({'message': '%s: %s' % (type(e).__name__, str(e))})
else:
raise plugins.toolkit.ValidationError({'message': '%s not configured' % PARSER_CONFIG_PROP})
# Introduce the users into the datasets
# Expected result: {'errors': ["...", "...", ...]
# 'users_datasets': [{'user': 'user_name', 'datasets': ['ds1', 'ds2', ...]}, ...]}
warns = []
# Parse the result using the parser set in the configuration
result = parser.parse_notification(request_data)
for user_info in result['users_datasets']:
for dataset_id in user_info['datasets']:
try:
dataset = plugins.toolkit.get_action('package_show')({'ignore_auth': True, constants.CONTEXT_CALLBACK: True}, {'id': dataset_id})
# Create the array
if constants.ALLOWED_USERS not in dataset:
dataset[constants.ALLOWED_USERS] = []
# Add the user only if he/she is not in the list
if user_info['user'] not in dataset[constants.ALLOWED_USERS]:
dataset[constants.ALLOWED_USERS].append(user_info['user'])
plugins.toolkit.get_action('package_update')({'ignore_auth': True}, dataset)
else:
log.warn('The user %s is already allowed to access the %s dataset' % (user_info['user'], dataset_id))
except plugins.toolkit.ObjectNotFound:
# If a dataset does not exist in the instance, an error message will be returned to the user.
# However the process won't stop and the process will continue with the remaining datasets.
log.warn('Dataset %s was not found in this instance' % dataset_id)
warns.append('Dataset %s was not found in this instance' % dataset_id)
except plugins.toolkit.ValidationError as e:
# Some datasets does not allow to introduce the list of allowed users since this property is
# only valid for private datasets outside an organization. In this case, a wanr will return
# but the process will continue
warns.append('Dataset %s: %s' % (dataset_id, e.error_dict[constants.ALLOWED_USERS][0]))
# Return warnings that inform about non-existing datasets
if len(warns) > 0:
return {'warns': warns}

View File

@ -1,94 +0,0 @@
import ckan.lib.base as base
import ckan.lib.helpers as helpers
import ckan.model as model
import ckan.plugins as plugins
import ckanext.privatedatasets.constants as constants
import ckanext.privatedatasets.db as db
import importlib
import logging
import pylons.config as config
from ckan.common import response
log = logging.getLogger(__name__)
PARSER_CONFIG_PROP = 'ckan.privatedatasets.parser'
######################################################################
############################ API CONTROLLER ##########################
######################################################################
class AdquiredDatasetsControllerAPI(base.BaseController):
def __call__(self, environ, start_response):
# avoid status_code_redirect intercepting error responses
environ['pylons.status_code_redirect'] = True
return base.BaseController.__call__(self, environ, start_response)
def add_users(self):
log.info('Notification Request received')
if db.package_allowed_users_table is None:
db.init_db(model)
# Get the parser from the configuration
class_path = config.get(PARSER_CONFIG_PROP, '')
if class_path != '':
try:
class_package = class_path.split(':')[0]
class_name = class_path.split(':')[1]
parser = getattr(importlib.import_module(class_package), class_name)
# Parse the result using the parser set in the configuration
result = parser().parse_notification()
except Exception as e:
result = {'errors': [type(e).__name__ + ': ' + str(e)]}
else:
result = {'errors': ['%s not configured' % PARSER_CONFIG_PROP]}
# Introduce the users into the datasets
# Expected result: {'errors': ["...", "...", ...]
# 'users_datasets': [{'user': 'user_name', 'datasets': ['ds1', 'ds2', ...]}, ...]}
warns = []
if 'errors' in result and len(result['errors']) > 0:
log.warn('Errors arised parsing the request: ' + str(result['errors']))
response.status_int = 400
return helpers.json.dumps({'errors': result['errors']})
elif 'users_datasets' in result:
for user_info in result['users_datasets']:
for dataset_id in user_info['datasets']:
try:
dataset = plugins.toolkit.get_action('package_show')({'ignore_auth': True, constants.CONTEXT_CALLBACK: True}, {'id': dataset_id})
# Create the array
if constants.ALLOWED_USERS not in dataset:
dataset[constants.ALLOWED_USERS] = []
# Add the user only if he/she is not in the list
if user_info['user'] not in dataset[constants.ALLOWED_USERS]:
dataset[constants.ALLOWED_USERS].append(user_info['user'])
plugins.toolkit.get_action('package_update')({'ignore_auth': True}, dataset)
else:
log.warn('The user %s is already allowed to access the %s dataset' % (user_info['user'], dataset_id))
except plugins.toolkit.ObjectNotFound:
# If a dataset does not exist in the instance, an error message will be returned to the user.
# However the process won't stop and the process will continue with the remaining datasets.
log.warn('Dataset %s was not found in this instance' % dataset_id)
warns.append('Dataset %s was not found in this instance' % dataset_id)
except plugins.toolkit.ValidationError as e:
# Some datasets does not allow to introduce the list of allowed users since this property is
# only valid for private datasets outside an organization. In this case, a wanr will return
# but the process will continue
warns.append('Dataset %s: %s' % (dataset_id, e.error_dict[constants.ALLOWED_USERS][0]))
# Commit the changes
model.Session.commit()
# Return warnings that inform about non-existing datasets
if len(warns) > 0:
return helpers.json.dumps({'warns': warns})

View File

@ -1,4 +1,4 @@
import ckan.lib.helpers as helpers
import ckan.plugins.toolkit as tk
import re
from urlparse import urlparse
@ -7,16 +7,14 @@ from ckan.common import request
class FiWareNotificationParser(object):
def parse_notification(self):
def parse_notification(self, request_data):
my_host = request.host
# Parse the body
content = helpers.json.loads(request.body, encoding='utf-8')
resources = content['resources']
user_name = content['customer_name']
resources = request_data['resources']
user_name = request_data['customer_name']
datasets = []
errors = []
for resource in resources:
if 'url' in resource:
@ -27,8 +25,7 @@ class FiWareNotificationParser(object):
if parsed_url.netloc == my_host:
datasets.append(dataset_name[0])
else:
errors.append('Dataset %s is associated with the CKAN instance located at %s' % (dataset_name[0], parsed_url.netloc))
raise tk.ValidationError({'message': 'Dataset %s is associated with the CKAN instance located at %s'
% (dataset_name[0], parsed_url.netloc)})
return {'errors': errors,
'users_datasets': [{'user': user_name, 'datasets': datasets}]
}
return {'users_datasets': [{'user': user_name, 'datasets': datasets}]}

View File

@ -1,6 +1,7 @@
import ckan.plugins as p
import ckan.plugins.toolkit as tk
import auth
import actions
import constants
import converters_validators as conv_val
import db
@ -13,6 +14,7 @@ class PrivateDatasets(p.SingletonPlugin, tk.DefaultDatasetForm):
p.implements(p.IAuthFunctions)
p.implements(p.IConfigurer)
p.implements(p.IRoutes, inherit=True)
p.implements(p.IActions)
p.implements(p.IPackageController)
p.implements(p.ITemplateHelpers)
@ -95,20 +97,24 @@ class PrivateDatasets(p.SingletonPlugin, tk.DefaultDatasetForm):
tk.add_resource('fanstatic', 'privatedatasets')
######################################################################
############################### ROUTES ###############################
############################## IROUTES ###############################
######################################################################
def after_map(self, m):
# DataSet adquired notification
m.connect('/dataset_adquired',
controller='ckanext.privatedatasets.controllers.api_controller:AdquiredDatasetsControllerAPI',
action='add_users', conditions=dict(method=['POST']))
m.connect('user_adquired_datasets', '/dashboad/adquired', ckan_icon='shopping-cart',
controller='ckanext.privatedatasets.controllers.ui_controller:AdquiredDatasetsControllerUI',
action='user_adquired_datasets', conditions=dict(method=['GET']))
return m
######################################################################
############################## IACTIONS ##############################
######################################################################
def get_actions(self):
return {'package_adquired': actions.package_adquired}
######################################################################
######################### IPACKAGECONTROLLER #########################
######################################################################