From 22334d3d50e3f412b64b76c593184e38e7d11674 Mon Sep 17 00:00:00 2001 From: Chas Nelson Date: Mon, 8 Dec 2025 15:50:38 +0000 Subject: [PATCH 1/7] feat: update Python versions and GitHub Actions versions --- .github/workflows/test.yml | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 291ea9d..e79e3ae 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,10 +6,10 @@ jobs: strategy: fail-fast: false matrix: - python-version: [ 2.7, 3.9 ] + python-version: [ 3.9, "3.10" ] steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - name: Install requirements @@ -25,18 +25,19 @@ jobs: fail-fast: false matrix: include: - - ckan-container-version: "2.9-py2" + - ckan-version: "ckan-dev:2.9-py3.9" ckan-postgres-version: "2.9" ckan-solr-version: "2.9-solr8" - - ckan-container-version: "2.9" - ckan-postgres-version: "2.9" - ckan-solr-version: "2.9-solr8" - - ckan-container-version: "2.10" + - ckan-version: "ckan-dev:2.10-py3.10" ckan-postgres-version: "2.10" ckan-solr-version: "2.10" + - ckan-version: "ckan-dev:2.11-py3.10" + ckan-postgres-version: "2.11" + ckan-solr-version: "2.11-solr9" container: - image: openknowledge/ckan-dev:${{ matrix.ckan-container-version }} + image: ckan/${{ matrix.ckan-version }} + options: --user root services: solr: image: ckan/ckan-solr:${{ matrix.ckan-solr-version }} @@ -57,7 +58,7 @@ jobs: CKAN_REDIS_URL: redis://redis:6379/1 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - name: Install requirements run: | pip install -r requirements.txt From d7903192f6a2a16992e994c4bb0aed3a7dd194ba Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Dec 2025 16:00:20 +0000 Subject: [PATCH 2/7] Initial plan From b8029aef95dd44167966760948613fd1179703c0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Dec 2025 16:06:42 +0000 Subject: [PATCH 3/7] Fix Python 3.10+ compatibility and database initialization issues Co-authored-by: ChasNelson1990 <7795189+ChasNelson1990@users.noreply.github.com> --- ckanext/versions/model.py | 12 ++++++++++-- ckanext/versions/tests/test_helpers.py | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/ckanext/versions/model.py b/ckanext/versions/model.py index 7e63796..0bc933d 100644 --- a/ckanext/versions/model.py +++ b/ckanext/versions/model.py @@ -44,8 +44,16 @@ def as_dict(self): def create_tables(): - Version.__table__.create() + from ckan.model import meta + Version.__table__.create(bind=meta.engine, checkfirst=True) def tables_exist(): - return Version.__table__.exists() + from ckan.model import meta + try: + engine = meta.engine + if engine is None: + return False + return Version.__table__.exists(bind=engine) + except Exception: + return False diff --git a/ckanext/versions/tests/test_helpers.py b/ckanext/versions/tests/test_helpers.py index 1426c5b..21d3af5 100644 --- a/ckanext/versions/tests/test_helpers.py +++ b/ckanext/versions/tests/test_helpers.py @@ -1,4 +1,4 @@ -import mock +from unittest import mock import pytest from ckan.plugins import toolkit From 4bea46b6be735eb3badba2539eb734d4cade79ec Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Dec 2025 16:11:11 +0000 Subject: [PATCH 4/7] Improve exception handling in tables_exist() Co-authored-by: ChasNelson1990 <7795189+ChasNelson1990@users.noreply.github.com> --- ckanext/versions/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ckanext/versions/model.py b/ckanext/versions/model.py index 0bc933d..12eeffa 100644 --- a/ckanext/versions/model.py +++ b/ckanext/versions/model.py @@ -55,5 +55,5 @@ def tables_exist(): if engine is None: return False return Version.__table__.exists(bind=engine) - except Exception: + except (AttributeError, TypeError): return False From 691779d69015987cd94449b12917ccbe69d71cb5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Dec 2025 16:25:32 +0000 Subject: [PATCH 5/7] Convert test_auth.py to use pytest fixtures instead of setup() Co-authored-by: ChasNelson1990 <7795189+ChasNelson1990@users.noreply.github.com> --- ckanext/versions/tests/test_auth.py | 130 +++++++++++++++++----------- 1 file changed, 79 insertions(+), 51 deletions(-) diff --git a/ckanext/versions/tests/test_auth.py b/ckanext/versions/tests/test_auth.py index e5b0456..dbea231 100644 --- a/ckanext/versions/tests/test_auth.py +++ b/ckanext/versions/tests/test_auth.py @@ -4,6 +4,61 @@ from ckan.tests import factories, helpers +@pytest.fixture +def org_admin(): + return factories.User() + + +@pytest.fixture +def org_editor(): + return factories.User() + + +@pytest.fixture +def org_member(): + return factories.User() + + +@pytest.fixture +def other_org_admin(): + return factories.User() + + +@pytest.fixture +def admin_user(): + return factories.Sysadmin() + + +@pytest.fixture +def org(org_admin, org_editor, org_member): + return factories.Organization( + users=[ + {'name': org_admin['name'], 'capacity': 'admin'}, + {'name': org_editor['name'], 'capacity': 'editor'}, + {'name': org_member['name'], 'capacity': 'member'}, + ] + ) + + +@pytest.fixture +def other_org(other_org_admin): + return factories.Organization( + users=[ + {'name': other_org_admin['name'], 'capacity': 'admin'}, + ] + ) + + +@pytest.fixture +def private_dataset(org): + return factories.Dataset(owner_org=org['id'], private=True) + + +@pytest.fixture +def public_dataset(org): + return factories.Dataset(owner_org=org['id'], private=False) + + @pytest.mark.usefixtures("clean_db", "versions_setup") class TestVersionsAuth(object): @@ -13,33 +68,6 @@ def _get_context(self, user): 'user': user if isinstance(user, str) else user['name'] } - def setup(self): - # TODO: Refactor to a new pytest approach - self.org_admin = factories.User() - self.org_editor = factories.User() - self.org_member = factories.User() - self.other_org_admin = factories.User() - self.admin_user = factories.Sysadmin() - - self.org = factories.Organization( - users=[ - {'name': self.org_admin['name'], 'capacity': 'admin'}, - {'name': self.org_editor['name'], 'capacity': 'editor'}, - {'name': self.org_member['name'], 'capacity': 'member'}, - ] - ) - - self.other_org = factories.Organization( - users=[ - {'name': self.other_org_admin['name'], 'capacity': 'admin'}, - ] - ) - - self.private_dataset = factories.Dataset(owner_org=self.org['id'], - private=True) - self.public_dataset = factories.Dataset(owner_org=self.org['id'], - private=False) - @pytest.mark.parametrize("user_type, dataset_type", [ ('org_admin', 'private_dataset'), ('org_admin', 'public_dataset'), @@ -48,11 +76,11 @@ def setup(self): ('admin_user', 'private_dataset'), ('admin_user', 'public_dataset'), ]) - def test_create_is_authorized(self, user_type, dataset_type): + def test_create_is_authorized(self, user_type, dataset_type, request): """Test that authorized users can create versions on a given dataset """ - user = getattr(self, user_type) - dataset = getattr(self, dataset_type) + user = request.getfixturevalue(user_type) + dataset = request.getfixturevalue(dataset_type) context = self._get_context(user) assert helpers.call_auth('version_create', context=context, @@ -64,12 +92,12 @@ def test_create_is_authorized(self, user_type, dataset_type): ('other_org_admin', 'private_dataset'), ('other_org_admin', 'public_dataset'), ]) - def test_create_is_unauthorized(self, user_type, dataset_type): + def test_create_is_unauthorized(self, user_type, dataset_type, request): """Test that unauthorized users cannot create versions on a given dataset """ - user = getattr(self, user_type) - dataset = getattr(self, dataset_type) + user = request.getfixturevalue(user_type) + dataset = request.getfixturevalue(dataset_type) context = self._get_context(user) with pytest.raises(toolkit.NotAuthorized): helpers.call_auth( @@ -85,11 +113,11 @@ def test_create_is_unauthorized(self, user_type, dataset_type): ('admin_user', 'private_dataset'), ('admin_user', 'public_dataset'), ]) - def test_delete_is_authorized(self, user_type, dataset_type): + def test_delete_is_authorized(self, user_type, dataset_type, request): """Test that authorized users can delete versions on a given dataset """ - user = getattr(self, user_type) - dataset = getattr(self, dataset_type) + user = request.getfixturevalue(user_type) + dataset = request.getfixturevalue(dataset_type) context = self._get_context(user) assert helpers.call_auth('version_delete', context=context, @@ -101,12 +129,12 @@ def test_delete_is_authorized(self, user_type, dataset_type): ('other_org_admin', 'private_dataset'), ('other_org_admin', 'public_dataset'), ]) - def test_delete_is_unauthorized(self, user_type, dataset_type): + def test_delete_is_unauthorized(self, user_type, dataset_type, request): """Test that unauthorized users cannot delete versions on a given dataset """ - user = getattr(self, user_type) - dataset = getattr(self, dataset_type) + user = request.getfixturevalue(user_type) + dataset = request.getfixturevalue(dataset_type) context = self._get_context(user) with pytest.raises(toolkit.NotAuthorized): helpers.call_auth( @@ -125,11 +153,11 @@ def test_delete_is_unauthorized(self, user_type, dataset_type): ('admin_user', 'public_dataset'), ('other_org_admin', 'public_dataset'), ]) - def test_list_is_authorized(self, user_type, dataset_type): + def test_list_is_authorized(self, user_type, dataset_type, request): """Test that authorized users can list versions of a given dataset """ - user = getattr(self, user_type) - dataset = getattr(self, dataset_type) + user = request.getfixturevalue(user_type) + dataset = request.getfixturevalue(dataset_type) context = self._get_context(user) assert helpers.call_auth('version_list', context=context, @@ -138,12 +166,12 @@ def test_list_is_authorized(self, user_type, dataset_type): @pytest.mark.parametrize("user_type, dataset_type", [ ('other_org_admin', 'private_dataset'), ]) - def test_list_is_unauthorized(self, user_type, dataset_type): + def test_list_is_unauthorized(self, user_type, dataset_type, request): """Test that unauthorized users cannot list versions on a given dataset """ - user = getattr(self, user_type) - dataset = getattr(self, dataset_type) + user = request.getfixturevalue(user_type) + dataset = request.getfixturevalue(dataset_type) context = self._get_context(user) with pytest.raises(toolkit.NotAuthorized): helpers.call_auth( @@ -162,11 +190,11 @@ def test_list_is_unauthorized(self, user_type, dataset_type): ('admin_user', 'public_dataset'), ('other_org_admin', 'public_dataset'), ]) - def test_show_is_authorized(self, user_type, dataset_type): + def test_show_is_authorized(self, user_type, dataset_type, request): """Test that authorized users can view versions of a given dataset """ - user = getattr(self, user_type) - dataset = getattr(self, dataset_type) + user = request.getfixturevalue(user_type) + dataset = request.getfixturevalue(dataset_type) context = self._get_context(user) assert helpers.call_auth('version_show', context=context, @@ -175,12 +203,12 @@ def test_show_is_authorized(self, user_type, dataset_type): @pytest.mark.parametrize("user_type, dataset_type", [ ('other_org_admin', 'private_dataset'), ]) - def test_show_is_unauthorized(self, user_type, dataset_type): + def test_show_is_unauthorized(self, user_type, dataset_type, request): """Test that unauthorized users cannot view versions on a given dataset """ - user = getattr(self, user_type) - dataset = getattr(self, dataset_type) + user = request.getfixturevalue(user_type) + dataset = request.getfixturevalue(dataset_type) context = self._get_context(user) with pytest.raises(toolkit.NotAuthorized): helpers.call_auth( From 9a41504c74d1ce87456a9e66a1e5df6b42805287 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Dec 2025 19:10:38 +0000 Subject: [PATCH 6/7] Add CKAN 2.10+ compatibility for Activity model and enable activity plugin Co-authored-by: ChasNelson1990 <7795189+ChasNelson1990@users.noreply.github.com> --- ckanext/versions/logic/action.py | 10 ++++++++-- ckanext/versions/logic/dataset_version_action.py | 12 +++++++++--- test.ini | 2 +- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/ckanext/versions/logic/action.py b/ckanext/versions/logic/action.py index 901c2d2..d994ce5 100644 --- a/ckanext/versions/logic/action.py +++ b/ckanext/versions/logic/action.py @@ -12,6 +12,12 @@ from ckanext.versions.model import Version +# CKAN 2.10+ moved Activity to a separate module +try: + from ckan.model.activity import Activity +except ImportError: + Activity = core_model.Activity + log = logging.getLogger(__name__) @@ -107,9 +113,9 @@ def resource_version_create(context, data_dict): site_id = toolkit.config.get('ckan.site_id', 'ckan_site_user') creator_user_id = model.User.get(site_id).id - activity = model.Session.query(model.Activity). \ + activity = model.Session.query(Activity). \ filter_by(object_id=resource.package_id). \ - order_by(model.Activity.timestamp.desc()). \ + order_by(Activity.timestamp.desc()). \ first() if not activity: diff --git a/ckanext/versions/logic/dataset_version_action.py b/ckanext/versions/logic/dataset_version_action.py index 138f236..3854d85 100644 --- a/ckanext/versions/logic/dataset_version_action.py +++ b/ckanext/versions/logic/dataset_version_action.py @@ -9,6 +9,12 @@ from ckanext.versions.logic.action import version_show from ckanext.versions.model import Version +# CKAN 2.10+ moved Activity to a separate module +try: + from ckan.model.activity import Activity +except ImportError: + Activity = core_model.Activity + log = logging.getLogger(__name__) @@ -48,11 +54,11 @@ def dataset_version_create(context, data_dict): creator_user_id = context['auth_user_obj'].id if activity_id: - activity = model.Activity.get(activity_id) + activity = Activity.get(activity_id) else: - activity = model.Session.query(model.Activity). \ + activity = model.Session.query(Activity). \ filter_by(object_id=dataset_id). \ - order_by(model.Activity.timestamp.desc()). \ + order_by(Activity.timestamp.desc()). \ first() if not activity: raise toolkit.ObjectNotFound('Activity not found') diff --git a/test.ini b/test.ini index ad35da7..25b8028 100644 --- a/test.ini +++ b/test.ini @@ -14,7 +14,7 @@ use = config:../ckan/test-core.ini # Insert any custom config settings to be used when running your extension's # tests here. -ckan.plugins = versions image_view +ckan.plugins = activity versions image_view # Logging configuration [loggers] From 7011367c71e36f1ad97a0fb7a7ead633888c899c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Dec 2025 19:20:12 +0000 Subject: [PATCH 7/7] Remove CKAN 2.9 support to maintain activity plugin compatibility Co-authored-by: ChasNelson1990 <7795189+ChasNelson1990@users.noreply.github.com> --- .github/workflows/test.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e79e3ae..3299cb9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,9 +25,6 @@ jobs: fail-fast: false matrix: include: - - ckan-version: "ckan-dev:2.9-py3.9" - ckan-postgres-version: "2.9" - ckan-solr-version: "2.9-solr8" - ckan-version: "ckan-dev:2.10-py3.10" ckan-postgres-version: "2.10" ckan-solr-version: "2.10"