Merge branch 'main' into feature/homepage_restyling
This commit is contained in:
commit
0056bd8dcd
|
@ -57,7 +57,7 @@ class d4SHomeController():
|
||||||
'sort': 'views_recent desc',
|
'sort': 'views_recent desc',
|
||||||
'fq': 'capacity:"public"'
|
'fq': 'capacity:"public"'
|
||||||
}
|
}
|
||||||
query = logic.get_action('package_search')(context, data_dict)
|
query = logic.get_action('package_search')(context, data_dict or {})
|
||||||
c.search_facets = query['search_facets']
|
c.search_facets = query['search_facets']
|
||||||
c.package_count = query['count']
|
c.package_count = query['count']
|
||||||
c.datasets = query['results']
|
c.datasets = query['results']
|
||||||
|
|
|
@ -71,7 +71,7 @@ class d4STypeController():
|
||||||
'sort': 'views_recent desc',
|
'sort': 'views_recent desc',
|
||||||
'fq': 'capacity:"public"'
|
'fq': 'capacity:"public"'
|
||||||
}
|
}
|
||||||
query = logic.get_action('package_search')(context, data_dict)
|
query = logic.get_action('package_search')(context, data_dict or {})
|
||||||
c.search_facets = query['search_facets']
|
c.search_facets = query['search_facets']
|
||||||
c.package_count = query['count']
|
c.package_count = query['count']
|
||||||
c.datasets = query['results']
|
c.datasets = query['results']
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright (c) 2014 CoNWeT Lab., Universidad Politécnica de Madrid
|
||||||
|
|
||||||
|
# This file is part of CKAN Private Dataset Extension.
|
||||||
|
|
||||||
|
# CKAN Private Dataset Extension is free software: you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Affero General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
|
||||||
|
# CKAN Private Dataset Extension is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
# 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/>.
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from logging import getLogger
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
log = getLogger(__name__)
|
||||||
|
|
||||||
|
AllowedUser = None
|
||||||
|
|
||||||
|
def init_db(model):
|
||||||
|
global AllowedUser
|
||||||
|
if AllowedUser is None:
|
||||||
|
|
||||||
|
class _AllowedUser(model.DomainObject):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get(cls, **kw):
|
||||||
|
'''Finds all the instances required.'''
|
||||||
|
query = model.Session.query(cls).autoflush(False)
|
||||||
|
results = query.filter_by(**kw).all()
|
||||||
|
### TODO: capire se questo controllo serve
|
||||||
|
log.debug("results in get %s", results)
|
||||||
|
if not isinstance(results, list):
|
||||||
|
log.debug("Errore: il risultato di get() non è una lista. Risultato:", results)
|
||||||
|
results = []
|
||||||
|
return results
|
||||||
|
|
||||||
|
AllowedUser = _AllowedUser
|
||||||
|
|
||||||
|
log.debug("allowed user: %s", AllowedUser)
|
||||||
|
|
||||||
|
# FIXME: Maybe a default value should not be included...
|
||||||
|
package_allowed_users_table = sa.Table(
|
||||||
|
'package_allowed_users',
|
||||||
|
model.meta.metadata,
|
||||||
|
sa.Column('package_id', sa.types.UnicodeText, primary_key=True, default=u''),
|
||||||
|
sa.Column('user_name', sa.types.UnicodeText, primary_key=True, default=u''),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create the table only if it does not exist
|
||||||
|
package_allowed_users_table.create(checkfirst=True)
|
||||||
|
|
||||||
|
model.meta.mapper(AllowedUser, package_allowed_users_table,)
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* (C) Copyright 2014 CoNWeT Lab., Universidad Politécnica de Madrid
|
||||||
|
*
|
||||||
|
* This file is part of CKAN Private Dataset Extension.
|
||||||
|
*
|
||||||
|
* CKAN Private Dataset Extension is free software: you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* CKAN Private Dataset Extension is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||||
|
* License for more details.
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Dataset allowed_users, searchable and acquire_url toggler
|
||||||
|
* allowed_users, acquire_url and searchable can only be active when a
|
||||||
|
* user attempts to create a private dataset
|
||||||
|
*/
|
||||||
|
|
||||||
|
this.ckan.module('allowed-users', function ($, _) {
|
||||||
|
return {
|
||||||
|
initialize: function() {
|
||||||
|
this.original_acquire_url = $('[name=acquire_url]').val();
|
||||||
|
$('#field-private').on('change', this._onChange);
|
||||||
|
this._onChange(); //Initial
|
||||||
|
},
|
||||||
|
_onChange: function() {
|
||||||
|
var ds_private = $('#field-private').val();
|
||||||
|
|
||||||
|
if (ds_private == 'True') {
|
||||||
|
$('#field-allowed_users_str').prop('disabled', false); //Enable
|
||||||
|
$('#field-acquire_url').prop('disabled', false); //Enable
|
||||||
|
$('#field-searchable').prop('disabled', false); //Enable
|
||||||
|
$('[name=acquire_url]').val(this.original_acquire_url); //Set previous acquire URL
|
||||||
|
} else {
|
||||||
|
$('#field-allowed_users_str').prop('disabled', true); //Disable
|
||||||
|
$('#field-acquire_url').prop('disabled', true); //Disable
|
||||||
|
$('#field-searchable').prop('disabled', true); //Disable
|
||||||
|
|
||||||
|
//Remove previous values
|
||||||
|
$('#field-allowed_users_str').select2('val', '');
|
||||||
|
this.original_acquire_url = $('[name=acquire_url]').val(); //Get previous value
|
||||||
|
$('[name=acquire_url]').val(''); //Acquire URL should be reseted
|
||||||
|
$('#field-searchable').val('True');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
|
@ -25,6 +25,8 @@ import collections
|
||||||
import ckan.plugins.toolkit as tk
|
import ckan.plugins.toolkit as tk
|
||||||
import ckan.logic as logic
|
import ckan.logic as logic
|
||||||
|
|
||||||
|
from ckanext.d4science_theme import db
|
||||||
|
|
||||||
log = getLogger(__name__)
|
log = getLogger(__name__)
|
||||||
|
|
||||||
systemtype_field = 'systemtypefield'
|
systemtype_field = 'systemtypefield'
|
||||||
|
@ -730,3 +732,77 @@ def get_site_statistics() -> dict[str, int]:
|
||||||
'group_count': len(logic.get_action('group_list')({}, {})),
|
'group_count': len(logic.get_action('group_list')({}, {})),
|
||||||
'organization_count': len(logic.get_action('organization_list')({}, {}))
|
'organization_count': len(logic.get_action('organization_list')({}, {}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
####### PRIVATE DATASETS SECTION ########
|
||||||
|
|
||||||
|
def is_dataset_acquired(pkg_dict):
|
||||||
|
|
||||||
|
log.debug("is_dataset package value: %s", pkg_dict) #restituisce True
|
||||||
|
|
||||||
|
db.init_db(model)
|
||||||
|
log.debug("post init %s", db)
|
||||||
|
|
||||||
|
#return False
|
||||||
|
if tk.c.user:
|
||||||
|
return len(db.AllowedUser.get(package_id=pkg_dict['id'], user_name=tk.c.user)) > 0
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def is_owner(pkg_dict):
|
||||||
|
#return False #TODO: debug this
|
||||||
|
if tk.c.userobj is not None:
|
||||||
|
return tk.c.userobj.id == pkg_dict['creator_user_id']
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_allowed_users_str(users):
|
||||||
|
if users:
|
||||||
|
return ','.join([user for user in users])
|
||||||
|
else:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
def can_read(pkg_dict):
|
||||||
|
try:
|
||||||
|
context = {'user': tk.c.user, 'userobj': tk.c.userobj, 'model': model}
|
||||||
|
return tk.check_access('package_show', context, pkg_dict)
|
||||||
|
except tk.NotAuthorized:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_config_bool_value(config_name, default_value=False):
|
||||||
|
env_name = config_name.upper().replace('.', '_')
|
||||||
|
value = os.environ.get(env_name, tk.config.get(config_name, default_value))
|
||||||
|
return value if type(value) == bool else value.strip().lower() in ('true', '1', 'on')
|
||||||
|
|
||||||
|
|
||||||
|
def show_acquire_url_on_create():
|
||||||
|
return get_config_bool_value('ckan.privatedatasets.show_acquire_url_on_create')
|
||||||
|
|
||||||
|
|
||||||
|
def show_acquire_url_on_edit():
|
||||||
|
return get_config_bool_value('ckan.privatedatasets.show_acquire_url_on_edit')
|
||||||
|
|
||||||
|
|
||||||
|
def acquire_button(package):
|
||||||
|
'''
|
||||||
|
Return a Get Access button for the given package id when the dataset has
|
||||||
|
an acquisition URL.
|
||||||
|
|
||||||
|
:param package: the the package to request access when the get access
|
||||||
|
button is clicked
|
||||||
|
:type package: Package
|
||||||
|
|
||||||
|
:returns: a get access button as an HTML snippet
|
||||||
|
:rtype: string
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
if 'acquire_url' in package and request.path.startswith('/dataset')\
|
||||||
|
and package['acquire_url'] != '':
|
||||||
|
url_dest = package['acquire_url']
|
||||||
|
data = {'url_dest': url_dest}
|
||||||
|
return tk.render_snippet('snippets/acquire_button.html', data)
|
||||||
|
else:
|
||||||
|
return ''
|
|
@ -4,26 +4,37 @@ from logging import getLogger
|
||||||
import ckan.plugins as plugins
|
import ckan.plugins as plugins
|
||||||
|
|
||||||
from ckanext.d4science_theme import helpers
|
from ckanext.d4science_theme import helpers
|
||||||
|
from ckanext.d4science_theme import validators
|
||||||
import ckan.plugins.toolkit as toolkit
|
import ckan.plugins.toolkit as toolkit
|
||||||
import ckan.model as model
|
import ckan.model as model
|
||||||
from ckanext.d4science_theme.controllers.home import d4SHomeController
|
from ckanext.d4science_theme.controllers.home import d4SHomeController
|
||||||
from ckanext.d4science_theme.controllers.systemtype import d4STypeController
|
from ckanext.d4science_theme.controllers.systemtype import d4STypeController
|
||||||
from ckanext.d4science_theme.controllers.organization import OrganizationVREController
|
from ckanext.d4science_theme.controllers.organization import OrganizationVREController
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from ckan.config.middleware.common_middleware import TrackingMiddleware
|
||||||
|
import ckan.lib.dictization.model_save as model_save
|
||||||
#from ckan.controllers.home import HomeController
|
#from ckan.controllers.home import HomeController
|
||||||
#from ckan.plugins import IRoutes
|
#from ckan.plugins import IRoutes
|
||||||
from flask import Blueprint, render_template
|
from flask import Blueprint, render_template, Flask, g, redirect, url_for
|
||||||
|
from ckan.types import Context
|
||||||
|
|
||||||
|
from typing import (
|
||||||
|
Any, Collection, Optional, TYPE_CHECKING, Type, Union, cast, overload,
|
||||||
|
Literal,
|
||||||
|
)
|
||||||
|
|
||||||
from ckan.common import (
|
from ckan.common import (
|
||||||
g
|
g
|
||||||
)
|
)
|
||||||
from flask import Flask, g
|
|
||||||
from ckan.lib.app_globals import app_globals
|
|
||||||
import ckan.plugins.toolkit as toolkit
|
|
||||||
|
|
||||||
|
#private datasets imports
|
||||||
|
from ckan.lib.plugins import DefaultPermissionLabels
|
||||||
|
from ckanext.d4science_theme.privatedatasets import auth, actions, constants, converters_validators as conv_val
|
||||||
|
from ckanext.d4science_theme import db
|
||||||
|
from ckanext.d4science_theme.privatedatasets.views import acquired_datasets
|
||||||
|
from ckan.lib import search
|
||||||
|
|
||||||
# Created by Francesco Mangiacrapa
|
HIDDEN_FIELDS = [constants.ALLOWED_USERS, constants.SEARCHABLE]
|
||||||
# francesco.mangiacrapa@isti.cnr.it
|
|
||||||
# ISTI-CNR Pisa (ITALY)
|
|
||||||
|
|
||||||
log = getLogger(__name__)
|
log = getLogger(__name__)
|
||||||
|
|
||||||
|
@ -35,22 +46,17 @@ def remove_check_replicated_custom_key(schema):
|
||||||
|
|
||||||
return schema
|
return schema
|
||||||
|
|
||||||
#CREATED BY FRANCESCO MANGIACRAPA FOR OVERRIDING THE package_extras_save FROM dictization.model_save.py
|
def _package_extras_save(
|
||||||
# Is this needed?
|
extra_dicts: Optional[list[dict[str, Any]]], pkg: 'model.Package',
|
||||||
def _package_extras_save(extra_dicts, obj, context):
|
context: Context) -> None:
|
||||||
''' It can save repeated extras as key-value '''
|
|
||||||
allow_partial_update = context.get("allow_partial_update", False)
|
allow_partial_update = context.get("allow_partial_update", False)
|
||||||
if extra_dicts is None and allow_partial_update:
|
if extra_dicts is None and allow_partial_update:
|
||||||
return
|
return
|
||||||
|
|
||||||
model = context["model"]
|
|
||||||
session = context["session"]
|
session = context["session"]
|
||||||
|
model = context["model"]
|
||||||
#ADDED BY FRANCESCO MANGIACRAPA
|
#extras_list = obj.extras_list
|
||||||
log.debug("extra_dicts: "+ str(extra_dicts))
|
extras_list = session.query(model.PackageExtra).filter_by(package_id=pkg.id).all()
|
||||||
#print "extra_dicts: "+str(extra_dicts)
|
|
||||||
|
|
||||||
extras_list = obj.extras_list
|
|
||||||
#extras = dict((extra.key, extra) for extra in extras_list)
|
#extras = dict((extra.key, extra) for extra in extras_list)
|
||||||
old_extras = {}
|
old_extras = {}
|
||||||
extras = {}
|
extras = {}
|
||||||
|
@ -58,41 +64,27 @@ def _package_extras_save(extra_dicts, obj, context):
|
||||||
old_extras.setdefault(extra.key, []).append(extra.value)
|
old_extras.setdefault(extra.key, []).append(extra.value)
|
||||||
extras.setdefault(extra.key, []).append(extra)
|
extras.setdefault(extra.key, []).append(extra)
|
||||||
|
|
||||||
#ADDED BY FRANCESCO MANGIACRAPA
|
|
||||||
#print "old_extras: "+str(old_extras)
|
|
||||||
|
|
||||||
new_extras = {}
|
new_extras = {}
|
||||||
for extra_dict in extra_dicts or []:
|
for extra_dict in extra_dicts or []:
|
||||||
#print 'extra_dict key: '+extra_dict["key"] + ', value: '+extra_dict["value"]
|
|
||||||
#new_extras.setdefault(extra_dict["key"], []).append(extra_dict["value"])
|
#new_extras.setdefault(extra_dict["key"], []).append(extra_dict["value"])
|
||||||
if extra_dict.get("deleted"):
|
if extra_dict.get("deleted"):
|
||||||
log.debug("extra_dict deleted: "+str(extra_dict["key"]))
|
|
||||||
#print 'extra_dict deleted: '+extra_dict["key"]
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
#if extra_dict['value'] is not None and not extra_dict["value"] == "":
|
#if extra_dict['value'] is not None and not extra_dict["value"] == "":
|
||||||
if extra_dict['value'] is not None:
|
if extra_dict['value'] is not None:
|
||||||
new_extras.setdefault(extra_dict["key"], []).append(extra_dict["value"])
|
new_extras.setdefault(extra_dict["key"], []).append(extra_dict["value"])
|
||||||
|
|
||||||
#ADDED BY FRANCESCO MANGIACRAPA
|
|
||||||
log.debug("new_extras: "+str(new_extras))
|
|
||||||
#print "new_extras: "+str(new_extras)
|
|
||||||
|
|
||||||
#new
|
#new
|
||||||
for key in set(new_extras.keys()) - set(old_extras.keys()):
|
for key in set(new_extras.keys()) - set(old_extras.keys()):
|
||||||
state = 'active'
|
state = 'active'
|
||||||
log.debug("adding key: "+str(key))
|
|
||||||
#print "adding key: "+str(key)
|
|
||||||
extra_lst = new_extras[key]
|
extra_lst = new_extras[key]
|
||||||
for extra in extra_lst:
|
for extra in extra_lst:
|
||||||
extra = model.PackageExtra(state=state, key=key, value=extra)
|
extra = model.PackageExtra(state=state, key=key, value=extra, package_id = pkg.id)
|
||||||
session.add(extra)
|
session.add(extra)
|
||||||
extras_list.append(extra)
|
extras_list.append(extra)
|
||||||
|
|
||||||
#deleted
|
#deleted
|
||||||
for key in set(old_extras.keys()) - set(new_extras.keys()):
|
for key in set(old_extras.keys()) - set(new_extras.keys()):
|
||||||
log.debug("deleting key: "+str(key))
|
|
||||||
#print "deleting key: "+str(key)
|
|
||||||
extra_lst = extras[key]
|
extra_lst = extras[key]
|
||||||
for extra in extra_lst:
|
for extra in extra_lst:
|
||||||
state = 'deleted'
|
state = 'deleted'
|
||||||
|
@ -105,13 +97,9 @@ def _package_extras_save(extra_dicts, obj, context):
|
||||||
for value in new_extras[key]:
|
for value in new_extras[key]:
|
||||||
old_occur = old_extras[key].count(value)
|
old_occur = old_extras[key].count(value)
|
||||||
new_occur = new_extras[key].count(value)
|
new_occur = new_extras[key].count(value)
|
||||||
log.debug("value: "+str(value) + ", new_occur: "+str(new_occur)+ ", old_occur: "+str(old_occur))
|
|
||||||
#print "value: "+str(value) + ", new_occur: "+str(new_occur) + ", old_occur: "+str(old_occur)
|
|
||||||
# it is an old value deleted or not
|
# it is an old value deleted or not
|
||||||
if value in old_extras[key]:
|
if value in old_extras[key]:
|
||||||
if old_occur == new_occur:
|
if old_occur == new_occur:
|
||||||
#print "extra - occurrences of: "+str(value) +", are equal into both list"
|
|
||||||
log.debug("extra - occurrences of: "+str(value) +", are equal into both list")
|
|
||||||
#there is a little bug, this code return always the first element, so I'm fixing with #FIX-STATUS
|
#there is a little bug, this code return always the first element, so I'm fixing with #FIX-STATUS
|
||||||
extra_values = get_package_for_value(extras[key], value)
|
extra_values = get_package_for_value(extras[key], value)
|
||||||
#extras_list.append(extra)
|
#extras_list.append(extra)
|
||||||
|
@ -119,47 +107,32 @@ def _package_extras_save(extra_dicts, obj, context):
|
||||||
state = 'active'
|
state = 'active'
|
||||||
extra.state = state
|
extra.state = state
|
||||||
session.add(extra)
|
session.add(extra)
|
||||||
#print "extra updated: "+str(extra)
|
|
||||||
log.debug("extra updated: "+str(extra))
|
|
||||||
|
|
||||||
elif new_occur > old_occur:
|
elif new_occur > old_occur:
|
||||||
#print "extra - a new occurrence of: "+str(value) +", is present into new list, adding it to old list"
|
|
||||||
log.debug("extra - a new occurrence of: "+str(value) +", is present into new list, adding it to old list")
|
|
||||||
state = 'active'
|
state = 'active'
|
||||||
extra = model.PackageExtra(state=state, key=key, value=value)
|
extra = model.PackageExtra(state=state, key=key, value=value, package_id = pkg.id)
|
||||||
extra.state = state
|
extra.state = state
|
||||||
session.add(extra)
|
session.add(extra)
|
||||||
extras_list.append(extra)
|
extras_list.append(extra)
|
||||||
old_extras[key].append(value)
|
old_extras[key].append(value)
|
||||||
log.debug("old extra values updated: "+str(old_extras[key]))
|
|
||||||
#print "old extra values updated: "+str(old_extras[key])
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
#remove all occurrences deleted - this code could be optimized, it is run several times but could be performed one shot
|
#remove all occurrences deleted - this code could be optimized, it is run several times but could be performed one shot
|
||||||
countDelete = old_occur-new_occur
|
countDelete = old_occur-new_occur
|
||||||
log.debug("extra - occurrence of: "+str(value) +", is not present into new list, removing "+str(countDelete) + " occurrence/s from old list")
|
|
||||||
#print "extra - occurrence of: "+str(value) +", is not present into new list, removing "+str(countDelete)+" occurrence/s from old list"
|
|
||||||
extra_values = get_package_for_value(extras[key], value)
|
extra_values = get_package_for_value(extras[key], value)
|
||||||
for idx, extra in enumerate(extra_values):
|
for idx, extra in enumerate(extra_values):
|
||||||
if idx < countDelete:
|
if idx < countDelete:
|
||||||
#print "extra - occurrence of: "+str(value) +", is not present into new list, removing it from old list"
|
|
||||||
log.debug("pkg extra deleting: "+str(extra.value))
|
|
||||||
#print "pkg extra deleting: "+str(extra.value)
|
|
||||||
state = 'deleted'
|
state = 'deleted'
|
||||||
extra.state = state
|
extra.state = state
|
||||||
|
|
||||||
else:
|
else:
|
||||||
#print "pkg extra reactivating: "+str(extra.value)
|
|
||||||
log.debug("pkg extra reactivating: "+str(extra.value))
|
|
||||||
state = 'active'
|
state = 'active'
|
||||||
extra.state = state
|
extra.state = state
|
||||||
session.add(extra)
|
session.add(extra) #valuta se metterlo dentro il for, ma fuori dall'else
|
||||||
|
|
||||||
else:
|
else:
|
||||||
#print "extra new value: "+str(value)
|
|
||||||
log.debug("extra new value: "+str(value))
|
|
||||||
state = 'active'
|
state = 'active'
|
||||||
extra = model.PackageExtra(state=state, key=key, value=value)
|
extra = model.PackageExtra(state=state, key=key, value=value, package_id = pkg.id)
|
||||||
extra.state = state
|
extra.state = state
|
||||||
session.add(extra)
|
session.add(extra)
|
||||||
extras_list.append(extra)
|
extras_list.append(extra)
|
||||||
|
@ -171,35 +144,60 @@ def _package_extras_save(extra_dicts, obj, context):
|
||||||
if value not in new_extras[key]:
|
if value not in new_extras[key]:
|
||||||
extra_values = get_package_for_value(extras[key], value)
|
extra_values = get_package_for_value(extras[key], value)
|
||||||
for extra in extra_values:
|
for extra in extra_values:
|
||||||
#print "not present extra deleting: "+str(extra)
|
|
||||||
log.debug("not present extra deleting: "+str(extra))
|
|
||||||
state = 'deleted'
|
state = 'deleted'
|
||||||
extra.state = state
|
extra.state = state
|
||||||
|
#add session.delete(extra)?
|
||||||
|
|
||||||
|
|
||||||
#ADDED BY FRANCESCO MANGIACRAPA
|
|
||||||
def get_package_for_value(list_package, value):
|
def get_package_for_value(list_package, value):
|
||||||
''' Returns a list of packages containing the value passed in input
|
'''Returns a list of packages containing the value passed in input'''
|
||||||
'''
|
return [x for x in list_package if x.value == value]
|
||||||
lst = []
|
|
||||||
for x in list_package:
|
#OVERRIDING BASE SQL ALCHEMY ENGINE INSTANCE serve per la connessione con gcube?
|
||||||
if x.value == value:
|
def _init_TrackingMiddleware(self, app, config):
|
||||||
lst.append(x)
|
self.app = app
|
||||||
else:
|
log.debug('TrackingMiddleware d4Science instance')
|
||||||
return lst
|
sqlalchemy_url = config.get('sqlalchemy.url')
|
||||||
|
log.debug('sqlalchemy_url read: '+str(sqlalchemy_url))
|
||||||
|
|
||||||
return lst
|
sqlalchemy_pool = config.get('sqlalchemy.pool_size')
|
||||||
|
if sqlalchemy_pool is None:
|
||||||
|
sqlalchemy_pool = 5
|
||||||
|
|
||||||
|
log.debug('sqlalchemy_pool read: '+str(sqlalchemy_pool))
|
||||||
|
sqlalchemy_overflow = config.get('sqlalchemy.max_overflow')
|
||||||
|
|
||||||
|
if sqlalchemy_overflow is None:
|
||||||
|
sqlalchemy_overflow = 10
|
||||||
|
|
||||||
|
log.debug('sqlalchemy_overflow read: '+str(sqlalchemy_overflow))
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.engine = sa.create_engine(sqlalchemy_url, pool_size=int(sqlalchemy_pool), max_overflow=int(sqlalchemy_overflow))
|
||||||
|
except TypeError as e:
|
||||||
|
log.error('pool size does not work: ' +str(e.args))
|
||||||
|
self.engine = sa.create_engine(sqlalchemy_url)
|
||||||
|
|
||||||
|
|
||||||
|
class D4Science_ThemePlugin(plugins.SingletonPlugin, toolkit.DefaultDatasetForm, DefaultPermissionLabels):
|
||||||
class D4Science_ThemePlugin(plugins.SingletonPlugin, toolkit.DefaultDatasetForm):
|
|
||||||
plugins.implements(plugins.IConfigurer)
|
plugins.implements(plugins.IConfigurer)
|
||||||
plugins.implements(plugins.IDatasetForm)
|
plugins.implements(plugins.IDatasetForm, inherit=True)
|
||||||
plugins.implements(plugins.ITemplateHelpers)
|
plugins.implements(plugins.ITemplateHelpers)
|
||||||
plugins.implements(plugins.IFacets)
|
plugins.implements(plugins.IFacets)
|
||||||
#plugins.implements(IRoutes, inherit=True)
|
#plugins.implements(IRoutes, inherit=True)
|
||||||
|
|
||||||
#ckan 2.10
|
#ckan 2.10
|
||||||
plugins.implements(plugins.IBlueprint)
|
plugins.implements(plugins.IBlueprint)
|
||||||
|
plugins.implements(plugins.IValidators)
|
||||||
|
plugins.implements(plugins.IPackageController, inherit=True)
|
||||||
|
|
||||||
|
#PRIVATE DATASETS SECTION
|
||||||
|
plugins.implements(plugins.IAuthFunctions)
|
||||||
|
#plugins.implements(plugins.IRoutes, inherit=True)
|
||||||
|
plugins.implements(plugins.IActions)
|
||||||
|
plugins.implements(plugins.IPermissionLabels)
|
||||||
|
plugins.implements(plugins.IResourceController)
|
||||||
|
|
||||||
|
|
||||||
# IConfigurer
|
# IConfigurer
|
||||||
def update_config(self, config_):
|
def update_config(self, config_):
|
||||||
|
@ -217,39 +215,151 @@ class D4Science_ThemePlugin(plugins.SingletonPlugin, toolkit.DefaultDatasetForm)
|
||||||
# that we'll use to refer to this fanstatic directory from CKAN
|
# that we'll use to refer to this fanstatic directory from CKAN
|
||||||
# templates.
|
# templates.
|
||||||
toolkit.add_resource('assets', 'd4science_theme')
|
toolkit.add_resource('assets', 'd4science_theme')
|
||||||
# toolkit.add_resource('assets', 'd4science_scripts')
|
|
||||||
|
|
||||||
|
def _modify_package_schema(self):
|
||||||
|
# Personalizza il campo 'extras' rimuovendo il validatore extra_key_not_in_root_schema
|
||||||
|
|
||||||
|
return {
|
||||||
|
#private datasets
|
||||||
|
'private': [toolkit.get_validator('ignore_missing'),
|
||||||
|
toolkit.get_validator('boolean_validator')],
|
||||||
|
constants.ALLOWED_USERS_STR: [toolkit.get_validator('ignore_missing'),
|
||||||
|
conv_val.private_datasets_metadata_checker],
|
||||||
|
constants.ALLOWED_USERS: [conv_val.allowed_users_convert,
|
||||||
|
toolkit.get_validator('ignore_missing'),
|
||||||
|
conv_val.private_datasets_metadata_checker],
|
||||||
|
constants.ACQUIRE_URL: [toolkit.get_validator('ignore_missing'),
|
||||||
|
conv_val.private_datasets_metadata_checker,
|
||||||
|
conv_val.url_checker,
|
||||||
|
toolkit.get_converter('convert_to_extras')],
|
||||||
|
constants.SEARCHABLE: [toolkit.get_validator('ignore_missing'),
|
||||||
|
conv_val.private_datasets_metadata_checker,
|
||||||
|
toolkit.get_converter('convert_to_extras'),
|
||||||
|
toolkit.get_validator('boolean_validator')],
|
||||||
|
#d4science_theme
|
||||||
|
'extras': {
|
||||||
|
'id': [toolkit.get_validator('ignore')], # Ignora 'id' come prima
|
||||||
|
'key': [toolkit.get_validator('not_empty'), toolkit.get_validator('unicode_safe'), validators.ignore_duplicate_keys], # Aggiunto ignore duplicate
|
||||||
|
'value': [toolkit.get_validator('not_missing')]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#IDatasetForm
|
#IDatasetForm
|
||||||
def create_package_schema(self):
|
def create_package_schema(self):
|
||||||
# let's grab the default schema in our plugin
|
# let's grab the default schema in our plugin
|
||||||
|
log.debug("creating package....")
|
||||||
schema = super(D4Science_ThemePlugin, self).create_package_schema()
|
schema = super(D4Science_ThemePlugin, self).create_package_schema()
|
||||||
|
#log.debug("schema after create prima del validator %s", schema)
|
||||||
schema = remove_check_replicated_custom_key(schema)
|
schema = remove_check_replicated_custom_key(schema)
|
||||||
|
#log.debug("create_package1 (remove __before): %s", schema)
|
||||||
|
schema.update(self._modify_package_schema())
|
||||||
#d.package_dict_save = _package_dict_save
|
#d.package_dict_save = _package_dict_save
|
||||||
|
#log.debug("create_package2 (remove extras validator): %s", schema)
|
||||||
return schema
|
return schema
|
||||||
|
|
||||||
#IDatasetForm
|
#IDatasetForm
|
||||||
def update_package_schema(self):
|
def update_package_schema(self):
|
||||||
|
log.debug("** update_package **")
|
||||||
schema = super(D4Science_ThemePlugin, self).update_package_schema()
|
schema = super(D4Science_ThemePlugin, self).update_package_schema()
|
||||||
schema = remove_check_replicated_custom_key(schema)
|
schema = remove_check_replicated_custom_key(schema)
|
||||||
|
#log.debug("update_package1 (remove __before) %s", schema)
|
||||||
|
schema.update(self._modify_package_schema())
|
||||||
|
#log.debug("update_package2 (remove extras validator) %s", schema)
|
||||||
return schema
|
return schema
|
||||||
|
|
||||||
#IDatasetForm
|
#IDatasetForm
|
||||||
def show_package_schema(self):
|
def show_package_schema(self):
|
||||||
|
log.debug("** show package **")
|
||||||
schema = super(D4Science_ThemePlugin, self).show_package_schema()
|
schema = super(D4Science_ThemePlugin, self).show_package_schema()
|
||||||
|
#log.debug("show_package1 %s", schema)
|
||||||
schema = remove_check_replicated_custom_key(schema)
|
schema = remove_check_replicated_custom_key(schema)
|
||||||
|
#log.debug("show_package1.5 no before %s", schema)
|
||||||
|
schema.update({
|
||||||
|
#private datasets
|
||||||
|
constants.ALLOWED_USERS: [conv_val.get_allowed_users,
|
||||||
|
toolkit.get_validator('ignore_missing')],
|
||||||
|
constants.ACQUIRE_URL: [toolkit.get_converter('convert_from_extras'),
|
||||||
|
toolkit.get_validator('ignore_missing')],
|
||||||
|
constants.SEARCHABLE: [toolkit.get_converter('convert_from_extras'),
|
||||||
|
toolkit.get_validator('ignore_missing')],
|
||||||
|
#d4science_theme
|
||||||
|
'extras': {
|
||||||
|
'id': [toolkit.get_validator('ignore')], # Ignora 'id' come prima
|
||||||
|
'key': [toolkit.get_validator('not_empty'), toolkit.get_validator('unicode_safe'), validators.ignore_duplicate_keys], # Aggiunto ignore duplicate
|
||||||
|
'value': [toolkit.get_validator('not_missing')]
|
||||||
|
}
|
||||||
|
})
|
||||||
return schema
|
return schema
|
||||||
|
|
||||||
|
|
||||||
#IDatasetForm
|
#IDatasetForm
|
||||||
def is_fallback(self):
|
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 to register this plugin as the default handler for package types not handled by any other IDatasetForm plugin
|
||||||
return False
|
return True
|
||||||
|
|
||||||
#IDatasetForm
|
#IDatasetForm
|
||||||
def package_types(self):
|
def package_types(self):
|
||||||
# This plugin doesn't handle any special package types, it just
|
# This plugin doesn't handle any special package types, it just
|
||||||
# registers itself as the default (above).
|
# registers itself as the default (above).
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
#IDatasetForm
|
||||||
|
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
|
||||||
|
|
||||||
|
#IDatasetForm
|
||||||
|
def package_types(self):
|
||||||
|
# This plugin doesn't handle any special package types, it just
|
||||||
|
# registers itself as the default (above).
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_validators(self):
|
||||||
|
return {
|
||||||
|
'ignore_duplicate_keys': validators.ignore_duplicate_keys
|
||||||
|
}
|
||||||
|
|
||||||
|
def convert_to_boolean(self, value):
|
||||||
|
log.debug("value boolean %s", value)
|
||||||
|
if isinstance(value, str):
|
||||||
|
if value.lower() == 'true':
|
||||||
|
return True
|
||||||
|
elif value.lower() == 'false':
|
||||||
|
return False
|
||||||
|
return value
|
||||||
|
|
||||||
|
#IValidator
|
||||||
|
def validate(self, context, data_dict, schema, action):
|
||||||
|
# Modifica il comportamento del validatore per ignorare le chiavi duplicate
|
||||||
|
# Assicurati che 'private' sia un valore booleano
|
||||||
|
log.debug("calling, validate, questo è lo schema: %s", schema)
|
||||||
|
data_dict['private'] = self.convert_to_boolean(data_dict.get('private', False))
|
||||||
|
errors = []
|
||||||
|
#todo change this function
|
||||||
|
if 'extras' in data_dict:
|
||||||
|
log.debug("extras presente")
|
||||||
|
extras = data_dict['extras']
|
||||||
|
new_extras = []
|
||||||
|
log.debug("extras value before for: %s e lunghezza: %s", extras, len(extras))
|
||||||
|
# Aggiungi ogni coppia chiave-valore alla nuova lista, mantenendo i duplicati
|
||||||
|
for extra in extras:
|
||||||
|
new_extras.append(extra)
|
||||||
|
log.debug("Aggiunta extra con chiave duplicata: %s -> %s", extra['key'], extra['value'])
|
||||||
|
|
||||||
|
# Aggiorna il data_dict con la lista di extras che include i duplicati
|
||||||
|
data_dict['extras'] = new_extras
|
||||||
|
log.debug("new extras mantenendo i duplicati: %s", new_extras)
|
||||||
|
log.debug("pre return validate %s", data_dict)
|
||||||
|
return data_dict, errors
|
||||||
|
|
||||||
|
def apply_custom_extras_validator(self, data_dict):
|
||||||
|
"""
|
||||||
|
Funzione helper per applicare il validator agli extras
|
||||||
|
"""
|
||||||
|
extras = data_dict.get('extras', [])
|
||||||
|
# Esegui il validator per assicurare il salvataggio dei duplicati
|
||||||
|
validators.ignore_duplicate_keys('extras', data_dict, [], {})
|
||||||
|
|
||||||
#ITemplateHelpers
|
#ITemplateHelpers
|
||||||
def get_helpers(self):
|
def get_helpers(self):
|
||||||
|
@ -290,11 +400,19 @@ class D4Science_ThemePlugin(plugins.SingletonPlugin, toolkit.DefaultDatasetForm)
|
||||||
'd4science_get_location_to_bboxes' : helpers.get_location_to_bboxes,
|
'd4science_get_location_to_bboxes' : helpers.get_location_to_bboxes,
|
||||||
'd4science_get_content_moderator_system_placeholder': helpers.get_content_moderator_system_placeholder,
|
'd4science_get_content_moderator_system_placeholder': helpers.get_content_moderator_system_placeholder,
|
||||||
'd4science_get_site_statistics': helpers.get_site_statistics,
|
'd4science_get_site_statistics': helpers.get_site_statistics,
|
||||||
|
#privatedatasets section
|
||||||
|
'is_dataset_acquired': helpers.is_dataset_acquired,
|
||||||
|
'get_allowed_users_str': helpers.get_allowed_users_str,
|
||||||
|
'is_owner': helpers.is_owner,
|
||||||
|
'can_read': helpers.can_read,
|
||||||
|
'show_acquire_url_on_create': helpers.show_acquire_url_on_create,
|
||||||
|
'show_acquire_url_on_edit': helpers.show_acquire_url_on_edit,
|
||||||
|
'acquire_button': helpers.acquire_button
|
||||||
}
|
}
|
||||||
|
|
||||||
#Overriding package_extras_save method
|
#Overriding package_extras_save method
|
||||||
# Is this needed?
|
# Is this needed?
|
||||||
# model_save.package_extras_save = _package_extras_save
|
model_save.package_extras_save = _package_extras_save
|
||||||
|
|
||||||
#Overriding index home controller - rimosso in ckan 2.10
|
#Overriding index home controller - rimosso in ckan 2.10
|
||||||
#d4sHC = d4SHomeController()
|
#d4sHC = d4SHomeController()
|
||||||
|
@ -302,6 +420,9 @@ class D4Science_ThemePlugin(plugins.SingletonPlugin, toolkit.DefaultDatasetForm)
|
||||||
|
|
||||||
global d4s_ctg_namespaces_controller
|
global d4s_ctg_namespaces_controller
|
||||||
|
|
||||||
|
#OVERRIDING BASE SQL ALCHEMY ENGINE INSTANCE
|
||||||
|
TrackingMiddleware.__init__ = _init_TrackingMiddleware
|
||||||
|
|
||||||
#if d4s_ctg_namespaces_controller is None:
|
#if d4s_ctg_namespaces_controller is None:
|
||||||
# log.info("d4s_ctg_namespaces_controller instancing...")
|
# log.info("d4s_ctg_namespaces_controller instancing...")
|
||||||
# d4s_ctg_namespaces_controller = helpers.get_d4s_namespace_controller()
|
# d4s_ctg_namespaces_controller = helpers.get_d4s_namespace_controller()
|
||||||
|
@ -391,6 +512,7 @@ class D4Science_ThemePlugin(plugins.SingletonPlugin, toolkit.DefaultDatasetForm)
|
||||||
('/organization_vre', 'organization_vre', d4sOC.index),
|
('/organization_vre', 'organization_vre', d4sOC.index),
|
||||||
('/tags', 'tags', tags),
|
('/tags', 'tags', tags),
|
||||||
('/groups', 'groups', groups),
|
('/groups', 'groups', groups),
|
||||||
|
('/dashboard/acquired', 'acquired_datasets', acquired_datasets) #privatedatasets rule
|
||||||
]
|
]
|
||||||
for rule in rules:
|
for rule in rules:
|
||||||
blueprint.add_url_rule(*rule)
|
blueprint.add_url_rule(*rule)
|
||||||
|
@ -437,4 +559,249 @@ class D4Science_ThemePlugin(plugins.SingletonPlugin, toolkit.DefaultDatasetForm)
|
||||||
|
|
||||||
return facets_dict
|
return facets_dict
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
######################## PRIVATEDATASET FORM #########################
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
def __init__(self, name=None):
|
||||||
|
self.indexer = search.PackageSearchIndex()
|
||||||
|
|
||||||
|
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 package_types(self):
|
||||||
|
# This plugin doesn't handle any special package types, it just
|
||||||
|
# registers itself as the default (above).
|
||||||
|
return []
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
########################### AUTH FUNCTIONS ###########################
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
def get_auth_functions(self):
|
||||||
|
auth_functions = {'package_show': auth.package_show,
|
||||||
|
'package_update': auth.package_update,
|
||||||
|
'resource_show': auth.resource_show,
|
||||||
|
constants.PACKAGE_ACQUIRED: auth.package_acquired,
|
||||||
|
constants.ACQUISITIONS_LIST: auth.acquisitions_list,
|
||||||
|
constants.PACKAGE_DELETED: auth.revoke_access}
|
||||||
|
|
||||||
|
return auth_functions
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
############################## IACTIONS ##############################
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
def get_actions(self):
|
||||||
|
action_functions = {constants.PACKAGE_ACQUIRED: actions.package_acquired,
|
||||||
|
constants.ACQUISITIONS_LIST: actions.acquisitions_list,
|
||||||
|
constants.PACKAGE_DELETED: actions.revoke_access}
|
||||||
|
|
||||||
|
return action_functions
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
######################### IPACKAGECONTROLLER #########################
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
def _delete_pkg_atts(self, pkg_dict, attrs):
|
||||||
|
for attr in attrs:
|
||||||
|
if attr in pkg_dict:
|
||||||
|
del pkg_dict[attr]
|
||||||
|
|
||||||
|
def before_dataset_index(self, pkg_dict):
|
||||||
|
|
||||||
|
if 'extras_' + constants.SEARCHABLE in pkg_dict:
|
||||||
|
if pkg_dict['extras_searchable'] == 'False':
|
||||||
|
pkg_dict['capacity'] = 'private'
|
||||||
|
else:
|
||||||
|
pkg_dict['capacity'] = 'public'
|
||||||
|
|
||||||
|
return pkg_dict
|
||||||
|
|
||||||
|
def after_dataset_create(self, context, pkg_dict):
|
||||||
|
session = context['session']
|
||||||
|
update_cache = False
|
||||||
|
|
||||||
|
db.init_db(context['model'])
|
||||||
|
|
||||||
|
# Get the users and the package ID
|
||||||
|
if constants.ALLOWED_USERS in pkg_dict:
|
||||||
|
|
||||||
|
allowed_users = pkg_dict[constants.ALLOWED_USERS]
|
||||||
|
package_id = pkg_dict['id']
|
||||||
|
|
||||||
|
# Get current users
|
||||||
|
users = db.AllowedUser.get(package_id=package_id)
|
||||||
|
|
||||||
|
# Delete users and save the list of current users
|
||||||
|
current_users = []
|
||||||
|
for user in users:
|
||||||
|
current_users.append(user.user_name)
|
||||||
|
if user.user_name not in allowed_users:
|
||||||
|
session.delete(user)
|
||||||
|
update_cache = True
|
||||||
|
|
||||||
|
# Add non existing users
|
||||||
|
for user_name in allowed_users:
|
||||||
|
if user_name not in current_users:
|
||||||
|
out = db.AllowedUser()
|
||||||
|
out.package_id = package_id
|
||||||
|
out.user_name = user_name
|
||||||
|
out.save()
|
||||||
|
session.add(out)
|
||||||
|
update_cache = True
|
||||||
|
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
# The cache should be updated. Otherwise, the system may return
|
||||||
|
# outdated information in future requests
|
||||||
|
if update_cache:
|
||||||
|
new_pkg_dict = toolkit.get_action('package_show')(
|
||||||
|
{'model': context['model'],
|
||||||
|
'ignore_auth': True,
|
||||||
|
'validate': False,
|
||||||
|
'use_cache': False},
|
||||||
|
{'id': package_id})
|
||||||
|
|
||||||
|
# Prevent acquired datasets jumping to the first position
|
||||||
|
revision = toolkit.get_action('revision_show')({'ignore_auth': True}, {'id': new_pkg_dict['revision_id']})
|
||||||
|
new_pkg_dict['metadata_modified'] = revision.get('timestamp', '')
|
||||||
|
self.indexer.update_dict(new_pkg_dict)
|
||||||
|
|
||||||
|
return pkg_dict
|
||||||
|
|
||||||
|
def after_dataset_update(self, context, pkg_dict):
|
||||||
|
return self.after_dataset_create(context, pkg_dict)
|
||||||
|
|
||||||
|
def after_dataset_show(self, context, pkg_dict):
|
||||||
|
|
||||||
|
void = False
|
||||||
|
|
||||||
|
for resource in pkg_dict['resources']:
|
||||||
|
if resource == {}:
|
||||||
|
void = True
|
||||||
|
|
||||||
|
if void:
|
||||||
|
del pkg_dict['resources']
|
||||||
|
del pkg_dict['num_resources']
|
||||||
|
|
||||||
|
user_obj = context.get('auth_user_obj')
|
||||||
|
updating_via_api = context.get(constants.CONTEXT_CALLBACK, False)
|
||||||
|
|
||||||
|
# allowed_users and searchable fileds can be only viewed by (and only if the dataset is private):
|
||||||
|
# * the dataset creator
|
||||||
|
# * the sysadmin
|
||||||
|
# * users allowed to update the allowed_users list via the notification API
|
||||||
|
if pkg_dict.get('private') is False or not updating_via_api and (not user_obj or (pkg_dict['creator_user_id'] != user_obj.id and not user_obj.sysadmin)):
|
||||||
|
# The original list cannot be modified
|
||||||
|
attrs = list(HIDDEN_FIELDS)
|
||||||
|
self._delete_pkg_atts(pkg_dict, attrs)
|
||||||
|
|
||||||
|
return pkg_dict
|
||||||
|
|
||||||
|
def after_dataset_delete(self, context, pkg_dict):
|
||||||
|
session = context['session']
|
||||||
|
package_id = pkg_dict['id']
|
||||||
|
|
||||||
|
# Get current users
|
||||||
|
db.init_db(context['model'])
|
||||||
|
users = db.AllowedUser.get(package_id=package_id)
|
||||||
|
|
||||||
|
# Delete all the users
|
||||||
|
for user in users:
|
||||||
|
session.delete(user)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
return pkg_dict
|
||||||
|
|
||||||
|
def after_dataset_search(self, search_results, search_params):
|
||||||
|
for result in search_results['results']:
|
||||||
|
# Extra fields should not be returned
|
||||||
|
# The original list cannot be modified
|
||||||
|
attrs = list(HIDDEN_FIELDS)
|
||||||
|
|
||||||
|
# Additionally, resources should not be included if the user is not allowed
|
||||||
|
# to show the resource
|
||||||
|
context = {
|
||||||
|
'model': model,
|
||||||
|
'session': model.Session,
|
||||||
|
'user': toolkit.c.user,
|
||||||
|
'user_obj': toolkit.c.userobj
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
toolkit.check_access('package_show', context, result)
|
||||||
|
except toolkit.NotAuthorized:
|
||||||
|
# NotAuthorized exception is risen when the user is not allowed
|
||||||
|
# to read the package.
|
||||||
|
attrs.append('resources')
|
||||||
|
# Delete
|
||||||
|
self._delete_pkg_atts(result, attrs)
|
||||||
|
|
||||||
|
return search_results
|
||||||
|
|
||||||
|
def before_dataset_view(self, pkg_dict):
|
||||||
|
|
||||||
|
for resource in pkg_dict['resources']:
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'model': model,
|
||||||
|
'session': model.Session,
|
||||||
|
'user': toolkit.c.user,
|
||||||
|
'user_obj': toolkit.c.userobj
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
toolkit.check_access('resource_show', context, resource)
|
||||||
|
except toolkit.NotAuthorized:
|
||||||
|
pkg_dict['resources'].remove(resource)
|
||||||
|
pkg_dict = self.before_view(pkg_dict)
|
||||||
|
return pkg_dict
|
||||||
|
|
||||||
|
def get_dataset_labels(self, dataset_obj):
|
||||||
|
labels = super(D4Science_ThemePlugin, self).get_dataset_labels(
|
||||||
|
dataset_obj)
|
||||||
|
|
||||||
|
if getattr(dataset_obj, 'searchable', False):
|
||||||
|
labels.append('searchable')
|
||||||
|
|
||||||
|
return labels
|
||||||
|
|
||||||
|
def get_user_dataset_labels(self, user_obj):
|
||||||
|
labels = super(D4Science_ThemePlugin, self).get_user_dataset_labels(
|
||||||
|
user_obj)
|
||||||
|
|
||||||
|
labels.append('searchable')
|
||||||
|
return labels
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
######################### IRESOURCECONTROLLER ########################
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
def before_resource_create(self, context, resource):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def after_resource_create(self, context, resource):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def before_resource_update(self, context, current, resource):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def before_resource_delete(self, context, resource, resources):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def before_resource_show(self, resource_dict):
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'model': model,
|
||||||
|
'session': model.Session,
|
||||||
|
'user': toolkit.c.user,
|
||||||
|
'user_obj': toolkit.c.userobj
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
toolkit.check_access('resource_show', context, resource_dict)
|
||||||
|
except toolkit.NotAuthorized:
|
||||||
|
resource_dict.clear()
|
||||||
|
return resource_dict
|
|
@ -0,0 +1,239 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright (c) 2014 - 2017 CoNWeT Lab., Universidad Politécnica de Madrid
|
||||||
|
# Copyright (c) 2018 Future Internet Consulting and Development Solutions S.L.
|
||||||
|
|
||||||
|
# This file is part of CKAN Private Dataset Extension.
|
||||||
|
|
||||||
|
# CKAN Private Dataset Extension is free software: you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Affero General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
|
||||||
|
# CKAN Private Dataset Extension is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
# 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/>.
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import importlib
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
import ckan.plugins as plugins
|
||||||
|
|
||||||
|
from ckanext.d4science_theme.privatedatasets import constants
|
||||||
|
from ckanext.d4science_theme import db
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
PARSER_CONFIG_PROP = 'ckan.d4science_theme.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
|
||||||
|
|
||||||
|
'''
|
||||||
|
context['method'] = 'grant'
|
||||||
|
return _process_package(context, request_data)
|
||||||
|
|
||||||
|
|
||||||
|
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 = 'package_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
|
||||||
|
|
||||||
|
|
||||||
|
def revoke_access(context, request_data):
|
||||||
|
'''
|
||||||
|
API action to be called in order to revoke access grants of an user.
|
||||||
|
|
||||||
|
This API should be called to delete the user from 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
|
||||||
|
|
||||||
|
'''
|
||||||
|
context['method'] = 'revoke'
|
||||||
|
return _process_package(context, request_data)
|
||||||
|
|
||||||
|
|
||||||
|
def _process_package(context, request_data):
|
||||||
|
log.info('Notification received: %s' % request_data)
|
||||||
|
|
||||||
|
# Check access
|
||||||
|
method = constants.PACKAGE_ACQUIRED if context.get('method') == 'grant' else constants.PACKAGE_DELETED
|
||||||
|
plugins.toolkit.check_access(method, context, request_data)
|
||||||
|
|
||||||
|
# Get the parser from the configuration
|
||||||
|
class_path = os.environ.get(PARSER_CONFIG_PROP.upper().replace('.', '_'), plugins.toolkit.config.get(PARSER_CONFIG_PROP, ''))
|
||||||
|
|
||||||
|
if class_path != '':
|
||||||
|
try:
|
||||||
|
cls = class_path.split(':')
|
||||||
|
class_package = cls[0]
|
||||||
|
class_name = cls[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})
|
||||||
|
|
||||||
|
# Parse the result using the parser set in the configuration
|
||||||
|
# Expected result: {'errors': ["...", "...", ...]
|
||||||
|
# 'users_datasets': [{'user': 'user_name', 'datasets': ['ds1', 'ds2', ...]}, ...]}
|
||||||
|
result = parser.parse_notification(request_data)
|
||||||
|
|
||||||
|
warns = []
|
||||||
|
|
||||||
|
for user_info in result['users_datasets']:
|
||||||
|
for dataset_id in user_info['datasets']:
|
||||||
|
|
||||||
|
try:
|
||||||
|
context_pkg_show = context.copy()
|
||||||
|
context_pkg_show['ignore_auth'] = True
|
||||||
|
context_pkg_show[constants.CONTEXT_CALLBACK] = True
|
||||||
|
dataset = plugins.toolkit.get_action('package_show')(context_pkg_show, {'id': dataset_id})
|
||||||
|
|
||||||
|
# This operation can only be performed with private datasets
|
||||||
|
# This check is redundant since the package_update function will throw an exception
|
||||||
|
# if a list of allowed users is included in a public dataset. However, this check
|
||||||
|
# should be performed in order to avoid strange future exceptions
|
||||||
|
if dataset.get('private', None) is True:
|
||||||
|
|
||||||
|
# Create the array if it does not exist
|
||||||
|
if constants.ALLOWED_USERS not in dataset or dataset[constants.ALLOWED_USERS] is None:
|
||||||
|
dataset[constants.ALLOWED_USERS] = []
|
||||||
|
|
||||||
|
method = context['method'] == 'grant'
|
||||||
|
present = user_info['user'] in dataset[constants.ALLOWED_USERS]
|
||||||
|
# Deletes the user only if it is in the list
|
||||||
|
if (not method and present) or (method and not present):
|
||||||
|
if method:
|
||||||
|
dataset[constants.ALLOWED_USERS].append(user_info['user'])
|
||||||
|
else:
|
||||||
|
dataset[constants.ALLOWED_USERS].remove(user_info['user'])
|
||||||
|
|
||||||
|
context_pkg_update = context.copy()
|
||||||
|
context_pkg_update['ignore_auth'] = True
|
||||||
|
|
||||||
|
# Set creator as the user who is performing the changes
|
||||||
|
user_show = plugins.toolkit.get_action('user_show')
|
||||||
|
creator_user_id = dataset.get('creator_user_id', '')
|
||||||
|
user_show_context = {'ignore_auth': True}
|
||||||
|
user = user_show(user_show_context, {'id': creator_user_id})
|
||||||
|
context_pkg_update['user'] = user.get('name', '')
|
||||||
|
|
||||||
|
plugins.toolkit.get_action('package_update')(context_pkg_update, dataset)
|
||||||
|
log.info('Action %s access to dataset ended successfully' % context['method'])
|
||||||
|
else:
|
||||||
|
log.debug('Action %s access to dataset not completed. The dataset %s already %s access to the user %s' % (context['method'], dataset_id, context['method'], user_info['user']))
|
||||||
|
else:
|
||||||
|
log.debug('Dataset %s is public. Cannot %s access to users' % (dataset_id, context['method']))
|
||||||
|
warns.append('Unable to upload the dataset %s: It\'s a public dataset' % 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.debug('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
|
||||||
|
# WARN: This exception should not be risen anymore since public datasets are not updated.
|
||||||
|
message = '%s(%s): %s' % (dataset_id, constants.ALLOWED_USERS, e.error_dict[constants.ALLOWED_USERS][0])
|
||||||
|
log.debug(message)
|
||||||
|
warns.append(message)
|
||||||
|
|
||||||
|
# Return warnings that inform about non-existing datasets
|
||||||
|
if len(warns) > 0:
|
||||||
|
return {'warns': warns}
|
|
@ -0,0 +1,130 @@
|
||||||
|
import ckan.lib.helpers as helpers
|
||||||
|
import ckan.logic.auth as logic_auth
|
||||||
|
import ckan.plugins.toolkit as tk
|
||||||
|
try:
|
||||||
|
import ckan.authz as authz
|
||||||
|
except ImportError:
|
||||||
|
import ckan.new_authz as authz
|
||||||
|
|
||||||
|
import ckanext.d4science_theme.db as db
|
||||||
|
|
||||||
|
from ckan.common import _, request
|
||||||
|
|
||||||
|
|
||||||
|
@tk.auth_allow_anonymous_access
|
||||||
|
def package_show(context, data_dict):
|
||||||
|
user = context.get('user')
|
||||||
|
user_obj = context.get('auth_user_obj')
|
||||||
|
package = logic_auth.get_package_object(context, data_dict)
|
||||||
|
|
||||||
|
# datasets can be read by its creator
|
||||||
|
if package and user_obj and package.creator_user_id == user_obj.id:
|
||||||
|
return {'success': True}
|
||||||
|
|
||||||
|
# Not active packages can only be seen by its owners
|
||||||
|
if package.state == 'active':
|
||||||
|
# anyone can see a public package
|
||||||
|
if not package.private:
|
||||||
|
return {'success': True}
|
||||||
|
|
||||||
|
# if the user has rights to read in the organization or in the group
|
||||||
|
if package.owner_org:
|
||||||
|
authorized = authz.has_user_permission_for_group_or_org(
|
||||||
|
package.owner_org, user, 'read')
|
||||||
|
else:
|
||||||
|
authorized = False
|
||||||
|
|
||||||
|
# if the user is not authorized yet, we should check if the
|
||||||
|
# user is in the allowed_users object
|
||||||
|
if not authorized:
|
||||||
|
# Init the model
|
||||||
|
db.init_db(context['model'])
|
||||||
|
|
||||||
|
# Branch not executed if the database return an empty list
|
||||||
|
if db.AllowedUser.get(package_id=package.id, user_name=user):
|
||||||
|
authorized = True
|
||||||
|
|
||||||
|
if not authorized:
|
||||||
|
# Show a flash message with the URL to acquire the dataset
|
||||||
|
# This message only can be shown when the user tries to access the dataset via its URL (/dataset/...)
|
||||||
|
# The message cannot be displayed in other pages that uses the package_show function such as
|
||||||
|
# the user profile page
|
||||||
|
|
||||||
|
if hasattr(package, 'extras') and 'acquire_url' in package.extras and request.path.startswith('/dataset/')\
|
||||||
|
and package.extras['acquire_url'] != '':
|
||||||
|
helpers.flash_notice(_('This private dataset can be acquired. To do so, please click ' +
|
||||||
|
'<a target="_blank" href="%s">here</a>') % package.extras['acquire_url'],
|
||||||
|
allow_html=True)
|
||||||
|
|
||||||
|
return {'success': False, 'msg': _('User %s not authorized to read package %s') % (user, package.id)}
|
||||||
|
else:
|
||||||
|
return {'success': True}
|
||||||
|
else:
|
||||||
|
return {'success': False, 'msg': _('User %s not authorized to read package %s') % (user, package.id)}
|
||||||
|
|
||||||
|
|
||||||
|
def package_update(context, data_dict):
|
||||||
|
user = context.get('user')
|
||||||
|
user_obj = context.get('auth_user_obj')
|
||||||
|
package = logic_auth.get_package_object(context, data_dict)
|
||||||
|
|
||||||
|
# Only the package creator can update it
|
||||||
|
if package and user_obj and package.creator_user_id == user_obj.id:
|
||||||
|
return {'success': True}
|
||||||
|
|
||||||
|
# if the user has rights to update a dataset in the organization or in the group
|
||||||
|
if package and package.owner_org:
|
||||||
|
authorized = authz.has_user_permission_for_group_or_org(
|
||||||
|
package.owner_org, user, 'update_dataset')
|
||||||
|
else:
|
||||||
|
authorized = False
|
||||||
|
|
||||||
|
if not authorized:
|
||||||
|
return {'success': False, 'msg': _('User %s is not authorized to edit package %s') % (user, package.id)}
|
||||||
|
else:
|
||||||
|
return {'success': True}
|
||||||
|
|
||||||
|
|
||||||
|
@tk.auth_allow_anonymous_access
|
||||||
|
def resource_show(context, data_dict):
|
||||||
|
# This function is needed since CKAN resource_show function uses the default package_show
|
||||||
|
# function instead of the one defined in the plugin.
|
||||||
|
# A bug is openend in order to be able to remove this function
|
||||||
|
# https://github.com/ckan/ckan/issues/1818
|
||||||
|
# It's fixed now, so this function can be deleted when the new version is released.
|
||||||
|
_model = context['model']
|
||||||
|
user = context.get('user')
|
||||||
|
resource = logic_auth.get_resource_object(context, data_dict)
|
||||||
|
|
||||||
|
# check authentication against package
|
||||||
|
query = _model.Session.query(_model.Package)\
|
||||||
|
.join(_model.ResourceGroup)\
|
||||||
|
.join(_model.Resource)\
|
||||||
|
.filter(_model.ResourceGroup.id == resource.resource_group_id)
|
||||||
|
pkg = query.first()
|
||||||
|
if not pkg:
|
||||||
|
raise tk.ObjectNotFound(_('No package found for this resource, cannot check auth.'))
|
||||||
|
|
||||||
|
pkg_dict = {'id': pkg.id}
|
||||||
|
authorized = package_show(context, pkg_dict).get('success')
|
||||||
|
|
||||||
|
if not authorized:
|
||||||
|
return {'success': False, 'msg': _('User %s not authorized to read resource %s') % (user, resource.id)}
|
||||||
|
else:
|
||||||
|
return {'success': True}
|
||||||
|
|
||||||
|
|
||||||
|
@tk.auth_allow_anonymous_access
|
||||||
|
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']}
|
||||||
|
|
||||||
|
#V2 old repo d4science
|
||||||
|
@tk.auth_allow_anonymous_access
|
||||||
|
def revoke_access(context, data_dict):
|
||||||
|
# TODO: Check functionality and improve security(if needed)
|
||||||
|
return {'success': True}
|
|
@ -0,0 +1,28 @@
|
||||||
|
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright (c) 2014 CoNWeT Lab., Universidad Politécnica de Madrid
|
||||||
|
|
||||||
|
# This file is part of CKAN Private Dataset Extension.
|
||||||
|
|
||||||
|
# CKAN Private Dataset Extension is free software: you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Affero General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
|
||||||
|
# CKAN Private Dataset Extension is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
# 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/>.
|
||||||
|
|
||||||
|
ALLOWED_USERS = 'allowed_users'
|
||||||
|
ACQUISITIONS_LIST = 'acquisitions_list'
|
||||||
|
ALLOWED_USERS_STR = 'allowed_users_str'
|
||||||
|
SEARCHABLE = 'searchable'
|
||||||
|
ACQUIRE_URL = 'acquire_url'
|
||||||
|
CONTEXT_CALLBACK = 'updating_via_cb'
|
||||||
|
PACKAGE_ACQUIRED = 'package_acquired'
|
||||||
|
PACKAGE_DELETED = 'revoke_access'
|
|
@ -0,0 +1,108 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright (c) 2014 CoNWeT Lab., Universidad Politécnica de Madrid
|
||||||
|
# Copyright (c) 2019 Future Internet Consulting and Development Solutions S.L.
|
||||||
|
|
||||||
|
# This file is part of CKAN Private Dataset Extension.
|
||||||
|
|
||||||
|
# CKAN Private Dataset Extension is free software: you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Affero General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
|
||||||
|
# CKAN Private Dataset Extension is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
# 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/>.
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
from itertools import count
|
||||||
|
import re
|
||||||
|
|
||||||
|
import ckan.plugins.toolkit as toolkit
|
||||||
|
from ckan.common import _
|
||||||
|
import six
|
||||||
|
|
||||||
|
from ckanext.d4science_theme.privatedatasets import constants
|
||||||
|
from ckanext.d4science_theme import db
|
||||||
|
|
||||||
|
|
||||||
|
def private_datasets_metadata_checker(key, data, errors, context):
|
||||||
|
|
||||||
|
dataset_id = data.get(('id',))
|
||||||
|
private_val = data.get(('private',))
|
||||||
|
|
||||||
|
# Avoid missing value
|
||||||
|
# "if not private_val:" is not valid because private_val can be False
|
||||||
|
if not isinstance(private_val, six.string_types) and not isinstance(private_val, bool):
|
||||||
|
private_val = None
|
||||||
|
|
||||||
|
# If the private field is not included in the data dict, we must check the current value
|
||||||
|
if private_val is None and dataset_id:
|
||||||
|
dataset_dict = toolkit.get_action('package_show')({'ignore_auth': True}, {'id': dataset_id})
|
||||||
|
private_val = dataset_dict.get('private')
|
||||||
|
|
||||||
|
private = private_val is True if isinstance(private_val, bool) else private_val == 'True'
|
||||||
|
metadata_value = data[key]
|
||||||
|
|
||||||
|
# If allowed users are included and the dataset is not private outside and organization, an error will be raised.
|
||||||
|
if metadata_value and not private:
|
||||||
|
errors[key].append(_('This field is only valid when you create a private dataset'))
|
||||||
|
|
||||||
|
|
||||||
|
def allowed_users_convert(key, data, errors, context):
|
||||||
|
|
||||||
|
# By default, all the fileds are in the data dictionary even if they contains nothing. In this case,
|
||||||
|
# the value is 'ckan.lib.navl.dictization_functions.Missing' and for this reason the type is checked
|
||||||
|
|
||||||
|
# Get the allowed user list
|
||||||
|
if (constants.ALLOWED_USERS,) in data and isinstance(data[(constants.ALLOWED_USERS,)], list):
|
||||||
|
allowed_users = data[(constants.ALLOWED_USERS,)]
|
||||||
|
elif (constants.ALLOWED_USERS_STR,) in data and isinstance(data[(constants.ALLOWED_USERS_STR,)], six.string_types):
|
||||||
|
allowed_users_str = data[(constants.ALLOWED_USERS_STR,)].strip()
|
||||||
|
allowed_users = [allowed_user for allowed_user in allowed_users_str.split(',') if allowed_user.strip() != '']
|
||||||
|
else:
|
||||||
|
allowed_users = None
|
||||||
|
|
||||||
|
if allowed_users is not None:
|
||||||
|
current_index = max([int(k[1]) for k in data.keys() if len(k) == 2 and k[0] == key[0]] + [-1])
|
||||||
|
|
||||||
|
if len(allowed_users) == 0:
|
||||||
|
data[(constants.ALLOWED_USERS,)] = []
|
||||||
|
else:
|
||||||
|
for num, allowed_user in zip(count(current_index + 1), allowed_users):
|
||||||
|
allowed_user = allowed_user.strip()
|
||||||
|
data[(key[0], num)] = allowed_user
|
||||||
|
|
||||||
|
|
||||||
|
def get_allowed_users(key, data, errors, context):
|
||||||
|
pkg_id = data[('id',)]
|
||||||
|
|
||||||
|
db.init_db(context['model'])
|
||||||
|
|
||||||
|
users = db.AllowedUser.get(package_id=pkg_id)
|
||||||
|
|
||||||
|
for i, user in enumerate(users):
|
||||||
|
data[(key[0], i)] = user.user_name
|
||||||
|
|
||||||
|
|
||||||
|
def url_checker(key, data, errors, context):
|
||||||
|
url = data.get(key, None)
|
||||||
|
|
||||||
|
if url:
|
||||||
|
# DJango Regular Expression to check URLs
|
||||||
|
regex = re.compile(
|
||||||
|
r'^https?://' # scheme is validated separately
|
||||||
|
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}(?<!-)\.?)|' # domain...
|
||||||
|
r'localhost|' # localhost...
|
||||||
|
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4
|
||||||
|
r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6
|
||||||
|
r'(?::\d+)?' # optional port
|
||||||
|
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
|
||||||
|
|
||||||
|
if regex.match(url) is None:
|
||||||
|
errors[key].append(_('The URL "%s" is not valid.') % url)
|
|
@ -0,0 +1,69 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright (c) 2014 CoNWeT Lab., Universidad Politécnica de Madrid
|
||||||
|
|
||||||
|
# This file is part of CKAN Private Dataset Extension.
|
||||||
|
|
||||||
|
# CKAN Private Dataset Extension is free software: you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Affero General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
|
||||||
|
# CKAN Private Dataset Extension is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
# 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 re
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
from ckan.common import request
|
||||||
|
import ckan.plugins.toolkit as tk
|
||||||
|
|
||||||
|
|
||||||
|
class FiWareNotificationParser(object):
|
||||||
|
|
||||||
|
def parse_notification(self, request_data):
|
||||||
|
my_host = request.host
|
||||||
|
|
||||||
|
fields = ['customer_name', 'resources']
|
||||||
|
|
||||||
|
for field in fields:
|
||||||
|
if field not in request_data:
|
||||||
|
raise tk.ValidationError({'message': '%s not found in the request' % field})
|
||||||
|
|
||||||
|
# Parse the body
|
||||||
|
resources = request_data['resources']
|
||||||
|
user_name = request_data['customer_name']
|
||||||
|
datasets = []
|
||||||
|
|
||||||
|
if not isinstance(user_name, str):
|
||||||
|
raise tk.ValidationError({'message': 'Invalid customer_name format'})
|
||||||
|
|
||||||
|
if not isinstance(resources, list):
|
||||||
|
raise tk.ValidationError({'message': 'Invalid resources format'})
|
||||||
|
|
||||||
|
for resource in resources:
|
||||||
|
if isinstance(resource, dict) and 'url' in resource:
|
||||||
|
parsed_url = urlparse(resource['url'])
|
||||||
|
dataset_name = re.findall('^/dataset/([^/]+).*$', parsed_url.path)
|
||||||
|
|
||||||
|
resource_url = parsed_url.netloc
|
||||||
|
if ':' in my_host and ':' not in resource_url:
|
||||||
|
# Add the default port depending on the protocol
|
||||||
|
default_port = '80' if parsed_url.scheme == 'http' else '443'
|
||||||
|
resource_url = resource_url + default_port
|
||||||
|
|
||||||
|
if len(dataset_name) == 1:
|
||||||
|
if resource_url == my_host:
|
||||||
|
datasets.append(dataset_name[0])
|
||||||
|
else:
|
||||||
|
raise tk.ValidationError({'message': 'Dataset %s is associated with the CKAN instance located at %s, expected %s'
|
||||||
|
% (dataset_name[0], resource_url, my_host)})
|
||||||
|
else:
|
||||||
|
raise tk.ValidationError({'message': 'Invalid resource format'})
|
||||||
|
|
||||||
|
return {'users_datasets': [{'user': user_name, 'datasets': datasets}]}
|
|
@ -0,0 +1,53 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright (c) 2018 Future Internet Consulting and Development Solutions S.L.
|
||||||
|
|
||||||
|
# This file is part of CKAN Private Dataset Extension.
|
||||||
|
|
||||||
|
# CKAN Private Dataset Extension is free software: you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Affero General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
|
||||||
|
# CKAN Private Dataset Extension is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
# 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/>.
|
||||||
|
|
||||||
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
|
from ckan import logic, model
|
||||||
|
from ckan.common import _, g
|
||||||
|
from ckan.lib import base
|
||||||
|
import ckan.plugins.toolkit as toolkit
|
||||||
|
|
||||||
|
from flask import render_template
|
||||||
|
|
||||||
|
from ckanext.d4science_theme.privatedatasets import constants
|
||||||
|
|
||||||
|
|
||||||
|
def acquired_datasets():
|
||||||
|
context = {'auth_user_obj': g.userobj, 'for_view': True, 'model': model, 'session': model.Session, 'user': g.user}
|
||||||
|
data_dict = {'user_obj': g.userobj}
|
||||||
|
try:
|
||||||
|
user_dict = toolkit.get_action('user_show')(context, data_dict)
|
||||||
|
acquired_datasets = toolkit.get_action(constants.ACQUISITIONS_LIST)(context, None)
|
||||||
|
except logic.NotFound:
|
||||||
|
base.abort(404, _('User not found'))
|
||||||
|
except logic.NotAuthorized:
|
||||||
|
base.abort(403, _('Not authorized to see this page'))
|
||||||
|
|
||||||
|
extra_vars = {
|
||||||
|
'user_dict': user_dict,
|
||||||
|
'acquired_datasets': acquired_datasets,
|
||||||
|
}
|
||||||
|
return render_template('user/dashboard_acquired.html', extra_vars)
|
||||||
|
|
||||||
|
|
||||||
|
class AcquiredDatasetsControllerUI():
|
||||||
|
|
||||||
|
def acquired_datasets(self):
|
||||||
|
return acquired_datasets()
|
|
@ -1,21 +1,24 @@
|
||||||
{% extends "package/read_base.html" %}
|
{% ckan_extends %}
|
||||||
|
{% set dataset_extent = h.get_pkg_dict_extra(c.pkg_dict, 'spatial', '') %}
|
||||||
|
{% set d4science_cms_obj_placeholders = h.d4science_get_content_moderator_system_placeholder() %}
|
||||||
|
{% set moderation_item_status = h.get_pkg_dict_extra(c.pkg_dict,d4science_cms_obj_placeholders.item_status,'') %}
|
||||||
|
|
||||||
{% block primary_content_inner %}
|
{% block primary_content_inner %}
|
||||||
{{ super() }}
|
{% if moderation_item_status %}
|
||||||
{% set dataset_extent = h.get_pkg_dict_extra(c.pkg_dict, 'spatial', '') %}
|
<span class="label pull-right" style='margin-left:5px; padding:3px;'>
|
||||||
{% if dataset_extent %}
|
{{ moderation_item_status }}
|
||||||
{% snippet "spatial/snippets/dataset_map.html", extent=dataset_extent %}
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% block package_description %}
|
{% block package_description %}
|
||||||
{% if pkg.private %}
|
{% if pkg.private %}
|
||||||
<span class="dataset-private badge badge-inverse pull-right">
|
<span class="dataset-private label label-inverse pull-right">
|
||||||
<i class="fa fa-lock"></i>
|
<i class="icon-lock"></i>
|
||||||
{{ _('Private') }}
|
{{ _('Private') }}
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<h1>
|
<div class="principaltitle">
|
||||||
{% block page_heading %}
|
{% block page_heading %}
|
||||||
{{ h.dataset_display_name(pkg) }}
|
{{ pkg.title or pkg.name }}
|
||||||
{% if pkg.state.startswith('draft') %}
|
{% if pkg.state.startswith('draft') %}
|
||||||
[{{ _('Draft') }}]
|
[{{ _('Draft') }}]
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -23,28 +26,34 @@
|
||||||
[{{ _('Deleted') }}]
|
[{{ _('Deleted') }}]
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</h1>
|
</div>
|
||||||
{% block package_notes %}
|
{% block package_notes %}
|
||||||
{% if pkg.notes %}
|
{% if pkg.notes %}
|
||||||
<div class="notes embedded-content">
|
<div class="notes embedded-content" style="white-space:pre-line;">
|
||||||
{{ h.render_markdown(h.get_translated(pkg, 'notes')) }}
|
{{ h.render_markdown(pkg.notes) }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{# FIXME why is this here? seems wrong #}
|
{# FIXME why is this here? seems wrong #}
|
||||||
<span class="insert-comment-thread"></span>
|
<span class="insert-comment-thread"></span>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% if dataset_extent %}
|
||||||
|
{% snippet "spatial/snippets/dataset_map.html", extent=dataset_extent %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% block package_tags %}
|
||||||
|
<div class="sectiontitle">{{_('Tags')}}</div>
|
||||||
|
{% snippet "package/snippets/tags.html", tags=pkg.tags %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block package_resources %}
|
{% block package_resources %}
|
||||||
{% snippet "package/snippets/resources_list.html", pkg=pkg, resources=pkg.resources %}
|
{% snippet "package/snippets/resources_list.html", pkg=pkg, resources=pkg.resources %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block package_tags %}
|
|
||||||
{% snippet "package/snippets/tags.html", tags=pkg.tags %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block package_additional_info %}
|
{% block package_additional_info %}
|
||||||
{% snippet "package/snippets/additional_info.html", pkg_dict=pkg %}
|
{% snippet "package/snippets/additional_info.html", pkg_dict=pkg %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -1,5 +1,74 @@
|
||||||
|
{% set key_item_url = _('Item') + ' URL' %}
|
||||||
|
|
||||||
|
<script type="text/javascript" src="/fanstatic/d4science_theme/d4science_scripts.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
//ADDED by Francesco Mangiacrapa
|
||||||
|
jsonToHTML = function(containerID, cssClassToTable) {
|
||||||
|
CKAN_D4S_JSON_Util.jsonToHTML(containerID, cssClassToTable);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<section class="additional-info">
|
<section class="additional-info">
|
||||||
<h3>{{ _('Additional Info') }}</h3>
|
{% block extras scoped %}
|
||||||
|
{#
|
||||||
|
This performs a sort
|
||||||
|
{% for extra in h.sorted_extras(pkg_dict.extras) %}
|
||||||
|
#}
|
||||||
|
{% if pkg_dict.extras %}
|
||||||
|
{# Added by Francesco Mangiacrapa, see 17901 #}
|
||||||
|
{% set extra_item_url = h.get_pkg_dict_extra(pkg_dict,key_item_url) %}
|
||||||
|
{% if extra_item_url %}
|
||||||
|
<div><div class="sectiontitle">{{ key_item_url }}</div>
|
||||||
|
<table class="qr-code-table">
|
||||||
|
<tr>
|
||||||
|
<td><a href={{ extra_item_url }} target="_blank">{{ extra_item_url }}</a></td>
|
||||||
|
<td>{% snippet "package/snippets/qrcode_show.html", package_url=extra_item_url %}</td>
|
||||||
|
</tr>
|
||||||
|
</table></div>
|
||||||
|
{% endif %}
|
||||||
|
{% set extras_indexed_for_categories = h.d4science_get_extras_indexed_for_namespaces(pkg_dict.extras) %}
|
||||||
|
{% for k_cat in extras_indexed_for_categories %}
|
||||||
|
{% set category_idx = extras_indexed_for_categories[k_cat] %}
|
||||||
|
{% if(k_cat!='nocategory') %}
|
||||||
|
<div><div class="sectiontitle">{{category_idx.category.title}}</div>
|
||||||
|
{% if category_idx.category.description %}
|
||||||
|
<div class="notes embedded-content"><p>Description: {{category_idx.category.description}}</p></div>
|
||||||
|
{% endif %}
|
||||||
|
<table class="table table-striped table-bordered table-condensed">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">{{ _('Field') }}</th>
|
||||||
|
<th scope="col">{{ _('Value') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% set my_extras = h.d4science_get_extra_for_category(extras_indexed_for_categories, k_cat) %}
|
||||||
|
{% snippet "package/snippets/extras_table.html", my_extras=my_extras, key_item_url=key_item_url %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% set my_extras = h.d4science_get_extra_for_category(extras_indexed_for_categories, 'nocategory') %}
|
||||||
|
{% if my_extras|length > 0 %}
|
||||||
|
<div><div class="sectiontitle">{{ _('Additional Info') }}</div>
|
||||||
|
<table class="table table-striped table-bordered table-condensed">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">{{ _('Field') }}</th>
|
||||||
|
<th scope="col">{{ _('Value') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% snippet "package/snippets/extras_table.html", my_extras=my_extras, key_item_url=key_item_url %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
<div class="sectiontitle">{{ _('Management Info') }}</div>
|
||||||
<table class="table table-striped table-bordered table-condensed">
|
<table class="table table-striped table-bordered table-condensed">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -13,11 +82,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row" class="dataset-label">{{ _('Source') }}</th>
|
<th scope="row" class="dataset-label">{{ _('Source') }}</th>
|
||||||
{% if h.is_url(pkg_dict.url) %}
|
{% if h.is_url(pkg_dict.url) %}
|
||||||
<td class="dataset-details" property="foaf:homepage">
|
<td class="dataset-details" property="foaf:homepage">{{ h.link_to(pkg_dict.url, pkg_dict.url, rel='foaf:homepage', target='_blank') }}</td>
|
||||||
<a href="{{ pkg_dict.url }}" rel="foaf:homepage" target="_blank">
|
|
||||||
{{ pkg_dict.url }}
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<td class="dataset-details" property="foaf:homepage">{{ pkg_dict.url }}</td>
|
<td class="dataset-details" property="foaf:homepage">{{ pkg_dict.url }}</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -35,16 +100,17 @@
|
||||||
<td class="dataset-details" property="dc:creator">{{ pkg_dict.author }}</td>
|
<td class="dataset-details" property="dc:creator">{{ pkg_dict.author }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{# Added by Francesco Mangiacrapa #}
|
||||||
|
{% set user_maintainer = h.d4science_get_user_info(pkg_dict.maintainer) %}
|
||||||
{% if pkg_dict.maintainer_email %}
|
{% if pkg_dict.maintainer_email %}
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row" class="dataset-label">{{ _('Maintainer') }}</th>
|
<th scope="row" class="dataset-label">{{ _('Maintainer') }}</th>
|
||||||
<td class="dataset-details" property="dc:contributor">{{ h.mail_to(email_address=pkg_dict.maintainer_email, name=pkg_dict.maintainer) }}</td>
|
<td class="dataset-details" property="dc:contributor">{{ h.mail_to(email_address=pkg_dict.maintainer_email, name=user_maintainer.fullname if (user_maintainer and user_maintainer.fullname) else pkg_dict.maintainer) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% elif pkg_dict.maintainer %}
|
{% elif pkg_dict.maintainer %}
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row" class="dataset-label">{{ _('Maintainer') }}</th>
|
<th scope="row" class="dataset-label">{{ _('Maintainer') }}</th>
|
||||||
<td class="dataset-details" property="dc:contributor">{{ pkg_dict.maintainer }}</td>
|
<td class="dataset-details" property="dc:contributor">{{ user_maintainer.fullname if (user_maintainer and user_maintainer.fullname) else pkg_dict.maintainer }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
@ -78,18 +144,7 @@
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</tbody>
|
||||||
{% block extras scoped %}
|
|
||||||
{% for extra in h.sorted_extras(pkg_dict.extras) %}
|
|
||||||
{% set key, value = extra %}
|
|
||||||
<tr rel="dc:relation" resource="_:extra{{ i }}">
|
|
||||||
<th scope="row" class="dataset-label" property="rdfs:label">{{ _(key|e) }}</th>
|
|
||||||
<td class="dataset-details" property="rdf:value">{{ value }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
</table>
|
||||||
</section>
|
{% endblock %}
|
||||||
|
</section>
|
|
@ -0,0 +1,59 @@
|
||||||
|
<!-- Applying "Show more"/"Show less" to 'spatial field', see #23298 -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
function manageLongField(containerID, n) {
|
||||||
|
try {
|
||||||
|
var str = document.getElementById(containerID).innerHTML;
|
||||||
|
if (str && str.length <= n) {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
var newContent = "<div><input type='checkbox' class='read-more-state' id='post-"+containerID+"'' /><label for='post-"+containerID+"'' class='read-more-trigger'></label><p class='read-more-wrap'>";
|
||||||
|
newContent += str.substring(0, n);
|
||||||
|
newContent += "<span class='read-more-target'>";
|
||||||
|
newContent += str.substring(n, str.length);
|
||||||
|
newContent += "</span></p></div>"
|
||||||
|
|
||||||
|
document.getElementById(containerID).innerHTML = newContent
|
||||||
|
} catch (e) {
|
||||||
|
console.log('invalid containerID ' + containerID, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<!-- reading placeholder used by D4Science Content Moderator System, see #22854 -->
|
||||||
|
{% set d4science_cms_obj_placeholders = h.d4science_get_content_moderator_system_placeholder() %}
|
||||||
|
{% for extra_lnk in my_extras %}
|
||||||
|
{% set index = loop.index %}
|
||||||
|
{% for k in extra_lnk.keys() %}
|
||||||
|
{# Added by Francesco Mangiacrapa, see: #21701 #}
|
||||||
|
{% set extra_value = extra_lnk[k] %}
|
||||||
|
<!-- showing only extra fields with no empty values -->
|
||||||
|
{% if extra_value is defined and extra_value|length %}
|
||||||
|
{# Added by Francesco Mangiacrapa, see: #7055 #}
|
||||||
|
{% set isHttp = extra_value.startswith(('http://', 'https://')) %}
|
||||||
|
<tr rel="dc:relation" resource="_:extra{{ i }}">
|
||||||
|
<!-- no showing ITEM URL and D4Science CMS fields into table -->
|
||||||
|
{% if k != key_item_url and (not k.startswith(d4science_cms_obj_placeholders.prefix)) %}
|
||||||
|
<th scope="row" class="dataset-label" property="rdfs:label">{{ _(k) }}</th>
|
||||||
|
{% if isHttp %}
|
||||||
|
{% if k == 'graphic-preview-file'%}
|
||||||
|
<td class="dataset-details" property="rdf:value"><img src={{ extra_value }} alt={{ extra_value }}></td>
|
||||||
|
{% else %}
|
||||||
|
<td class="dataset-details" property="rdf:value"><a href={{ extra_value }} target="_blank">{{ extra_value }}</a></td>
|
||||||
|
{% endif %}
|
||||||
|
{% elif k == 'responsible-party' %}
|
||||||
|
<td class="dataset-details" property="rdf:value"><div id="responsible-party-extra-{{index}}" class="d4s-json-table">{{ extra_value }}</div><script>jsonToHTML('responsible-party-extra-{{index}}')</script></td>
|
||||||
|
{% elif k == 'dataset-reference-date' %}
|
||||||
|
<td class="dataset-details" property="rdf:value"><div id="dataset-reference-date-extra-{{index}}" class="d4s-json-table">{{ extra_value }}</div><script>jsonToHTML('dataset-reference-date-extra-{{index}}')</script></td>
|
||||||
|
{% elif k == 'coupled-resource' %}
|
||||||
|
<td class="dataset-details" property="rdf:value"><div id="coupled-resource-date-extra-{{index}}" class="d4s-json-table">{{ extra_value }}</div><script>jsonToHTML('coupled-resource-date-extra-{{index}}')</script></td>
|
||||||
|
{% elif k.startswith('Zenodo') %}
|
||||||
|
<td class="dataset-details" property="rdf:value"><div id="related-identifier-extra-{{index}}" class="d4s-json-table">{{ extra_value }}</div><script>jsonToHTML('related-identifier-extra-{{index}}')</script></td>
|
||||||
|
{% elif k == 'spatial' %}
|
||||||
|
<td class="dataset-details" property="rdf:value"><div id='spatial-{{index}}'>{{ extra_value }}</div><script>manageLongField('spatial-{{index}}', 150)</script></td>
|
||||||
|
{% else %}
|
||||||
|
<td class="dataset-details" property="rdf:value">{{ extra_value }}</td>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
|
@ -1,41 +1,48 @@
|
||||||
{#
|
{#
|
||||||
Displays a sidebar module with information for given package
|
Displays a sidebard module with information for given package
|
||||||
|
|
||||||
pkg - The package dict that owns the resources.
|
pkg - The package dict that owns the resources.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
{% snippet "package/snippets/info.html", pkg=pkg %}
|
{% snippet "package/snippets/info.html", pkg=pkg %}
|
||||||
|
|
||||||
#}
|
#}
|
||||||
{% block package_info %}
|
{% block package_info %}
|
||||||
{% if pkg %}
|
{% if pkg %}
|
||||||
<section class="module module-narrow">
|
<section class="module module-narrow">
|
||||||
<div class="module context-info">
|
<div class="module context-info">
|
||||||
<div class="module-content">
|
<div class="module-content">
|
||||||
{% block package_info_inner %}
|
{% block package_info_inner %}
|
||||||
{% block heading %}
|
{% block heading %}
|
||||||
<h1 class="heading">{{ h.dataset_display_name(pkg) }}</h1>
|
<div class="infotitle">{{ h.dataset_display_name(pkg) }}</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block nums %}
|
{% if pkg.extras %}
|
||||||
{% set num_followers = h.follow_count('dataset', pkg.id) %}
|
{% for extra in pkg.extras if extra['key'] == 'graphic-preview-file' %}
|
||||||
<div class="nums">
|
<div class="graphic-preview-style">
|
||||||
<dl>
|
<div><a href={{ extra['value'] }} target="_blank"><img src={{ extra['value'] }} alt='graphic-preview-file'>Show Graphic</a></div>
|
||||||
<dt>{{ _('Followers') }}</dt>
|
</div>
|
||||||
<dd data-module="followers-counter" data-module-id="{{ pkg.id }}" data-module-num_followers="{{ num_followers }}">{{ h.SI_number_span(num_followers) }}</dd>
|
{% endfor %}
|
||||||
</dl>
|
{% endif %}
|
||||||
</div>
|
{% block nums %}
|
||||||
{% endblock %}
|
<div class="nums">
|
||||||
{% block follow_button %}
|
<dl>
|
||||||
{% if not hide_follow_button %}
|
<dt>{{ _('Followers') }}</dt>
|
||||||
<div class="follow_button">
|
<dd>{{ h.SI_number_span(h.follow_count('dataset', pkg.id)) }}</dd>
|
||||||
{{ h.follow_button('dataset', pkg.id) }}
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endblock %}
|
||||||
|
{% block follow_button %}
|
||||||
|
{% if not hide_follow_button %}
|
||||||
|
<div class="follow_button">
|
||||||
|
{{ h.follow_button('dataset', pkg.name) }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% endblock %}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</section>
|
{% endif %}
|
||||||
{% endif %}
|
{% endblock %}
|
||||||
{% endblock %}
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
{% set qr_code_image = h.d4science_get_qrcode_for_url(package_url) %}
|
||||||
|
{% block package_qrcode_for_url %}
|
||||||
|
<div style="padding: 1px;"><img src='data:image/svg+xml;base64, {{ qr_code_image }}' alt={{ package_url }}> </div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -1,82 +1,108 @@
|
||||||
{#
|
{% set can_edit = h.check_access('package_update', {'id':pkg.id }) %}
|
||||||
Renders a single resource with icons and view links.
|
{% set url_action = 'resource_edit' if url_is_edit and can_edit else 'resource_read' %}
|
||||||
|
{# {% set url = h.url_for(controller='package', action=url_action, id=pkg.name, resource_id=res.id) %} #}
|
||||||
res - A resource dict to render
|
{% set url = h.url_for('dataset_resource.read', id=pkg.name, resource_id=res.id) %}
|
||||||
pkg - A package dict that the resource belongs to
|
|
||||||
can_edit - Whether the user is allowed to edit the resource
|
|
||||||
url_is_edit - Whether the link to the resource should be to editing it (set to False to make the link view the resource)
|
|
||||||
url - URL of the resource details page(resource edit/read depending on url_is_edit, by default).
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
{% snippet "package/snippets/resource_item.html", res=resource, pkg=pkg, can_edit=True, url_is_edit=False %}
|
|
||||||
|
|
||||||
#}
|
|
||||||
{% set url_action = pkg.type ~ ('_resource.edit' if url_is_edit and can_edit else '_resource.read') %}
|
|
||||||
{% set url = url or h.url_for(url_action, id=pkg.name, resource_id=res.id) %}
|
|
||||||
|
|
||||||
<li class="resource-item" data-id="{{ res.id }}">
|
<li class="resource-item" data-id="{{ res.id }}">
|
||||||
|
{# Added by Francesco Mangiacrapa block custom_view_on_resources see:4851 #}
|
||||||
|
{% block custom_view_on_resources %}
|
||||||
|
{% set user = c.user %}
|
||||||
|
{% if user %}
|
||||||
{% block resource_item_title %}
|
{% block resource_item_title %}
|
||||||
<a class="heading" href="{{ url }}" title="{{ res.name or res.description }}">
|
<a class="heading" href="{{ url }}" title="{{ res.name or res.description }}">
|
||||||
{{ h.resource_display_name(res) | truncate(50) }}<span class="format-label" property="dc:format" data-format="{{ res.format.lower() or 'data' }}">{{ h.get_translated(res, 'format') }}</span>
|
{{ h.resource_display_name(res) | truncate(50) }}<span class="format-label" property="dc:format" data-format="{{ res.format.lower() or 'data' }}">{{ res.format }}</span>
|
||||||
{{ h.popular('views', res.tracking_summary.total, min=10) if res.tracking_summary }}
|
{{ h.popular('views', res.tracking_summary.total, min=10) }}
|
||||||
</a>
|
</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block resource_item_description %}
|
{% block resource_item_description %}
|
||||||
<p class="description">
|
<p class="description">
|
||||||
{% if res.description %}
|
{% if res.description %}
|
||||||
{{ h.markdown_extract(h.get_translated(res, 'description'), extract_length=80) }}
|
{{ h.markdown_extract(res.description, extract_length=80) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block resource_item_explore %}
|
{% block resource_item_explore %}
|
||||||
{% if not url_is_edit %}
|
{% if not url_is_edit %}
|
||||||
|
{# Only if can edit, explorer button is shown with several facility #}
|
||||||
|
{% if can_edit %}
|
||||||
<div class="dropdown btn-group">
|
<div class="dropdown btn-group">
|
||||||
<a href="#" class="btn btn-primary dropdown-toggle" type="button" id="dropdownExplorer" data-bs-toggle="dropdown" aria-expanded="false">
|
<a href="#" class="btn btn-primary dropdown-toggle" data-toggle="dropdown">
|
||||||
<i class="fa fa-share"></i>
|
<i class="icon-share-alt"></i>
|
||||||
{{ _('Explore') }}
|
{{ _('Explore') }}
|
||||||
<span class="caret"></span>
|
<span class="caret"></span>
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu" aria-labelledby="dropdownExplorer">
|
<ul class="dropdown-menu">
|
||||||
{% block resource_item_explore_links %}
|
{% block resource_item_explore_links %}
|
||||||
{% block explore_view %}
|
|
||||||
<li>
|
|
||||||
<a class="dropdown-item" href="{{ url }}">
|
|
||||||
{% if res.has_views %}
|
|
||||||
<i class="fa fa-chart-bar"></i>
|
|
||||||
{{ _('Preview') }}
|
|
||||||
{% else %}
|
|
||||||
<i class="fa fa-info-circle"></i>
|
|
||||||
{{ _('More information') }}
|
|
||||||
{% endif %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endblock explore_view %}
|
|
||||||
|
|
||||||
{% if res.url and h.is_url(res.url) %}
|
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item resource-url-analytics" href="{{ res.url }}" target="_blank" rel="noreferrer">
|
<a href="{{ url }}">
|
||||||
{% if res.has_views or res.url_type == 'upload' %}
|
{% if res.has_views %}
|
||||||
<i class="fa fa-arrow-circle-down"></i>
|
<i class="icon-bar-chart"></i>
|
||||||
{{ _('Download') }}
|
{{ _('Preview') }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<i class="fa fa-external-link"></i>
|
<i class="icon-info-sign"></i>
|
||||||
{{ _('Go to resource') }}
|
{{ _('More information') }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% if res.url and h.is_url(res.url) %}
|
||||||
|
{# Added by Francesco Mangiacrapa #}
|
||||||
|
<script type="text/javascript" >
|
||||||
|
//handles the click event
|
||||||
|
function handleClick(event, element, url) {
|
||||||
|
window.open(url, "_blank", null);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{# Comment by Francesco Mangiacrapa, in order to avoid "Download" and "Go to Resource" facilities through the Explore button #}
|
||||||
|
<!--<li>
|
||||||
|
<a class="resource-url-analytics" href="#" onclick="return handleClick(event, this, '{{ res.url }}');">
|
||||||
|
{% if res.has_views %}
|
||||||
|
<i class="icon-download"></i>
|
||||||
|
{{ _('Download') }}
|
||||||
|
{% else %}
|
||||||
|
<i class="icon-external-link"></i>
|
||||||
|
{{ _('Go to resource') }}
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
</li>-->
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if can_edit %}
|
{#{% if can_edit %}#}
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item" href="{{ h.url_for(pkg.type ~ '_resource.edit', id=pkg.name, resource_id=res.id) }}">
|
{# <a href="{{ h.url_for(controller='package', action='resource_edit', id=pkg.name, resource_id=res.id) }}"> #}
|
||||||
<i class="fa fa-pencil-square"></i>
|
<a href="{{ h.url_for('dataset_resource.edit', id=pkg.name, resource_id=res.id) }}">
|
||||||
|
<i class="icon-edit"></i>
|
||||||
{{ _('Edit') }}
|
{{ _('Edit') }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{#{% endif %}#}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{ url }}" title="Go to {{ url }}">
|
||||||
|
{{ _('Go to resource') }}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
{% else %}
|
||||||
|
{% block resource_item_title2 %}
|
||||||
|
{# Updated by Francesco Mangiacrapa, see: #10056 #}
|
||||||
|
<a class="heading" href="javascript:CKAN_D4S_Functions_Util.showPopupD4S(null, 'myPopup_{{res.id}}', '250px');">
|
||||||
|
{{ h.resource_display_name(res) | truncate(50) }}<span class="format-label" property="dc:format" data-format="{{ res.format.lower() or 'data' }}">{{ res.format }}</span>
|
||||||
|
{{ h.popular('views', res.tracking_summary.total, min=10) }}
|
||||||
|
</a>
|
||||||
|
{% endblock %}
|
||||||
|
<div class="popupD4S" onclick="CKAN_D4S_Functions_Util.showPopupD4S(event, 'myPopup_{{res.id}}', '250px')">
|
||||||
|
{% block resource_item_description2 %}
|
||||||
|
<p class="description">
|
||||||
|
{% if res.description %}
|
||||||
|
{{ h.markdown_extract(res.description, extract_length=80) }}
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
{% endblock %}
|
||||||
|
<span class="popuptext" id="myPopup_{{res.id}}">The resource: '{{ h.resource_display_name(res) | truncate(30) }}' is not accessible as guest user. You must login to access it!</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
|
|
@ -1,37 +1,77 @@
|
||||||
{#
|
{#
|
||||||
Renders a list of resources with icons and view links.
|
Renders a list of resources with icons and view links.
|
||||||
|
|
||||||
resources - A list of resources (dicts) to render
|
resources - A list of resources to render
|
||||||
pkg - A package dict that the resources belong to.
|
pkg - A package object that the resources belong to.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
{% snippet "package/snippets/resources_list.html", pkg=pkg, resources=pkg.resources %}
|
{% snippet "package/snippets/resources_list.html", pkg=pkg, resources=pkg.resources %}
|
||||||
|
|
||||||
#}
|
#}
|
||||||
|
|
||||||
<section id="dataset-resources" class="resources">
|
<script type="text/javascript" >
|
||||||
<h2>{{ _('Data and Resources') }}</h2>
|
|
||||||
{% block resource_list %}
|
//Task #10389
|
||||||
{% if resources %}
|
window.addEventListener("message",
|
||||||
<ul class="{% block resource_list_class %}resource-list{% endblock %}">
|
function (e) {
|
||||||
{% block resource_list_inner %}
|
|
||||||
{% set can_edit = can_edit or h.check_access('package_update', {'id':pkg.id }) %}
|
var curr_loc = window.location.toString()
|
||||||
{% for resource in resources %}
|
var orgin = e.origin.toString()
|
||||||
{% snippet 'package/snippets/resource_item.html', pkg=pkg, res=resource, can_edit=can_edit %}
|
if(curr_loc.startsWith(orgin)){
|
||||||
{% endfor %}
|
//alert("ignoring message from myself");
|
||||||
{% endblock %}
|
return;
|
||||||
</ul>
|
}
|
||||||
{% else %}
|
//console.log("origin: "+e.data)
|
||||||
{% block resource_list_empty %}
|
if(e.data == null)
|
||||||
{% if h.check_access('resource_create', {'package_id': pkg['id']}) %}
|
return;
|
||||||
{% trans url=h.url_for(pkg.type ~ '_resource.new', id=pkg.name) %}
|
|
||||||
<p class="empty">This dataset has no data, <a href="{{ url }}">why not add some?</a></p>
|
var pMess = JSON.parse(e.data)
|
||||||
{% endtrans %}
|
//console.log(pMess.explore_vres_landing_page)
|
||||||
{% else %}
|
window.linktogateway = pMess.explore_vres_landing_page;
|
||||||
<p class="empty">{{ _('This dataset has no data') }}</p>
|
goToHomeLink("resource-private")
|
||||||
{% endif %}
|
},false);
|
||||||
{% endblock %}
|
|
||||||
|
//Task #10389
|
||||||
|
goToHomeLink = function (divId) {
|
||||||
|
|
||||||
|
var myDiv = document.getElementById(divId);
|
||||||
|
var myHost = window.linktogateway.substring(0, window.linktogateway.lastIndexOf("/"));
|
||||||
|
console.log("my host: "+myHost)
|
||||||
|
|
||||||
|
if(myDiv && myHost){
|
||||||
|
myDiv.innerHTML= myDiv.innerHTML + ". <a target=\"_blank\" href="+myHost+"/home"+">Go to Login...</a>";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section id="dataset-resources" class="resources">
|
||||||
|
<div class="sectiontitle">{{ _('Data and Resources') }}</div>
|
||||||
|
{% set user = c.user %}
|
||||||
|
{# Added by Francesco Mangiacrapa #10389 #}
|
||||||
|
{% if not user %}
|
||||||
|
<div id="resource-private" class="required-access">To access the resources you must log in</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{# end #}
|
||||||
</section>
|
{% block resource_list %}
|
||||||
|
{% if resources %}
|
||||||
|
<ul class="{% block resource_list_class %}resource-list{% endblock %}">
|
||||||
|
{% block resource_list_inner %}
|
||||||
|
{% for resource in resources %}
|
||||||
|
{% snippet 'package/snippets/resource_item.html', pkg=pkg, res=resource %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
{% if h.check_access('resource_create', {'package_id': pkg['id']}) %}
|
||||||
|
{% trans url=h.url_for(controller='package', action='new_resource', id=pkg.name) %}
|
||||||
|
<p class="empty">This dataset has no data, <a href="{{ url }}">why not add some?</a></p>
|
||||||
|
{% endtrans %}
|
||||||
|
{% else %}
|
||||||
|
<p class="empty">{{ _('This dataset has no data') }}</p>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
</section>
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
{#
|
||||||
|
|
||||||
|
Displays a Get Access button to request access to a private dataset.
|
||||||
|
|
||||||
|
ulr_dest - target url
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
{% snippet 'snippets/acquire_button.html', url_dest=url %}
|
||||||
|
|
||||||
|
#}
|
||||||
|
<a href={{ url_dest }} class="btn btn-mini" target="_blank">
|
||||||
|
<i class="icon-shopping-cart fa fa-shopping-cart"></i>
|
||||||
|
{{ _('Acquire') }}
|
||||||
|
</a>
|
||||||
|
|
|
@ -1,70 +1,220 @@
|
||||||
{#
|
{#
|
||||||
Displays a single of dataset.
|
Displays a single of dataset.
|
||||||
|
|
||||||
package - A package to display.
|
package - A package to display.
|
||||||
item_class - The class name to use on the list item.
|
item_class - The class name to use on the list item.
|
||||||
hide_resources - If true hides the resources (default: false).
|
hide_resources - If true hides the resources (default: false).
|
||||||
|
banner - If true displays a popular banner (default: false).
|
||||||
Example:
|
truncate - The length to trucate the description to (default: 180)
|
||||||
|
truncate_title - The length to truncate the title to (default: 80).
|
||||||
{% snippet 'snippets/package_item.html', package=c.datasets[0] %}
|
|
||||||
|
Example:
|
||||||
#}
|
|
||||||
{% set title = package.title or package.name %}
|
{% snippet 'snippets/package_item.html', package=c.datasets[0] %}
|
||||||
{% set notes = h.markdown_extract(package.notes, extract_length=180) %}
|
|
||||||
|
#}
|
||||||
{% block package_item %}
|
|
||||||
|
{% set truncate = truncate or 180 %}
|
||||||
|
{% set truncate_title = truncate_title or 80 %}
|
||||||
|
{% set title = package.title or package.name %}
|
||||||
|
{% set notes = h.markdown_extract(package.notes, extract_length=truncate) %}
|
||||||
|
{% set acquired = h.is_dataset_acquired(package) %}
|
||||||
|
{% set owner = h.is_owner(package) %}
|
||||||
|
|
||||||
|
{# {% resource 'd4science_theme/privatedatasets.css' %}#}
|
||||||
|
|
||||||
|
{#
|
||||||
|
CHANGED BY FRANCESCO.MANGIACRAPA
|
||||||
|
#}
|
||||||
|
|
||||||
|
{% block package_item_content %}
|
||||||
|
<!--Added by Francesco Mangiacrapa, See #6569, #7544, #8032, #8127 -->
|
||||||
|
{#
|
||||||
|
{{ package.extras }}
|
||||||
|
#}
|
||||||
|
|
||||||
|
{% block toolbar %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
<script type="text/javascript" >
|
||||||
|
|
||||||
|
flinktogateway = function (event, orgTitle) {
|
||||||
|
|
||||||
|
var myDiv = document.getElementById("d4s_modal-div");
|
||||||
|
var clickPosition = CKAN_D4S_Functions_Util.getPosition(myDiv, event)
|
||||||
|
var myPosition = JSON.parse(clickPosition);
|
||||||
|
//console.log('msg')
|
||||||
|
|
||||||
|
var mypopup = document.getElementById("myD4SModal");
|
||||||
|
mypopup.style.display = "block";
|
||||||
|
|
||||||
|
var offset = myDiv.offsetHeight? myDiv.offsetHeight/2 : 0;
|
||||||
|
myDiv.style.top = (myPosition.posY - Number(offset))+"px";
|
||||||
|
|
||||||
|
// Get the <span> element that closes the modal
|
||||||
|
var span = document.getElementById("d4s_span_modal");
|
||||||
|
|
||||||
|
// When the user clicks on <span> (x), close the modal
|
||||||
|
span.onclick = function() {
|
||||||
|
mypopup.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
// When the user clicks anywhere outside of the modal, close it
|
||||||
|
window.onclick = function(event) {
|
||||||
|
if (event.target == myD4SModal) {
|
||||||
|
mypopup.style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var myPopupContent = document.getElementById("d4s_p_content");
|
||||||
|
var msgComplete = "This {{ _('dataset') }} is private to the VRE: <b>"+orgTitle+"</b>.<br/>You can request access to VREs using 'Explore Virtual Research Environments'";
|
||||||
|
|
||||||
|
if(window.linktogateway != null)
|
||||||
|
myPopupContent.innerHTML= msgComplete + " <a target=\"_blank\" href="+window.linktogateway+">Go to Explore...</a>";
|
||||||
|
else {
|
||||||
|
myPopupContent.innerHTML= msgComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% if package.private and not h.can_read(package) %}
|
||||||
<li class="{{ item_class or "dataset-item" }}">
|
<li class="{{ item_class or "dataset-item" }}">
|
||||||
{% block content %}
|
<!-- The Modal -->
|
||||||
<div class="dataset-content">
|
<div id="myD4SModal" class="d4s_modal">
|
||||||
{% block heading %}
|
<!-- Modal content -->
|
||||||
<h2 class="dataset-heading">
|
<div id="d4s_modal-div" class="d4s_modal-content">
|
||||||
{% block heading_private %}
|
<h3>Access required...</h3>
|
||||||
{% if package.private %}
|
<span id="d4s_span_modal" class="d4s_close">×</span>
|
||||||
<span class="dataset-private badge bg-secondary">
|
<p id="d4s_p_content"></p>
|
||||||
<i class="fa fa-lock"></i>
|
</div>
|
||||||
{{ _('Private') }}
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
{% block heading_title %}
|
|
||||||
<a href="{{ h.url_for('%s.read' % package.type, id=package.name) }}" title="{{ title }}">
|
|
||||||
{{title|truncate(80)}}
|
|
||||||
</a>
|
|
||||||
{% endblock %}
|
|
||||||
{% block heading_meta %}
|
|
||||||
{% if package.get('state', '').startswith('draft') %}
|
|
||||||
<span class="badge bg-info">{{ _('Draft') }}</span>
|
|
||||||
{% elif package.get('state', '').startswith('deleted') %}
|
|
||||||
<span class="badge bg-danger">{{ _('Deleted') }}</span>
|
|
||||||
{% endif %}
|
|
||||||
{{ h.popular('recent views', package.tracking_summary.recent, min=10) if package.tracking_summary }}
|
|
||||||
{% endblock %}
|
|
||||||
</h2>
|
|
||||||
{% endblock %}
|
|
||||||
{% block notes %}
|
|
||||||
{% if notes %}
|
|
||||||
<div>{{ notes|urlize }}</div>
|
|
||||||
{% else %}
|
|
||||||
<p class="empty">{{ h.humanize_entity_type('package', package.type, 'no description') or _("There is no description for this dataset") }}</p>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
</div>
|
</div>
|
||||||
{% block resources %}
|
{% set orgTitle = package.organization.title %}
|
||||||
{% if package.resources and not hide_resources %}
|
<div class="dataset-content d4s_div_clickable" onclick="flinktogateway(event, '{{orgTitle}}')" title="This {{ _('dataset') }} is private to the VRE: {{package.organization.title}}. You can request access to VREs using 'Explore Virtual Research Environments'">
|
||||||
{% block resources_outer %}
|
<div class="show_meatadatatype">
|
||||||
<ul class="dataset-resources list-unstyled">
|
{% set mvalue = h.d4science_theme_get_systemtype_value_from_extras(package, package.extras) %}
|
||||||
{% block resources_inner %}
|
{% set mcolor = h.d4science_get_color_for_type(mvalue) %}
|
||||||
{% for resource in h.dict_list_reduce(package.resources, 'format') %}
|
<span class="button__badge" style="color: {{ mcolor }}">{{ mvalue }}</span>
|
||||||
<li>
|
</div>
|
||||||
<a href="{{ h.url_for(package.type ~ '.read', id=package.name) }}" class="badge badge-default" data-format="{{ resource.lower() }}">{{ resource }}</a>
|
<h3 class="dataset-heading">
|
||||||
</li>
|
{% if package.private and not h.can_read(package) %}
|
||||||
{% endfor %}
|
<span class="dataset-private label label-inverse">
|
||||||
{% endblock %}
|
<i class="icon-lock"></i>
|
||||||
</ul>
|
{{ _('Private') }}
|
||||||
{% endblock %}
|
</span>
|
||||||
|
{{ _(h.truncate(title, truncate_title)) }}
|
||||||
|
{% endif %}
|
||||||
|
{% if acquired and not owner %}
|
||||||
|
<span class="dataset-private label label-acquired">
|
||||||
|
<i class="icon-shopping-cart"></i>
|
||||||
|
{{ _('Acquired') }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
<!-- Customizations Acquire Button -->
|
||||||
|
{% if package.private and not h.can_read(package) %}
|
||||||
|
{# {{ _(h.truncate(title, truncate_title)) }} Dataset#} <!-- THIS IS PACKAGE TITLE -->
|
||||||
|
<div class="divider"/>
|
||||||
|
{{ h.acquire_button(package) }}
|
||||||
|
{% else %}
|
||||||
|
{{ h.link_to(h.truncate(title, truncate_title), h.url_for(controller='package', action='read', id=package.name)) }}
|
||||||
|
{% endif %}
|
||||||
|
<!-- End of customizations Acquire Button -->
|
||||||
|
|
||||||
|
{% if package.get('state', '').startswith('draft') %}
|
||||||
|
<span class="label label-info">{{ _('Draft') }}</span>
|
||||||
|
{% elif package.get('state', '').startswith('deleted') %}
|
||||||
|
<span class="label label-important">{{ _('Deleted') }}</span>
|
||||||
|
{% endif %}
|
||||||
|
{{ h.popular('recent views', package.tracking_summary.recent, min=10) if package.tracking_summary }}
|
||||||
|
</h3>
|
||||||
|
{% if banner %}
|
||||||
|
<span class="banner">{{ _('Popular') }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% if notes %}
|
||||||
{% endblock %}
|
<div style="word-wrap:break-word;">{{ notes|urlize(70,target='_blank') }}</div><!-- THIS IS PACKAGE DESCRIPTION -->
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{% endblock %}
|
{% else %}
|
||||||
|
<li class="{{ item_class or "dataset-item" }}">
|
||||||
|
<div class="dataset-content">
|
||||||
|
<div class="show_meatadatatype">
|
||||||
|
{% set mvalue = h.d4science_theme_get_systemtype_value_from_extras(package, package.extras)%}
|
||||||
|
{% set mcolor = h.d4science_get_color_for_type(mvalue) %}
|
||||||
|
<span class="button__badge" style="color: {{ mcolor }}">{{ mvalue }}</span>
|
||||||
|
</div>
|
||||||
|
<h3 class="dataset-heading">
|
||||||
|
{% if package.private and not h.can_read(package) %}
|
||||||
|
<span class="dataset-private label label-inverse">
|
||||||
|
<i class="icon-lock"></i>
|
||||||
|
{{ _('Private') }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
{% if acquired and not owner %}
|
||||||
|
<span class="dataset-private label label-acquired">
|
||||||
|
<i class="icon-shopping-cart"></i>
|
||||||
|
{{ _('Acquired') }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
{% if owner %}
|
||||||
|
<span class="dataset-private label label-owner">
|
||||||
|
<i class="icon-user"></i>
|
||||||
|
{{ _('Owner') }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Customizations Acquire Button -->
|
||||||
|
{% if package.private and not h.can_read(package) %}
|
||||||
|
{{ _(h.truncate(title, truncate_title)) }}
|
||||||
|
<div class="divider"/>
|
||||||
|
{{ h.acquire_button(package) }}
|
||||||
|
{% else %}
|
||||||
|
{# CHANGED BY FRANCESCO MANGIACRAPA #}
|
||||||
|
{{ h.link_to(h.truncate(title, truncate_title), h.url_for(controller='dataset', action='read', id=package.name)) }}
|
||||||
|
{% endif %}
|
||||||
|
<!-- End of customizations Acquire Button -->
|
||||||
|
|
||||||
|
{% if package.get('state', '').startswith('draft') %}
|
||||||
|
<span class="label label-info">{{ _('Draft') }}</span>
|
||||||
|
{% elif package.get('state', '').startswith('deleted') %}
|
||||||
|
<span class="label label-important">{{ _('Deleted') }}</span>
|
||||||
|
{% endif %}
|
||||||
|
{{ h.popular('recent views', package.tracking_summary.recent, min=10) if package.tracking_summary }}
|
||||||
|
</h3>
|
||||||
|
{% if banner %}
|
||||||
|
<span class="banner">{{ _('Popular') }}</span>
|
||||||
|
{% endif %}
|
||||||
|
{% if notes %}
|
||||||
|
<div style="word-wrap:break-word;">{{ notes|urlize(70,target='_blank') }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if package.resources and not hide_resources %}
|
||||||
|
|
||||||
|
{# CHANGED BY FRANCESCO MANGIACRAPA, see: #7055 #}
|
||||||
|
<ul class="dataset-resources unstyled">
|
||||||
|
{% for resource in package.resources %}
|
||||||
|
{% set resource_format = resource.format %}
|
||||||
|
{# TO INTERNAL RESOURCE PAGE set url = h.url_for(controller='package', action='resource_read', id=package.name, resource_id=resource.id) #}
|
||||||
|
<li>
|
||||||
|
|
||||||
|
{% if c.userobj %}
|
||||||
|
{# USER IS LOGGED #}
|
||||||
|
{# CHANGED BY FRANCESCO MANGIACRAPA #11178 #}
|
||||||
|
<a href="{{ resource.url }}" target="_blank" title="{{ resource.name or resource.description }}" class="label" data-format="{{ resource_format.lower() }}">{{ resource_format }}</a>
|
||||||
|
{% else %}
|
||||||
|
{# CHANGED BY FRANCESCO MANGIACRAPA #12377 #}
|
||||||
|
<a class="label" href="javascript:CKAN_D4S_Functions_Util.showPopupD4S(null, 'myPopup_{{resource.id}}');" title="{{ resource.name or resource.description }}" class="label" data-format="{{ resource.format.lower() }}">{{ resource.format }}</a>
|
||||||
|
<div class="popupD4SNoArrow" style="display: inline;" onclick="CKAN_D4S_Functions_Util.showPopupD4S(event, 'myPopup_{{resource.id}}')">
|
||||||
|
<span class="popuptext" id="myPopup_{{resource.id}}">The resource: '{{ h.resource_display_name(resource) | truncate(30) }}' is not accessible as guest user. You must login to access it!</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
|
@ -13,7 +13,7 @@
|
||||||
<ul class="{{ _class }}">
|
<ul class="{{ _class }}">
|
||||||
{% for tag in tags %}
|
{% for tag in tags %}
|
||||||
<li>
|
<li>
|
||||||
<a class="{% block tag_list_item_class %}tag{% endblock %}" href="{% url_for 'dataset.search', tags=tag.name %}" title="{{ tag.display_name }}">{{ tag.display_name|truncate(22) }}</a>
|
<a class="{% block tag_list_item_class %}tag{% endblock %}" href="{% url_for 'dataset.search', tags=tag.name %}" title="{{ tag.display_name }}"></a>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
{% extends "user/dashboard.html" %}
|
||||||
|
|
||||||
|
{% block dashboard_activity_stream_context %}{% endblock %}
|
||||||
|
|
||||||
|
{% block page_primary_action %}
|
||||||
|
{% link_for _('Acquire Dataset'), controller='package', action='search', class_="btn btn-primary", icon="shopping-cart" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block primary_content_inner %}
|
||||||
|
<h2 class="hide-heading">{{ _('Acquired Datasets') }}</h2>
|
||||||
|
{% if acquired_datasets %}
|
||||||
|
{% snippet 'snippets/package_list.html', packages=acquired_datasets %}
|
||||||
|
{% else %}
|
||||||
|
<p class="empty">
|
||||||
|
{{ _('You haven\'t acquired any datasets.') }}
|
||||||
|
{% link_for _('Acquire one now?'), controller='package', action='search' %}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,24 @@
|
||||||
|
import ckan.plugins.toolkit as toolkit
|
||||||
|
from logging import getLogger
|
||||||
|
|
||||||
|
log = getLogger(__name__)
|
||||||
|
|
||||||
|
def ignore_duplicate_keys(key, data, errors, context):
|
||||||
|
"""
|
||||||
|
Validator che consente chiavi duplicate negli extras.
|
||||||
|
"""
|
||||||
|
# Estrarre gli extras dalla richiesta
|
||||||
|
log.debug("controllo extra (data) %s", data)
|
||||||
|
extras = data.get(key, [])
|
||||||
|
|
||||||
|
# Log per il debugging
|
||||||
|
log.debug(f"Contenuto di 'data' per la chiave '{key}': {extras} (tipo: {type(extras)})")
|
||||||
|
|
||||||
|
# Se ci sono duplicati, evita di sollevare un'eccezione
|
||||||
|
if isinstance(extras, list): # Verifica che extras sia una lista
|
||||||
|
log.debug(f"Ignorando chiave duplicata: {extra['key']}")
|
||||||
|
else:
|
||||||
|
log.debug(f"Errore: 'extras' non è una lista, ma è di tipo {type(extras)}")
|
||||||
|
|
||||||
|
## Mantieni la lista aggiornata, che consente chiavi duplicate
|
||||||
|
#data[key] = extras
|
|
@ -1,3 +1,4 @@
|
||||||
webhelpers2
|
webhelpers2
|
||||||
xmltodict
|
xmltodict
|
||||||
pyqrcode
|
pyqrcode
|
||||||
|
sqlalchemy
|
||||||
|
|
|
@ -69,8 +69,7 @@ setup(
|
||||||
# installed, specify them here. If using Python 2.6 or less, then these
|
# installed, specify them here. If using Python 2.6 or less, then these
|
||||||
# have to be included in MANIFEST.in as well.
|
# have to be included in MANIFEST.in as well.
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
package_data={
|
#package_data={},
|
||||||
},
|
|
||||||
|
|
||||||
# Although 'package_data' is the preferred approach, in some case you may
|
# Although 'package_data' is the preferred approach, in some case you may
|
||||||
# need to place data files outside of your packages.
|
# need to place data files outside of your packages.
|
||||||
|
|
Loading…
Reference in New Issue