diff --git a/MANIFEST.in b/MANIFEST.in
index 597f7a8..3543c0f 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,2 +1,3 @@
recursive-include ckanext/privatedatasets/templates *
-recursive-include ckanext/privatedatasets/fanstatic *
\ No newline at end of file
+recursive-include ckanext/privatedatasets/templates_2.8 *
+recursive-include ckanext/privatedatasets/fanstatic *
diff --git a/ckanext/privatedatasets/controllers/ui_controller.py b/ckanext/privatedatasets/controllers/ui_controller.py
deleted file mode 100644
index 573ae25..0000000
--- a/ckanext/privatedatasets/controllers/ui_controller.py
+++ /dev/null
@@ -1,51 +0,0 @@
-# -*- 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 .
-
-import ckanext.privatedatasets.constants as constants
-import ckan.lib.base as base
-import ckan.model as model
-import ckan.plugins as plugins
-import logging
-
-from ckan.common import _
-
-log = logging.getLogger(__name__)
-
-
-class AcquiredDatasetsControllerUI(base.BaseController):
-
- def user_acquired_datasets(self):
-
- c = plugins.toolkit.c
- context = {
- 'model': model,
- 'session': model.Session,
- 'user': plugins.toolkit.c.user,
- }
-
- # Get user information
- try:
- c.user_dict = plugins.toolkit.get_action('user_show')(context.copy(), {'user_obj': c.userobj})
- c.user_dict['acquired_datasets'] = plugins.toolkit.get_action(constants.ACQUISITIONS_LIST)(context.copy(), None)
- except plugins.toolkit.ObjectNotFound:
- plugins.toolkit.abort(404, _('User not found'))
- except plugins.toolkit.NotAuthorized:
- plugins.toolkit.abort(401, _('Not authorized to see this page'))
-
- return plugins.toolkit.render('user/dashboard_acquired.html')
diff --git a/ckanext/privatedatasets/plugin.py b/ckanext/privatedatasets/plugin.py
index 38ba9a8..908b691 100755
--- a/ckanext/privatedatasets/plugin.py
+++ b/ckanext/privatedatasets/plugin.py
@@ -1,6 +1,7 @@
# -*- 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.
@@ -17,15 +18,16 @@
# You should have received a copy of the GNU Affero General Public License
# along with CKAN Private Dataset Extension. If not, see .
-from __future__ import absolute_import
+from __future__ import absolute_import, unicode_literals
from ckan import model, plugins as p
from ckan.lib import search
from ckan.lib.plugins import DefaultPermissionLabels
from ckan.plugins import toolkit as tk
+from flask import Blueprint
from ckanext.privatedatasets import auth, actions, constants, converters_validators as conv_val, db, helpers
-
+from ckanext.privatedatasets.views import acquired_datasets
HIDDEN_FIELDS = [constants.ALLOWED_USERS, constants.SEARCHABLE]
@@ -35,7 +37,7 @@ class PrivateDatasets(p.SingletonPlugin, tk.DefaultDatasetForm, DefaultPermissio
p.implements(p.IDatasetForm)
p.implements(p.IAuthFunctions)
p.implements(p.IConfigurer)
- p.implements(p.IRoutes, inherit=True)
+ p.implements(p.IBlueprint)
p.implements(p.IActions)
p.implements(p.IPackageController, inherit=True)
p.implements(p.ITemplateHelpers)
@@ -128,22 +130,22 @@ class PrivateDatasets(p.SingletonPlugin, tk.DefaultDatasetForm, DefaultPermissio
def update_config(self, config):
# Add this plugin's templates dir to CKAN's extra_template_paths, so
# that CKAN will use this plugin's custom templates.
- tk.add_template_directory(config, 'templates')
+ if p.toolkit.check_ckan_version(min_version='2.8'):
+ tk.add_template_directory(config, 'templates_2.8')
+ else:
+ tk.add_template_directory(config, 'templates')
# Register this plugin's fanstatic directory with CKAN.
- tk.add_resource('fanstatic', 'privatedatasets')
+ tk.add_resource(b'fanstatic', b'privatedatasets')
######################################################################
- ############################## IROUTES ###############################
+ ############################# IBLUEPRINT #############################
######################################################################
- def before_map(self, m):
- # DataSet acquired notification
- m.connect('user_acquired_datasets', '/dashboard/acquired', ckan_icon='shopping-cart',
- controller='ckanext.privatedatasets.controllers.ui_controller:AcquiredDatasetsControllerUI',
- action='user_acquired_datasets', conditions=dict(method=['GET']))
-
- return m
+ def get_blueprint(self):
+ blueprint = Blueprint('privatedatasets', self.__module__)
+ blueprint.add_url_rule('/dashboard/acquired', 'acquired_datasets', acquired_datasets)
+ return blueprint
######################################################################
############################## IACTIONS ##############################
diff --git a/ckanext/privatedatasets/templates/user/dashboard.html b/ckanext/privatedatasets/templates/user/dashboard.html
index b172e88..1d54bfc 100644
--- a/ckanext/privatedatasets/templates/user/dashboard.html
+++ b/ckanext/privatedatasets/templates/user/dashboard.html
@@ -18,7 +18,7 @@
{{ _('You haven\'t acquired any datasets.') }}
{% link_for _('Acquire one now?'), controller='package', action='search' %}
{% endif %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/ckanext/privatedatasets/templates_2.8/package/snippets/package_basic_fields.html b/ckanext/privatedatasets/templates_2.8/package/snippets/package_basic_fields.html
new file mode 100644
index 0000000..b240f46
--- /dev/null
+++ b/ckanext/privatedatasets/templates_2.8/package/snippets/package_basic_fields.html
@@ -0,0 +1,107 @@
+{% ckan_extends %}
+
+{% block package_basic_fields_org %}
+
+ {% resource 'privatedatasets/allowed_users.js' %}
+
+ {# if we have a default group then this wants remembering #}
+ {% if data.group_id %}
+
+ {% endif %}
+
+ {% set dataset_is_draft = data.get('state', 'draft').startswith('draft') or data.get('state', 'none') == 'none' %}
+ {% set dataset_has_organization = data.owner_org or data.group_id %}
+ {% set organizations_available = h.organizations_available('create_dataset') %}
+ {% set user_is_sysadmin = h.check_access('sysadmin') %}
+ {% set show_organizations_selector = organizations_available and (user_is_sysadmin or dataset_is_draft) %}
+ {% set editing = 'id' in data %}
+
+ {% if show_organizations_selector and show_visibility_selector %}
+
+ {% endif %}
+
+ {% if show_organizations_selector %}
+ {% set existing_org = data.owner_org or data.group_id %}
+
+
+
+
+ {% trans %}
+ Searchable datasets can be searched by anyone, while not-searchable datasets can only be accessed by entering directly its URL.
+ {% endtrans %}
+
+
+
+ {% endblock %}
+
+
+ {% if show_organizations_selector and show_visibility_selector %}
+
+ {% endif %}
+
+ {% set users_attrs = {'data-module': 'autocomplete', 'data-module-tags': '', 'data-module-source': '/api/2/util/user/autocomplete?q=?'} %}
+ {{ form.input('allowed_users_str', label=_('Allowed Users'), id='field-allowed_users_str', placeholder=_('Allowed Users'), value=h.get_allowed_users_str(data.allowed_users), error=errors.custom_text, classes=['control-full'], attrs=users_attrs) }}
+
+
+ {% if editing and h.show_acquire_url_on_edit() or not editing and h.show_acquire_url_on_create() %}
+ {{ form.input('acquire_url', label=_('Acquire URL'), id='field-acquire_url', placeholder=_('http://example.com/acquire/'), value=data.acquire_url, error=errors.custom_text, classes=['control-medium']) }}
+ {% else %}
+
+ {% endif %}
+
+ {% if data.id and h.check_access('package_delete', {'id': data.id}) and data.state != 'active' %}
+
+
+
+
+
+
+ {% endif %}
+
+{% endblock %}
diff --git a/ckanext/privatedatasets/templates_2.8/snippets/acquire_button.html b/ckanext/privatedatasets/templates_2.8/snippets/acquire_button.html
new file mode 100644
index 0000000..ca49b29
--- /dev/null
+++ b/ckanext/privatedatasets/templates_2.8/snippets/acquire_button.html
@@ -0,0 +1,15 @@
+{#
+
+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 %}
+
+#}
+
+
+ {{ _('Acquire') }}
+
diff --git a/ckanext/privatedatasets/templates_2.8/snippets/package_item.html b/ckanext/privatedatasets/templates_2.8/snippets/package_item.html
new file mode 100755
index 0000000..5db5776
--- /dev/null
+++ b/ckanext/privatedatasets/templates_2.8/snippets/package_item.html
@@ -0,0 +1,82 @@
+{#
+Displays a single of dataset.
+
+package - A package to display.
+item_class - The class name to use on the list item.
+hide_resources - If true hides the resources (default: false).
+banner - If true displays a popular banner (default: false).
+truncate - The length to trucate the description to (default: 180)
+truncate_title - The length to truncate the title to (default: 80).
+
+Example:
+
+ {% snippet 'snippets/package_item.html', package=c.datasets[0] %}
+
+#}
+{% 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 'privatedatasets/custom.css' %}
+
+
+ {{ _('You haven\'t acquired any datasets.') }}
+ {% link_for _('Acquire one now?'), controller='package', action='search' %}
+
+ {% endif %}
+{% endblock %}
diff --git a/ckanext/privatedatasets/tests/test_controller_ui.py b/ckanext/privatedatasets/tests/test_controller_ui.py
deleted file mode 100644
index 3f61cff..0000000
--- a/ckanext/privatedatasets/tests/test_controller_ui.py
+++ /dev/null
@@ -1,110 +0,0 @@
-# -*- 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 .
-
-import ckanext.privatedatasets.controllers.ui_controller as controller
-import unittest
-
-from mock import MagicMock, ANY
-from parameterized import parameterized
-
-
-class UIControllerTest(unittest.TestCase):
-
- def setUp(self):
-
- # Get the instance
- self.instanceUI = controller.AcquiredDatasetsControllerUI()
-
- # Load the mocks
- self._plugins = controller.plugins
- controller.plugins = MagicMock()
-
- self._model = controller.model
- controller.model = MagicMock()
-
- # Set exceptions
- controller.plugins.toolkit.ObjectNotFound = self._plugins.toolkit.ObjectNotFound
- controller.plugins.toolkit.NotAuthorized = self._plugins.toolkit.NotAuthorized
-
- def tearDown(self):
- # Unmock
- controller.plugins = self._plugins
- controller.model = self._model
-
- @parameterized.expand([
- (controller.plugins.toolkit.ObjectNotFound, 404),
- (controller.plugins.toolkit.NotAuthorized, 401)
- ])
- def test_exceptions_loading_users(self, exception, expected_status):
-
- # Configure the mock
- user_show = MagicMock(side_effect=exception)
- controller.plugins.toolkit.get_action = MagicMock(return_value=user_show)
-
- # Call the function
- self.instanceUI.user_acquired_datasets()
-
- # Assertations
- expected_context = {
- 'model': controller.model,
- 'session': controller.model.Session,
- 'user': controller.plugins.toolkit.c.user
- }
-
- user_show.assert_called_once_with(expected_context, {'user_obj': controller.plugins.toolkit.c.userobj})
- controller.plugins.toolkit.abort.assert_called_once_with(expected_status, ANY)
-
- def test_no_error_loading_users(self):
-
- user = 'example_user_test'
- controller.plugins.toolkit.c.user = user
-
- # actions
- default_user = {'user_name': 'test', 'another_val': 'example value'}
- user_show = MagicMock(return_value=default_user)
- acquisitions_list = MagicMock()
- def _get_action(action):
- if action == 'user_show':
- return user_show
- else:
- return acquisitions_list
-
- controller.plugins.toolkit.get_action = MagicMock(side_effect=_get_action)
-
- # Call the function
- returned = self.instanceUI.user_acquired_datasets()
-
- # User_show called correctly
- expected_context = {
- 'model': controller.model,
- 'session': controller.model.Session,
- 'user': controller.plugins.toolkit.c.user
- }
-
- user_show.assert_called_once_with(expected_context, {'user_obj': controller.plugins.toolkit.c.userobj})
-
- # Query called correctry
- expected_user = default_user.copy()
- expected_user['acquired_datasets'] = acquisitions_list.return_value
- acquisitions_list.assert_called_with(expected_context, None)
- self.assertEquals(expected_user, controller.plugins.toolkit.c.user_dict)
-
- # Check that the render method has been called and that its result has been returned
- self.assertEquals(controller.plugins.toolkit.render.return_value, returned)
- controller.plugins.toolkit.render.assert_called_once_with('user/dashboard_acquired.html')
diff --git a/ckanext/privatedatasets/tests/test_plugin.py b/ckanext/privatedatasets/tests/test_plugin.py
index 03c38c5..49502c1 100644
--- a/ckanext/privatedatasets/tests/test_plugin.py
+++ b/ckanext/privatedatasets/tests/test_plugin.py
@@ -19,11 +19,13 @@
import unittest
import copy
-import ckanext.privatedatasets.plugin as plugin
+from flask import Blueprint
from mock import MagicMock
from parameterized import parameterized
+import ckanext.privatedatasets.plugin as plugin
+
class PluginTest(unittest.TestCase):
@@ -51,7 +53,7 @@ class PluginTest(unittest.TestCase):
(plugin.p.IDatasetForm,),
(plugin.p.IAuthFunctions,),
(plugin.p.IConfigurer,),
- (plugin.p.IRoutes,),
+ (plugin.p.IBlueprint,),
(plugin.p.IActions,),
(plugin.p.IPackageController,),
(plugin.p.ITemplateHelpers,)
@@ -83,18 +85,15 @@ class PluginTest(unittest.TestCase):
self.privateDatasets.update_config(config)
# Test that functions are called as expected
- plugin.tk.add_template_directory.assert_called_once_with(config, 'templates')
+ if self._tk.check_ckan_version(min_version='2.8'):
+ plugin.tk.add_template_directory.assert_called_once_with(config, 'templates_2.8')
+ else:
+ plugin.tk.add_template_directory.assert_called_once_with(config, 'templates')
plugin.tk.add_resource('fanstatic', 'privatedatasets')
- def test_map(self):
+ def test_get_blueprint(self):
# Call the method
- m = MagicMock()
- self.privateDatasets.before_map(m)
-
- # Test that the connect method has been called
- m.connect.assert_any_call('user_acquired_datasets', '/dashboard/acquired', ckan_icon='shopping-cart',
- controller='ckanext.privatedatasets.controllers.ui_controller:AcquiredDatasetsControllerUI',
- action='user_acquired_datasets', conditions=dict(method=['GET']))
+ self.assertIsInstance(self.privateDatasets.get_blueprint(), Blueprint)
@parameterized.expand([
('package_acquired', plugin.actions.package_acquired),
diff --git a/ckanext/privatedatasets/tests/test_selenium.py b/ckanext/privatedatasets/tests/test_selenium.py
index a624fe6..3c7726b 100644
--- a/ckanext/privatedatasets/tests/test_selenium.py
+++ b/ckanext/privatedatasets/tests/test_selenium.py
@@ -47,7 +47,7 @@ class TestSelenium(unittest.TestCase):
@classmethod
def setUpClass(cls):
env = os.environ.copy()
- env['DEBUG'] = 'True'
+ env['DEBUG'] = 'False'
cls._process = Popen(['paster', 'serve', 'test.ini'], env=env)
@classmethod
@@ -260,7 +260,7 @@ class TestSelenium(unittest.TestCase):
def check_acquired(self, dataset, dataset_url, acquired, private):
driver = self.driver
driver.get(self.base_url + 'dashboard')
- driver.find_element_by_link_text('Acquired Datasets').click()
+ WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.LINK_TEXT, 'Acquired Datasets'))).click()
if acquired and private:
# This message could not be shown when the user has acquired at least one dataset
diff --git a/ckanext/privatedatasets/views.py b/ckanext/privatedatasets/views.py
new file mode 100644
index 0000000..05f6ea2
--- /dev/null
+++ b/ckanext/privatedatasets/views.py
@@ -0,0 +1,45 @@
+# -*- 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 .
+
+from __future__ import absolute_import, unicode_literals
+
+from ckan import logic
+from ckan.common import _, g
+from ckan.lib import base
+from ckan.plugins import toolkit
+
+from ckanext.privatedatasets import constants
+
+
+def acquired_datasets():
+ context = {'for_view': True, 'user': g.user, 'auth_user_obj': g.userobj}
+ data_dict = {'user_obj': g.userobj}
+ try:
+ user_dict = logic.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 base.render('user/dashboard_acquired.html', extra_vars)