From bb10fbdb796e6dcfd66094aad403b345ddcbc69d Mon Sep 17 00:00:00 2001 From: Quinten Date: Mon, 23 Feb 2026 15:56:37 +0100 Subject: [PATCH 1/4] Handle distribution/sample/analytics in profiles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract distribution parsing into _parse_distribution and use it when building dataset dicts; collect distribution URIs into dataset_dict["distribution"] and emit DCAT.distribution triples when graphing. Add ADMS.sample handling in DCAT-AP3 parsing and round‑trip graph serialization for dataset samples. Extend Health DCAT-AP profile to parse/serialize analytics distributions and include publisherNote/publisherType on agents, with helpers to read/write those properties. Update test to use .get() for analytics presence. Overall reduces duplicated distribution parsing code and adds support for sample/analytics agent metadata. --- ckanext/dcat/profiles/euro_dcat_ap_3.py | 14 ++ ckanext/dcat/profiles/euro_dcat_ap_base.py | 195 +++++++++--------- ckanext/dcat/profiles/euro_health_dcat_ap.py | 111 +++++++++- .../test_euro_health_dcat_ap_profile_parse.py | 2 +- 4 files changed, 226 insertions(+), 96 deletions(-) diff --git a/ckanext/dcat/profiles/euro_dcat_ap_3.py b/ckanext/dcat/profiles/euro_dcat_ap_3.py index a99cadfe..fcccbc09 100644 --- a/ckanext/dcat/profiles/euro_dcat_ap_3.py +++ b/ckanext/dcat/profiles/euro_dcat_ap_3.py @@ -30,6 +30,16 @@ def parse_dataset(self, dataset_dict, dataset_ref): # DCAT AP v2 scheming fields dataset_dict = self._parse_dataset_v2_scheming(dataset_dict, dataset_ref) + sample_uris = [] + for sample in self.g.objects(dataset_ref, ADMS.sample): + if (sample, RDF.type, DCAT.Distribution) in self.g: + resource_dict = self._parse_distribution(sample) + dataset_dict["resources"].append(resource_dict) + sample_uris.append(str(sample)) + + if sample_uris: + dataset_dict["sample"] = sample_uris + # DCAT AP v3: hasVersion values = self._object_value_list(dataset_ref, DCAT.hasVersion) if values: @@ -63,6 +73,10 @@ def graph_from_catalog(self, catalog_dict, catalog_ref): def _graph_from_dataset_v3(self, dataset_dict, dataset_ref): + for sample_uri in dataset_dict.get("sample", []): + if sample_uri: + self.g.add((dataset_ref, ADMS.sample, URIRef(sample_uri))) + dataset_series = False # TODO: support custom type names (ckan/ckanext-dataset-series#6) diff --git a/ckanext/dcat/profiles/euro_dcat_ap_base.py b/ckanext/dcat/profiles/euro_dcat_ap_base.py index ba9f1f2a..42a18d8f 100644 --- a/ckanext/dcat/profiles/euro_dcat_ap_base.py +++ b/ckanext/dcat/profiles/euro_dcat_ap_base.py @@ -127,7 +127,6 @@ def _parse_dataset_base(self, dataset_dict, dataset_ref): ("has_version", DCT.hasVersion), ("is_version_of", DCT.isVersionOf), ("source", DCT.source), - ("sample", ADMS.sample), ): values = self._object_value_list(dataset_ref, predicate) if values: @@ -218,99 +217,14 @@ def _parse_dataset_base(self, dataset_dict, dataset_ref): ) # Resources + distribution_uris = [] for distribution in self._distributions(dataset_ref): - - resource_dict = {} - - multilingual_fields = self._multilingual_resource_fields() - - # Simple values - for key, predicate in ( - ("access_url", DCAT.accessURL), - ("download_url", DCAT.downloadURL), - ("issued", DCT.issued), - ("modified", DCT.modified), - ("status", ADMS.status), - ("license", DCT.license), - ("rights", DCT.rights), - ): - multilingual = key in multilingual_fields - value = self._object_value( - distribution, predicate, multilingual=multilingual - ) - if value: - resource_dict[key] = value - - # Multilingual core fields - for key, predicate in ( - ("name", DCT.title), - ("description", DCT.description) - ): - if f"{key}_translated" in multilingual_fields: - value = self._object_value( - distribution, predicate, multilingual=True - ) - resource_dict[f"{key}_translated"] = value - resource_dict[f"{key}"] = value.get(self._default_lang) - else: - value = self._object_value(distribution, predicate) - if value: - resource_dict[key] = value - - # URL - - resource_dict["url"] = self._object_value( - distribution, DCAT.downloadURL - ) or self._object_value(distribution, DCAT.accessURL) - - # Lists - for key, predicate in ( - ("language", DCT.language), - ("documentation", FOAF.page), - ("conforms_to", DCT.conformsTo), - ): - values = self._object_value_list(distribution, predicate) - if values: - resource_dict[key] = json.dumps(values) - - # Format and media type - normalize_ckan_format = toolkit.asbool( - config.get("ckanext.dcat.normalize_ckan_format", True) - ) - imt, label = self._distribution_format(distribution, normalize_ckan_format) - - if imt: - resource_dict["mimetype"] = imt - - if label: - resource_dict["format"] = label - elif imt: - resource_dict["format"] = imt - - # Size - size = self._object_value_int(distribution, DCAT.byteSize) - if size is not None: - resource_dict["size"] = size - - # Checksum - for checksum in self.g.objects(distribution, SPDX.checksum): - algorithm = self._object_value(checksum, SPDX.algorithm) - checksum_value = self._object_value(checksum, SPDX.checksumValue) - if algorithm: - resource_dict["hash_algorithm"] = algorithm - if checksum_value: - resource_dict["hash"] = checksum_value - - # Distribution URI (explicitly show the missing ones) - resource_dict["uri"] = ( - str(distribution) if isinstance(distribution, term.URIRef) else "" - ) - - # Remember the (internal) distribution reference for referencing in - # further profiles, e.g. for adding more properties - resource_dict["distribution_ref"] = str(distribution) - + resource_dict = self._parse_distribution(distribution) dataset_dict["resources"].append(resource_dict) + distribution_uris.append(str(distribution)) + + if distribution_uris: + dataset_dict["distribution"] = distribution_uris if self.compatibility_mode: # Tweak the resulting dict to make it compatible with previous @@ -329,6 +243,98 @@ def _parse_dataset_base(self, dataset_dict, dataset_ref): return dataset_dict + def _parse_distribution(self, distribution): + resource_dict = {} + + multilingual_fields = self._multilingual_resource_fields() + + # Simple values + for key, predicate in ( + ("access_url", DCAT.accessURL), + ("download_url", DCAT.downloadURL), + ("issued", DCT.issued), + ("modified", DCT.modified), + ("status", ADMS.status), + ("license", DCT.license), + ("rights", DCT.rights), + ): + multilingual = key in multilingual_fields + value = self._object_value( + distribution, predicate, multilingual=multilingual + ) + if value: + resource_dict[key] = value + + # Multilingual core fields + for key, predicate in ( + ("name", DCT.title), + ("description", DCT.description) + ): + if f"{key}_translated" in multilingual_fields: + value = self._object_value( + distribution, predicate, multilingual=True + ) + resource_dict[f"{key}_translated"] = value + resource_dict[f"{key}"] = value.get(self._default_lang) + else: + value = self._object_value(distribution, predicate) + if value: + resource_dict[key] = value + + # URL + resource_dict["url"] = self._object_value( + distribution, DCAT.downloadURL + ) or self._object_value(distribution, DCAT.accessURL) + + # Lists + for key, predicate in ( + ("language", DCT.language), + ("documentation", FOAF.page), + ("conforms_to", DCT.conformsTo), + ): + values = self._object_value_list(distribution, predicate) + if values: + resource_dict[key] = json.dumps(values) + + # Format and media type + normalize_ckan_format = toolkit.asbool( + config.get("ckanext.dcat.normalize_ckan_format", True) + ) + imt, label = self._distribution_format(distribution, normalize_ckan_format) + + if imt: + resource_dict["mimetype"] = imt + + if label: + resource_dict["format"] = label + elif imt: + resource_dict["format"] = imt + + # Size + size = self._object_value_int(distribution, DCAT.byteSize) + if size is not None: + resource_dict["size"] = size + + # Checksum + for checksum in self.g.objects(distribution, SPDX.checksum): + algorithm = self._object_value(checksum, SPDX.algorithm) + checksum_value = self._object_value(checksum, SPDX.checksumValue) + if algorithm: + resource_dict["hash_algorithm"] = algorithm + if checksum_value: + resource_dict["hash"] = checksum_value + + # Distribution URI (explicitly show the missing ones) + resource_dict["uri"] = ( + str(distribution) if isinstance(distribution, term.URIRef) else "" + ) + + # Remember the (internal) distribution reference for referencing in + # further profiles, e.g. for adding more properties + resource_dict["distribution_ref"] = str(distribution) + + return resource_dict + def _graph_from_dataset_base(self, dataset_dict, dataset_ref): g = self.g @@ -387,7 +393,6 @@ def _graph_from_dataset_base(self, dataset_dict, dataset_ref): ("has_version", DCT.hasVersion, None, URIRefOrLiteral), ("is_version_of", DCT.isVersionOf, None, URIRefOrLiteral), ("source", DCT.source, None, URIRefOrLiteral), - ("sample", ADMS.sample, None, URIRefOrLiteral, DCAT.Distribution), ] self._add_list_triples_from_dict(dataset_dict, dataset_ref, items) @@ -751,6 +756,10 @@ def _graph_from_dataset_base(self, dataset_dict, dataset_ref): g.add((distribution, SPDX.checksum, checksum)) + for dist_uri in dataset_dict.get("distribution", []): + if dist_uri: + g.add((dataset_ref, DCAT.distribution, URIRef(dist_uri))) + def _graph_from_catalog_base(self, catalog_dict, catalog_ref): g = self.g diff --git a/ckanext/dcat/profiles/euro_health_dcat_ap.py b/ckanext/dcat/profiles/euro_health_dcat_ap.py index b80a5fe6..2e8eac5d 100644 --- a/ckanext/dcat/profiles/euro_health_dcat_ap.py +++ b/ckanext/dcat/profiles/euro_health_dcat_ap.py @@ -45,6 +45,16 @@ def parse_dataset(self, dataset_dict, dataset_ref): dataset_dict = self._parse_health_fields(dataset_dict, dataset_ref) + analytics_uris = [] + for analytics_dist in self.g.objects(dataset_ref, HEALTHDCATAP.analytics): + if (analytics_dist, RDF.type, DCAT.Distribution) in self.g: + resource_dict = self._parse_distribution(analytics_dist) + dataset_dict["resources"].append(resource_dict) + analytics_uris.append(str(analytics_dist)) + + if analytics_uris: + dataset_dict["analytics"] = analytics_uris + return dataset_dict def _parse_health_fields(self, dataset_dict, dataset_ref): @@ -92,7 +102,6 @@ def __parse_healthdcat_stringvalues( self, dataset_dict, dataset_ref, multilingual_fields ): for (key, predicate,) in ( - ("analytics", HEALTHDCATAP.analytics), ("code_values", HEALTHDCATAP.hasCodeValues), ("coding_system", HEALTHDCATAP.hasCodingSystem), ("health_category", HEALTHDCATAP.healthCategory), @@ -191,7 +200,6 @@ def graph_from_dataset(self, dataset_dict, dataset_ref): # key, predicate, fallbacks, _type, _class list_items = [ - ("analytics", HEALTHDCATAP.analytics, None, URIRefOrLiteral), ("code_values", HEALTHDCATAP.hasCodeValues, None, URIRefOrLiteral), ("coding_system", HEALTHDCATAP.hasCodingSystem, None, URIRefOrLiteral), ("health_category", HEALTHDCATAP.healthCategory, None, URIRefOrLiteral), @@ -254,6 +262,10 @@ def graph_from_dataset(self, dataset_dict, dataset_ref): # Dataset-level retention period self._add_retention_period(dataset_ref, dataset_dict.get("retention_period", [])) + for analytics_uri in dataset_dict.get("analytics", []): + if analytics_uri: + self.g.add((dataset_ref, HEALTHDCATAP.analytics, URIRef(analytics_uri))) + for resource_dict in dataset_dict.get("resources", []): distribution_ref = CleanedURIRef(resource_uri(resource_dict)) self._add_retention_period(distribution_ref, resource_dict.get("retention_period", [])) @@ -331,5 +343,100 @@ def _add_quality_annotation(self, dataset_dict, dataset_ref): self.g.add((annotation_ref, predicate, URIRef(uri))) + def _agents_details(self, subject, predicate): + """ + Override of parent method to include healthdcatap:publisherNote and + healthdcatap:publisherType for agents. + + Extends the standard agent details with HealthDCAT-AP specific properties. + """ + # Get the standard agent details from parent + agents = super()._agents_details(subject, predicate) + + # Add healthdcatap:publisherNote to each agent + for agent in agents: + agent_uri = agent.get("uri") + if agent_uri: + agent_ref = URIRef(agent_uri) + else: + # If no URI, we need to find the BNode agents + for ag in self.g.objects(subject, predicate): + agent_ref = ag + # Check if this is the publisher note we're looking for + note = self._object_value(ag, HEALTHDCATAP.publisherNote, multilingual=True) + if note: + agent["publisher_note"] = note + publisher_type = self._object_value_list( + ag, HEALTHDCATAP.publisherType + ) + if publisher_type: + agent["publisher_type"] = publisher_type + continue + + # Get publisherNote (with multilingual support) + note = self._object_value(agent_ref, HEALTHDCATAP.publisherNote, multilingual=True) + if note: + agent["publisher_note"] = note + + publisher_type = self._object_value_list( + agent_ref, HEALTHDCATAP.publisherType + ) + if publisher_type: + agent["publisher_type"] = publisher_type + + return agents + + def _add_agents( + self, dataset_ref, dataset_dict, agent_key, rdf_predicate, first_only=False + ): + """ + Override of parent method to include healthdcatap:publisherNote and + healthdcatap:publisherType for agents. + + Extends the standard agent serialization with HealthDCAT-AP specific properties. + """ + # Call parent method to add standard agent properties + super()._add_agents(dataset_ref, dataset_dict, agent_key, rdf_predicate, first_only) + + # Now add healthdcatap:publisherNote for each agent + agent = dataset_dict.get(agent_key) + if isinstance(agent, list) and len(agent) and self._not_empty_dict(agent[0]): + agents = [agent[0]] if first_only else agent + + for agent_dict in agents: + agent_uri = agent_dict.get("uri") + if agent_uri: + agent_ref = CleanedURIRef(agent_uri) + else: + # For BNode agents, we need to find them in the graph + # This is more complex, so we'll skip for now as they should have URIs + continue + + # Add publisherNote if it exists + publisher_note = agent_dict.get("publisher_note") + if publisher_note: + if isinstance(publisher_note, dict): + # Multilingual support + for lang, note in publisher_note.items(): + if note: + self.g.add((agent_ref, HEALTHDCATAP.publisherNote, Literal(note, lang=lang))) + elif isinstance(publisher_note, list): + # Multiple notes + for note in publisher_note: + if note: + self.g.add((agent_ref, HEALTHDCATAP.publisherNote, Literal(note))) + else: + # Single string note + self.g.add((agent_ref, HEALTHDCATAP.publisherNote, Literal(publisher_note))) + + self._add_triple_from_dict( + agent_dict, + agent_ref, + HEALTHDCATAP.publisherType, + "publisher_type", + _type=URIRefOrLiteral, + list_value=True, + ) + def graph_from_catalog(self, catalog_dict, catalog_ref): super().graph_from_catalog(catalog_dict, catalog_ref) diff --git a/ckanext/dcat/tests/profiles/health_dcat_ap/test_euro_health_dcat_ap_profile_parse.py b/ckanext/dcat/tests/profiles/health_dcat_ap/test_euro_health_dcat_ap_profile_parse.py index 949dccfd..ddf9f69f 100644 --- a/ckanext/dcat/tests/profiles/health_dcat_ap/test_euro_health_dcat_ap_profile_parse.py +++ b/ckanext/dcat/tests/profiles/health_dcat_ap/test_euro_health_dcat_ap_profile_parse.py @@ -81,7 +81,7 @@ def test_e2e_dcat_to_ckan(self): assert dataset["modified"] == "2024-12-31T23:59:59+00:00" assert dataset["temporal_resolution"] == "P1D" - assert dataset["analytics"] == ["http://example.com/analytics"] + assert dataset.get("analytics") == ["http://example.com/analytics"] assert sorted(dataset["code_values"]) == [ "http://example.com/code1", "http://example.com/code2", From b595576712a0b351fb7dd2f78f4f86fd4e376ce9 Mon Sep 17 00:00:00 2001 From: Quinten Date: Wed, 25 Feb 2026 12:04:56 +0100 Subject: [PATCH 2/4] Add 'sample' support to DCAT-AP v3 profile Add handling for the dataset 'sample' property in the European DCAT-AP 3 profile by using _add_list_triples_from_dict with ADMS.sample (allowing URI or literal). Update schema (dcat_ap_full.yaml) to include a 'sample' dataset field. Adjust tests: remove legacy v2 sample assertions, add parsing/serialization checks for v3, and update the example dataset JSON to include sample values. These changes enable parsing and serializing ADMS.sample values for DCAT-AP v3 datasets. --- ckanext/dcat/profiles/euro_dcat_ap_3.py | 7 ++++--- ckanext/dcat/schemas/dcat_ap_full.yaml | 5 +++++ .../profiles/dcat_ap/test_euro_dcatap_profile_parse.py | 1 - .../profiles/dcat_ap/test_euro_dcatap_profile_serialize.py | 2 -- .../profiles/dcat_ap_3/test_euro_dcatap_3_profile_parse.py | 4 ++++ .../dcat_ap_3/test_euro_dcatap_3_profile_serialize.py | 4 ++++ examples/ckan/ckan_full_dataset_dcat_ap.json | 4 ++++ test.ini | 3 ++- 8 files changed, 23 insertions(+), 7 deletions(-) diff --git a/ckanext/dcat/profiles/euro_dcat_ap_3.py b/ckanext/dcat/profiles/euro_dcat_ap_3.py index fcccbc09..9de65854 100644 --- a/ckanext/dcat/profiles/euro_dcat_ap_3.py +++ b/ckanext/dcat/profiles/euro_dcat_ap_3.py @@ -73,9 +73,10 @@ def graph_from_catalog(self, catalog_dict, catalog_ref): def _graph_from_dataset_v3(self, dataset_dict, dataset_ref): - for sample_uri in dataset_dict.get("sample", []): - if sample_uri: - self.g.add((dataset_ref, ADMS.sample, URIRef(sample_uri))) + items = [ + ("sample", ADMS.sample, None, URIRefOrLiteral), + ] + self._add_list_triples_from_dict(dataset_dict, dataset_ref, items) dataset_series = False diff --git a/ckanext/dcat/schemas/dcat_ap_full.yaml b/ckanext/dcat/schemas/dcat_ap_full.yaml index 39a7ae67..6e10d405 100644 --- a/ckanext/dcat/schemas/dcat_ap_full.yaml +++ b/ckanext/dcat/schemas/dcat_ap_full.yaml @@ -375,6 +375,11 @@ dataset_fields: # validators: ignore_missing scheming_multiple_text # TODO: implement separately as part of wider HVD support +- field_name: sample + label: Sample + validators: ignore_missing + help_text: A sample distribution of the dataset. + # Note: if not provided, this will be autogenerated - field_name: uri label: URI diff --git a/ckanext/dcat/tests/profiles/dcat_ap/test_euro_dcatap_profile_parse.py b/ckanext/dcat/tests/profiles/dcat_ap/test_euro_dcatap_profile_parse.py index d0252a11..be7b7ac3 100644 --- a/ckanext/dcat/tests/profiles/dcat_ap/test_euro_dcatap_profile_parse.py +++ b/ckanext/dcat/tests/profiles/dcat_ap/test_euro_dcatap_profile_parse.py @@ -148,7 +148,6 @@ def _get_extra_value_as_list(key): assert (sorted(_get_extra_value_as_list('source')) == [u'https://data.some.org/catalog/datasets/source-dataset-1', u'https://data.some.org/catalog/datasets/source-dataset-2']) - assert sorted(_get_extra_value_as_list('sample')) == [u'https://data.some.org/catalog/datasets/9df8df51-63db-37a8-e044-0003ba9b0d98/sample'] # Dataset URI assert _get_extra_value('uri') == u'https://data.some.org/catalog/datasets/9df8df51-63db-37a8-e044-0003ba9b0d98' diff --git a/ckanext/dcat/tests/profiles/dcat_ap/test_euro_dcatap_profile_serialize.py b/ckanext/dcat/tests/profiles/dcat_ap/test_euro_dcatap_profile_serialize.py index 88826149..e8a2f40e 100644 --- a/ckanext/dcat/tests/profiles/dcat_ap/test_euro_dcatap_profile_serialize.py +++ b/ckanext/dcat/tests/profiles/dcat_ap/test_euro_dcatap_profile_serialize.py @@ -100,7 +100,6 @@ def test_graph_from_dataset(self): {'key': 'has_version', 'value': '[\"https://data.some.org/catalog/datasets/derived-dataset-1\", \"https://data.some.org/catalog/datasets/derived-dataset-2\"]'}, {'key': 'is_version_of', 'value': '[\"https://data.some.org/catalog/datasets/original-dataset\"]'}, {'key': 'source', 'value': '[\"https://data.some.org/catalog/datasets/source-dataset-1\", \"https://data.some.org/catalog/datasets/source-dataset-2\", \"test_source\"]'}, - {'key': 'sample', 'value': '[\"https://data.some.org/catalog/datasets/9df8df51-63db-37a8-e044-0003ba9b0d98/sample\", \"test_sample\"]'}, ] } extras = self._extras(dataset) @@ -142,7 +141,6 @@ def test_graph_from_dataset(self): ('has_version', DCT.hasVersion, URIRef), ('is_version_of', DCT.isVersionOf, URIRef), ('source', DCT.source, [URIRef, URIRef, Literal]), - ('sample', ADMS.sample, [URIRef, Literal]), ]: values = json.loads(extras[item[0]]) assert len([t for t in g.triples((dataset_ref, item[1], None))]) == len(values) diff --git a/ckanext/dcat/tests/profiles/dcat_ap_3/test_euro_dcatap_3_profile_parse.py b/ckanext/dcat/tests/profiles/dcat_ap_3/test_euro_dcatap_3_profile_parse.py index 70d7dfea..46d2f3d5 100644 --- a/ckanext/dcat/tests/profiles/dcat_ap_3/test_euro_dcatap_3_profile_parse.py +++ b/ckanext/dcat/tests/profiles/dcat_ap_3/test_euro_dcatap_3_profile_parse.py @@ -91,6 +91,10 @@ def test_e2e_dcat_to_ckan(self): "http://data.europa.eu/eli/reg_impl/2023/138/oj", "http://data.europa.eu/eli/reg_impl/2023/138/oj_alt", ] + assert sorted(dataset["sample"]) == [ + "https://data.some.org/catalog/datasets/9df8df51-63db-37a8-e044-0003ba9b0d98/sample", + ] + # Repeating subfields assert dataset["contact"][0]["name"] == "Point of Contact" diff --git a/ckanext/dcat/tests/profiles/dcat_ap_3/test_euro_dcatap_3_profile_serialize.py b/ckanext/dcat/tests/profiles/dcat_ap_3/test_euro_dcatap_3_profile_serialize.py index 7c06fa11..a1ad3061 100644 --- a/ckanext/dcat/tests/profiles/dcat_ap_3/test_euro_dcatap_3_profile_serialize.py +++ b/ckanext/dcat/tests/profiles/dcat_ap_3/test_euro_dcatap_3_profile_serialize.py @@ -131,6 +131,10 @@ def test_e2e_ckan_to_dcat(self): self._triples_list_values(g, dataset_ref, DCATAP.applicableLegislation) == dataset["applicable_legislation"] ) + assert ( + self._triples_list_values(g, dataset_ref, ADMS.sample) + == dataset["sample"] + ) # Repeating subfields diff --git a/examples/ckan/ckan_full_dataset_dcat_ap.json b/examples/ckan/ckan_full_dataset_dcat_ap.json index bc170025..8239a8e2 100644 --- a/examples/ckan/ckan_full_dataset_dcat_ap.json +++ b/examples/ckan/ckan_full_dataset_dcat_ap.json @@ -49,6 +49,10 @@ "http://data.europa.eu/eli/reg_impl/2023/138/oj", "http://data.europa.eu/eli/reg_impl/2023/138/oj_alt" ], + "sample": [ + "https://example.org/sample/1", + "test_sample" + ], "contact": [ { "name": "Contact 1", diff --git a/test.ini b/test.ini index 529eebc4..f413ff26 100644 --- a/test.ini +++ b/test.ini @@ -9,9 +9,10 @@ host = 0.0.0.0 port = 5000 [app:main] -use = config:../ckan/test-core.ini +use = config:/etc/ckan/default/src/ckan/test-core.ini ckan.plugins = dcat harvest ckanext.dcat.enable_content_negotiation=True +sqlalchemy.url = postgresql://ckandbuser:ckandbpassword@localhost:5432/ckandb # Needed for the harvest tests ckan.activity_streams_enabled = false From 7eaddee7e29756f140d04fa0ed3291dcec039541 Mon Sep 17 00:00:00 2001 From: Quinten Date: Wed, 25 Feb 2026 14:34:01 +0100 Subject: [PATCH 3/4] Update dcat_ap_full.yaml --- ckanext/dcat/schemas/dcat_ap_full.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ckanext/dcat/schemas/dcat_ap_full.yaml b/ckanext/dcat/schemas/dcat_ap_full.yaml index 6e10d405..4aa9ce96 100644 --- a/ckanext/dcat/schemas/dcat_ap_full.yaml +++ b/ckanext/dcat/schemas/dcat_ap_full.yaml @@ -377,7 +377,8 @@ dataset_fields: - field_name: sample label: Sample - validators: ignore_missing + preset: multiple_text + validators: ignore_missing scheming_multiple_text help_text: A sample distribution of the dataset. # Note: if not provided, this will be autogenerated From 18150b0922be758e876345b63ee95da30fc474ed Mon Sep 17 00:00:00 2001 From: Quinten <125458528+qplevier@users.noreply.github.com> Date: Wed, 25 Mar 2026 14:04:38 +0100 Subject: [PATCH 4/4] Update CKAN config file path for test-core.ini --- test.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test.ini b/test.ini index f413ff26..529eebc4 100644 --- a/test.ini +++ b/test.ini @@ -9,10 +9,9 @@ host = 0.0.0.0 port = 5000 [app:main] -use = config:/etc/ckan/default/src/ckan/test-core.ini +use = config:../ckan/test-core.ini ckan.plugins = dcat harvest ckanext.dcat.enable_content_negotiation=True -sqlalchemy.url = postgresql://ckandbuser:ckandbpassword@localhost:5432/ckandb # Needed for the harvest tests ckan.activity_streams_enabled = false