# import from third parties from dateutil.parser import parse import json import logging from multiline_formatter.formatter import MultilineMessagesFormatter import os from pylons import config from routes.mapper import SubMapper import sys # imports from ckan from ckan.common import OrderedDict import ckan.lib.helpers as helpers from ckan.lib.plugins import DefaultTranslation, DefaultDatasetForm from ckan.logic.validators import tag_string_convert import ckan.plugins as plugins import ckan.plugins.toolkit as toolkit import ckan.model as model # imports from this extension import ckanext.odsh.helpers as odsh_helpers import ckanext.odsh.helpers_tpsh as helpers_tpsh import ckanext.odsh.helper_pkg_dict as helper_pkg_dict from helper_pkg_dict import HelperPgkDict import ckanext.odsh.logic.action as action import ckanext.odsh.validation as validation import ckanext.odsh.search as search from ckanext.odsh.odsh_logger import OdshLogger import ckanext.odsh.tools as tools log = logging.getLogger(__name__) _ = toolkit._ class OdshPlugin(plugins.SingletonPlugin, DefaultTranslation, DefaultDatasetForm): plugins.implements(plugins.IActions) plugins.implements(plugins.IConfigurer) plugins.implements(plugins.IDatasetForm) plugins.implements(plugins.IFacets) plugins.implements(plugins.IPackageController, inherit=True) plugins.implements(plugins.IRoutes, inherit=True) plugins.implements(plugins.ITemplateHelpers) plugins.implements(plugins.ITranslation) plugins.implements(plugins.IValidators) plugins.implements(plugins.IResourceController, inherit=True) # IActions def get_actions(self): return {'package_create': action.odsh_package_create, 'user_update':action.tpsh_user_update, 'user_create': action.odsh_user_create, 'resource_create': action.odsh_resource_create,} # IConfigurer def update_config(self, config_): toolkit.add_template_directory(config_, 'templates') toolkit.add_public_directory(config_, 'public') toolkit.add_resource('fanstatic', 'odsh') # IDatasetForm def package_types(self): # This plugin doesn't handle any special package types, it just # registers itself as the default (above). return [] def is_fallback(self): # Return True to register this plugin as the default handler for # package types not handled by any other IDatasetForm plugin. return True def create_package_schema(self): schema = super(OdshPlugin, self).create_package_schema() self._update_schema(schema) self._tpsh_update_create_or_update_package_schema(schema) return schema def update_package_schema(self): schema = super(OdshPlugin, self).update_package_schema() self._update_schema(schema) self._tpsh_update_create_or_update_package_schema(schema) return schema def show_package_schema(self): schema = super(OdshPlugin, self).show_package_schema() self._tpsh_update_show_package_schema(schema) return schema def _update_schema(self, schema): for field in ['title', 'license_id']: schema.update({field: [toolkit.get_converter('not_empty')]}) for i, item in enumerate(schema['tags']['name']): if item == toolkit.get_validator('tag_name_validator'): schema['tags']['name'][i] = toolkit.get_validator( 'odsh_tag_name_validator') for i, item in enumerate(schema['tag_string']): if item == tag_string_convert: schema['tag_string'][i] = validation.tag_string_convert schema['resources'].update({ 'url': [toolkit.get_converter('not_empty')], 'format': [toolkit.get_converter('odsh_validate_format')], }) schema['extras'].update({ 'key': [ toolkit.get_converter('known_spatial_uri'), toolkit.get_converter('validate_licenseAttributionByText'), ] }) schema.update( {'__extras': [toolkit.get_converter('odsh_validate_extras')]}) def _tpsh_update_create_or_update_package_schema(self, schema): schema.update({ 'language': [ toolkit.get_validator('ignore_missing'), toolkit.get_converter('convert_to_extras') ], 'thumbnail': [ toolkit.get_validator('ignore_missing'), toolkit.get_converter('convert_to_extras') ], 'relatedPackage': [ toolkit.get_validator('tpsh_validate_relatedPackage'), toolkit.get_converter('convert_to_extras') ], 'accessibility': [ toolkit.get_validator('ignore_missing'), toolkit.get_converter('convert_to_extras') ] }) return schema def _tpsh_update_show_package_schema(self, schema): schema.update({ 'language': [ toolkit.get_converter('convert_from_extras'), toolkit.get_validator('ignore_missing') ], 'thumbnail': [ toolkit.get_converter('convert_from_extras'), toolkit.get_validator('ignore_missing') ], 'relatedPackage': [ toolkit.get_converter('convert_from_extras'), toolkit.get_validator('ignore_missing') ], 'accessibility': [ toolkit.get_converter('convert_from_extras'), toolkit.get_validator('ignore_missing') ], }) return schema # IFacets def dataset_facets(self, facets_dict, package_type): return OrderedDict({'organization': _('Herausgeber'), 'groups': _('Kategorie'), 'res_format': _('Dateiformat'), 'license_title': _('Lizenz'), # 'tags': _('Tags'), 'openness': _('Open-Data-Eigenschaften') }) def group_facets(self, facets_dict, group_type, package_type): return OrderedDict({'organization': _('Herausgeber'), 'res_format': _('Dateiformat'), 'license_title': _('Lizenz'), 'groups': _('Kategorie')}) def organization_facets(self, facets_dict, organization_type, package_type): return OrderedDict({'organization': _('Herausgeber'), 'res_format': _('Dateiformat'), 'license_title': _('Lizenz'), 'groups': _('Kategorie')}) # IPackageController def after_show(self, context, pkg_dict): ''' corrects missing relationships in pkg dict adds the following key-value-pairs to pkg_dict: # key: 'is_new', value: True if the dataset has been created within the last month ''' pkg_dict = helpers_tpsh.correct_missing_relationship( pkg_dict, helpers_tpsh.get_pkg_relationships_from_model(pkg_dict) ) self._update_is_new_in_pkg_dict(pkg_dict) return pkg_dict def before_view(self, pkg_dict): ''' adds the following key-value-pairs to pkg_dict: # key: 'is_new', value: True if the dataset has been created within the last month ''' self._update_is_new_in_pkg_dict(pkg_dict) return pkg_dict def after_create(self, context, resource): if resource.get('package_id'): tools.add_attributes_resources(context, resource) def after_update(self, context, resource): if resource.get('package_id'): tools.add_attributes_resources(context, resource) @staticmethod def _update_is_new_in_pkg_dict(pkg_dict): is_new = HelperPgkDict(pkg_dict).is_package_new() pkg_dict.update({'is_new': is_new}) def before_index(self, dict_pkg): # make special date fields solr conform fields = ["issued", "temporal_start", "temporal_end"] for field in fields: field = 'extras_' + field if field in dict_pkg and dict_pkg[field]: d = parse(dict_pkg[field]) dict_pkg[field] = '{0.year:04d}-{0.month:02d}-{0.day:02d}T00:00:00Z'.format( d) self.map_qa_score(dict_pkg) return dict_pkg # IRoutes def before_map(self, map): map.connect( 'info_page', '/info_page', controller='ckanext.odsh.controller:OdshRouteController', action='info_page' ) map.connect( 'home', '/', controller='ckanext.odsh.controller:OdshRouteController', action='start' ) map.redirect('/dataset/{id}/resource/{resource_id}', '/dataset/{id}') if plugins.toolkit.asbool(config.get('ckanext.dcat.enable_rdf_endpoints', True)): odsh_helpers.odsh_remove_route(map, 'dcat_catalog') map.connect( 'dcat_catalog', config.get( 'ckanext.dcat.catalog_endpoint', '/catalog.{_format}' ), controller='ckanext.odsh.controller:OdshDCATController', action='read_catalog', requirements={'_format': 'xml|rdf|n3|ttl|jsonld'} ) # /api ver 3 or none with matomo GET_POST = dict(method=['GET', 'POST']) with SubMapper( map, controller='ckanext.odsh.controller:OdshApiController', path_prefix='/api{ver:/3|}', ver='/3' ) as m: m.connect('/action/{logic_function}', action='action', conditions=GET_POST) with SubMapper(map, controller='ckanext.odsh.controller:OdshFeedController') as m: m.connect('/feeds/custom.atom', action='custom') with SubMapper(map, controller='ckanext.odsh.controller:OdshPackageController') as m: m.connect('new_view', '/dataset/{id}/resource/{resource_id}/new_view', action='edit_view', ckan_icon='pencil-square-o') with SubMapper(map, controller='ckanext.odsh.controller:OdshGroupController') as m: m.connect('organizations_index', '/organization', action='index') # redirect all user routes to custom controller with SubMapper(map, controller='ckanext.odsh.controller:OdshUserController') as m: m.connect('user_index', '/user', action='index') m.connect('/user/edit', action='edit') m.connect( 'user_edit', '/user/edit/{id:.*}', action='edit', ckan_icon='cog') m.connect('user_delete', '/user/delete/{id}', action='delete') m.connect('/user/reset/{id:.*}', action='perform_reset') m.connect('/user/reset', action='request_reset') m.connect('register', '/user/register', action='register') m.connect('login', '/user/login', action='login') m.connect('/user/_logout', action='logout') m.connect('/user/logged_in', action='logged_in') m.connect('/user/logged_out', action='logged_out') m.connect('/user/logged_out_redirect', action='logged_out_page') m.connect('user_datasets', '/user/{id:(?!(generate_key|activity)).*}', action='read', ckan_icon='sitemap') map.connect( 'comment_datarequest', '/datarequest/new', controller='ckanext.datarequests.controllers.ui_controller:DataRequestsUI', action='new', conditions=dict(method=['GET', 'POST']), ckan_icon='comment' ) map.connect( 'comment_datarequest', '/datarequest/{id}', controller='ckanext.datarequests.controllers.ui_controller:DataRequestsUI', action='comment', conditions=dict(method=['GET', 'POST']), ckan_icon='comment' ) return map def after_map(self, map): return map # ITemplateHelpers def get_helpers(self): # Template helper function names should begin with the name of the # extension they belong to, to avoid clashing with functions from # other extensions. return { 'odsh_main_groups': odsh_helpers.odsh_main_groups, 'odsh_now': odsh_helpers.odsh_now, 'odsh_group_id_selected': odsh_helpers.odsh_group_id_selected, 'odsh_get_facet_items_dict': odsh_helpers.odsh_get_facet_items_dict, 'odsh_openness_score_dataset_html': odsh_helpers.odsh_openness_score_dataset_html, 'odsh_get_resource_details': odsh_helpers.odsh_get_resource_details, 'odsh_get_resource_views': odsh_helpers.odsh_get_resource_views, 'odsh_get_bounding_box': odsh_helpers.odsh_get_bounding_box, 'odsh_get_spatial_text': odsh_helpers.odsh_get_spatial_text, 'odsh_render_datetime': odsh_helpers.odsh_render_datetime, 'odsh_upload_known_formats': odsh_helpers.odsh_upload_known_formats, 'odsh_encodeurl': odsh_helpers.odsh_encodeurl, 'odsh_extract_error': odsh_helpers.odsh_extract_error, 'odsh_extract_error_new': odsh_helpers.odsh_extract_error_new, 'odsh_extract_value_from_extras': odsh_helpers.odsh_extract_value_from_extras, 'odsh_create_checksum': odsh_helpers.odsh_create_checksum, 'presorted_license_options': odsh_helpers.presorted_license_options, 'odsh_tracking_id': odsh_helpers.odsh_tracking_id, 'odsh_tracking_url': odsh_helpers.odsh_tracking_url, 'odsh_has_more_facets': odsh_helpers.odsh_has_more_facets, 'odsh_public_url': odsh_helpers.odsh_public_url, 'odsh_spatial_extends_available': odsh_helpers.spatial_extends_available, 'odsh_public_resource_url': odsh_helpers.odsh_public_resource_url, 'odsh_get_version_id': odsh_helpers.odsh_get_version_id, 'odsh_show_testbanner': odsh_helpers.odsh_show_testbanner, 'odsh_is_slave': odsh_helpers.odsh_is_slave, 'odsh_use_matomo': helpers_tpsh.use_matomo, 'tpsh_get_daterange_prettified': helper_pkg_dict.get_daterange_prettified, 'tpsh_get_language_of_package': helpers_tpsh.get_language_of_package, 'get_language_icon': helpers_tpsh.get_language_icon, 'short_name_for_category': odsh_helpers.short_name_for_category, 'get_spatial_for_selection': helpers_tpsh.get_spatial_for_selection, 'get_subject_for_selection': helpers_tpsh.get_subject_for_selection, 'get_language_for_selection': helpers_tpsh.get_language_for_selection, 'tpsh_get_resource_size': helpers_tpsh.get_resource_size, 'tpsh_get_address_org':helpers_tpsh.get_address_org, 'tpsh_get_body_mail':helpers_tpsh.get_body_mail, } # IValidators def get_validators(self): return validation.get_validators() # Add the custom parameters to Solr's facet queries # use several daterange queries agains temporal_start and temporal_end field # TODO: use field of type date_range in solr index instead def before_search(self, search_params): return search.before_search(search_params) scores = [['0OL'], ['0OL', '1RE'], ['0OL', '1RE', '2OF'], [ '0OL', '1RE', '2OF', '3URI'], ['0OL', '1RE', '2OF', '3URI', '4LD']] def map_qa_score(self, dict_pkg): if 'validated_data_dict' in dict_pkg and 'openness_score' in dict_pkg['validated_data_dict']: d = json.loads(dict_pkg['validated_data_dict']) score = -1 for r in d['resources']: if 'qa' in r: i = r['qa'].find('openness_score') s = int(r['qa'][i+17]) if s > score: score = s if score > 0: dict_pkg['openness'] = OdshPlugin.scores[score-1]