From a2166e0875e4502addc292489a07135cb2e8cc25 Mon Sep 17 00:00:00 2001 From: amercader Date: Wed, 8 Jan 2025 13:11:33 +0100 Subject: [PATCH 1/3] Split plugin templates into separate folders Split the `dcat` and `strucured_data` plugins templates so they can be loaded independently --- ckanext/dcat/plugins/__init__.py | 9 ++++++++- .../dcat/templates/{ => dcat}/home/index.html | 0 .../templates/{ => dcat}/package/read_base.html | 17 ----------------- .../templates/{ => dcat}/package/search.html | 0 .../scheming/display_snippets/dcat_date.html | 0 .../scheming/display_snippets/file_size.html | 0 .../scheming/form_snippets/number.html | 0 .../form_snippets/repeating_subfields.html | 0 .../structured_data/package/read_base.html | 13 +++++++++++++ 9 files changed, 21 insertions(+), 18 deletions(-) rename ckanext/dcat/templates/{ => dcat}/home/index.html (100%) rename ckanext/dcat/templates/{ => dcat}/package/read_base.html (55%) rename ckanext/dcat/templates/{ => dcat}/package/search.html (100%) rename ckanext/dcat/templates/{ => dcat}/scheming/display_snippets/dcat_date.html (100%) rename ckanext/dcat/templates/{ => dcat}/scheming/display_snippets/file_size.html (100%) rename ckanext/dcat/templates/{ => dcat}/scheming/form_snippets/number.html (100%) rename ckanext/dcat/templates/{ => dcat}/scheming/form_snippets/repeating_subfields.html (100%) create mode 100644 ckanext/dcat/templates/structured_data/package/read_base.html diff --git a/ckanext/dcat/plugins/__init__.py b/ckanext/dcat/plugins/__init__.py index 1477a38f..dd0661e9 100644 --- a/ckanext/dcat/plugins/__init__.py +++ b/ckanext/dcat/plugins/__init__.py @@ -81,7 +81,7 @@ def i18n_directory(self): # IConfigurer def update_config(self, config): - p.toolkit.add_template_directory(config, '../templates') + p.toolkit.add_template_directory(config, '../templates/dcat') # Check catalog URI on startup to emit a warning if necessary utils.catalog_uri() @@ -243,8 +243,15 @@ def get_auth_functions(self): class StructuredDataPlugin(p.SingletonPlugin): + + p.implements(p.IConfigurer, inherit=True) p.implements(p.ITemplateHelpers, inherit=True) + # IConfigurer + + def update_config(self, config): + p.toolkit.add_template_directory(config, '../templates/structured_data') + # ITemplateHelpers def get_helpers(self): diff --git a/ckanext/dcat/templates/home/index.html b/ckanext/dcat/templates/dcat/home/index.html similarity index 100% rename from ckanext/dcat/templates/home/index.html rename to ckanext/dcat/templates/dcat/home/index.html diff --git a/ckanext/dcat/templates/package/read_base.html b/ckanext/dcat/templates/dcat/package/read_base.html similarity index 55% rename from ckanext/dcat/templates/package/read_base.html rename to ckanext/dcat/templates/dcat/package/read_base.html index 4c6023c6..afbb198c 100644 --- a/ckanext/dcat/templates/package/read_base.html +++ b/ckanext/dcat/templates/dcat/package/read_base.html @@ -12,20 +12,3 @@ {% endwith %} {% endif %} {% endblock -%} -{% block scripts %} - {{ super() }} - {% block structured_data %} - {# - h.structured_data is defined in the 'structured_data' plugin, - you have to activate the plugin (or implement the method yourself) - to make use of this feature. - More information about structured data: - https://developers.google.com/search/docs/guides/intro-structured-data - #} - {% if h.helper_available('structured_data') %} - - {% endif %} - {% endblock %} -{% endblock %} diff --git a/ckanext/dcat/templates/package/search.html b/ckanext/dcat/templates/dcat/package/search.html similarity index 100% rename from ckanext/dcat/templates/package/search.html rename to ckanext/dcat/templates/dcat/package/search.html diff --git a/ckanext/dcat/templates/scheming/display_snippets/dcat_date.html b/ckanext/dcat/templates/dcat/scheming/display_snippets/dcat_date.html similarity index 100% rename from ckanext/dcat/templates/scheming/display_snippets/dcat_date.html rename to ckanext/dcat/templates/dcat/scheming/display_snippets/dcat_date.html diff --git a/ckanext/dcat/templates/scheming/display_snippets/file_size.html b/ckanext/dcat/templates/dcat/scheming/display_snippets/file_size.html similarity index 100% rename from ckanext/dcat/templates/scheming/display_snippets/file_size.html rename to ckanext/dcat/templates/dcat/scheming/display_snippets/file_size.html diff --git a/ckanext/dcat/templates/scheming/form_snippets/number.html b/ckanext/dcat/templates/dcat/scheming/form_snippets/number.html similarity index 100% rename from ckanext/dcat/templates/scheming/form_snippets/number.html rename to ckanext/dcat/templates/dcat/scheming/form_snippets/number.html diff --git a/ckanext/dcat/templates/scheming/form_snippets/repeating_subfields.html b/ckanext/dcat/templates/dcat/scheming/form_snippets/repeating_subfields.html similarity index 100% rename from ckanext/dcat/templates/scheming/form_snippets/repeating_subfields.html rename to ckanext/dcat/templates/dcat/scheming/form_snippets/repeating_subfields.html diff --git a/ckanext/dcat/templates/structured_data/package/read_base.html b/ckanext/dcat/templates/structured_data/package/read_base.html new file mode 100644 index 00000000..291e5789 --- /dev/null +++ b/ckanext/dcat/templates/structured_data/package/read_base.html @@ -0,0 +1,13 @@ +{% ckan_extends %} + +{% block scripts %} + + {{ super() }} + + {% block structured_data %} + + + {% endblock %} +{% endblock %} From f5786b11805060b9361a6dbca331bd217b590ac1 Mon Sep 17 00:00:00 2001 From: amercader Date: Wed, 8 Jan 2025 13:16:11 +0100 Subject: [PATCH 2/3] Separate template helpers into their own module --- ckanext/dcat/blueprints.py | 5 +-- ckanext/dcat/helpers.py | 52 ++++++++++++++++++++++++++++++++ ckanext/dcat/plugins/__init__.py | 8 ++--- ckanext/dcat/utils.py | 46 ---------------------------- 4 files changed, 59 insertions(+), 52 deletions(-) create mode 100644 ckanext/dcat/helpers.py diff --git a/ckanext/dcat/blueprints.py b/ckanext/dcat/blueprints.py index e7b14b19..4a12a3d3 100644 --- a/ckanext/dcat/blueprints.py +++ b/ckanext/dcat/blueprints.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from flask import Blueprint, jsonify, make_response +from flask import Blueprint, jsonify from ckantoolkit import config @@ -7,6 +7,7 @@ import ckan.plugins.toolkit as toolkit import ckanext.dcat.utils as utils +from ckanext.dcat.helpers import endpoints_enabled dcat = Blueprint( 'dcat', @@ -23,7 +24,7 @@ def read_dataset(_id, _format=None, package_type=None): return utils.read_dataset_page(_id, _format) -if utils.endpoints_enabled(): +if endpoints_enabled(): # requirements={'_format': 'xml|rdf|n3|ttl|jsonld'} dcat.add_url_rule(config.get('ckanext.dcat.catalog_endpoint', diff --git a/ckanext/dcat/helpers.py b/ckanext/dcat/helpers.py new file mode 100644 index 00000000..c66e7223 --- /dev/null +++ b/ckanext/dcat/helpers.py @@ -0,0 +1,52 @@ +""" +Helpers used by templates +""" +import simplejson as json + +import ckantoolkit as toolkit + +from ckanext.dcat.processors import RDFSerializer + +config = toolkit.config + + +ENABLE_RDF_ENDPOINTS_CONFIG = "ckanext.dcat.enable_rdf_endpoints" + + +def endpoints_enabled(): + return toolkit.asbool(config.get(ENABLE_RDF_ENDPOINTS_CONFIG, True)) + + +def get_endpoint(_type="dataset"): + return "dcat.read_dataset" if _type == "dataset" else "dcat.read_catalog" + + +def structured_data(dataset_dict, profiles=None, _format="jsonld"): + """ + Returns a string containing the structured data of the given + dataset id and using the given profiles (if no profiles are supplied + the default profiles are used). + + This string can be used in the frontend. + """ + + if not profiles: + profiles = ["schemaorg"] + + serializer = RDFSerializer(profiles=profiles) + + output = serializer.serialize_dataset(dataset_dict, _format=_format) + + # parse result again to prevent UnicodeDecodeError and add formatting + try: + json_data = json.loads(output) + return json.dumps( + json_data, + sort_keys=True, + indent=4, + separators=(",", ": "), + cls=json.JSONEncoderForHTML, + ) + except ValueError: + # result was not JSON, return anyway + return output diff --git a/ckanext/dcat/plugins/__init__.py b/ckanext/dcat/plugins/__init__.py index dd0661e9..8db84192 100644 --- a/ckanext/dcat/plugins/__init__.py +++ b/ckanext/dcat/plugins/__init__.py @@ -19,6 +19,7 @@ dcat_datasets_list, dcat_auth, ) +from ckanext.dcat import helpers from ckanext.dcat import utils from ckanext.dcat.validators import dcat_validators @@ -102,9 +103,8 @@ def update_config(self, config): def get_helpers(self): return { - 'helper_available': utils.helper_available, - 'dcat_get_endpoint': utils.get_endpoint, - 'dcat_endpoints_enabled': utils.endpoints_enabled, + 'dcat_get_endpoint': helpers.get_endpoint, + 'dcat_endpoints_enabled': helpers.endpoints_enabled, } # IActions @@ -256,5 +256,5 @@ def update_config(self, config): def get_helpers(self): return { - 'structured_data': utils.structured_data, + 'structured_data': helpers.structured_data, } diff --git a/ckanext/dcat/utils.py b/ckanext/dcat/utils.py index d5fd1749..99254d7a 100644 --- a/ckanext/dcat/utils.py +++ b/ckanext/dcat/utils.py @@ -37,7 +37,6 @@ DCAT_CLEAN_TAGS = 'ckanext.dcat.clean_tags' DEFAULT_CATALOG_ENDPOINT = '/catalog.{_format}' -ENABLE_RDF_ENDPOINTS_CONFIG = 'ckanext.dcat.enable_rdf_endpoints' ENABLE_CONTENT_NEGOTIATION_CONFIG = 'ckanext.dcat.enable_content_negotiation' @@ -95,43 +94,6 @@ def field_labels(): 'created': _('Created'), } -def helper_available(helper_name): - ''' - Checks if a given helper name is available on `h` - ''' - try: - getattr(h, helper_name) - except (AttributeError, HelperError): - return False - return True - -def structured_data(dataset_id, profiles=None, _format='jsonld'): - ''' - Returns a string containing the structured data of the given - dataset id and using the given profiles (if no profiles are supplied - the default profiles are used). - - This string can be used in the frontend. - ''' - if not profiles: - profiles = ['schemaorg'] - - data = toolkit.get_action('dcat_dataset_show')( - {}, - { - 'id': dataset_id, - 'profiles': profiles, - 'format': _format, - } - ) - # parse result again to prevent UnicodeDecodeError and add formatting - try: - json_data = json.loads(data) - return json.dumps(json_data, sort_keys=True, - indent=4, separators=(',', ': '), cls=json.JSONEncoderForHTML) - except ValueError: - # result was not JSON, return anyway - return data def catalog_uri(): ''' @@ -459,11 +421,3 @@ def read_catalog_page(_format): response.headers['Content-type'] = CONTENT_TYPES[_format] return response - - -def endpoints_enabled(): - return toolkit.asbool(config.get(ENABLE_RDF_ENDPOINTS_CONFIG, True)) - - -def get_endpoint(_type='dataset'): - return 'dcat.read_dataset' if _type == 'dataset' else 'dcat.read_catalog' From 86474f7b6a932bcca26279d82b33d9f7739307d4 Mon Sep 17 00:00:00 2001 From: amercader Date: Wed, 8 Jan 2025 13:30:27 +0100 Subject: [PATCH 3/3] Update structured data docs --- docs/google-dataset-search.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/google-dataset-search.md b/docs/google-dataset-search.md index 9710e45b..f88b8e68 100644 --- a/docs/google-dataset-search.md +++ b/docs/google-dataset-search.md @@ -2,18 +2,22 @@ The `structured_data` plugin will add the necessary markup to dataset pages in order to get your datasets indexed by [Google Dataset Search](https://toolbox.google.com/datasetsearch). This markup is a [structured data](https://developers.google.com/search/docs/guides/intro-structured-data) JSON-LD snippet that uses the [schema.org](https://schema.org) vocabulary to describe the dataset. - ckan.plugins = dcat structured_data + ckan.plugins = structured_data -By default this uses the `schemaorg` profile (see [Profiles](profiles.md#profiles)) to serialize the dataset to JSON-LD, which is then added to the dataset detail page. -To change the schema, you have to override the Jinja template block called `structured_data` in [`templates/package/read_base.html`](https://github.com/ckan/ckanext-dcat/blob/master/ckanext/dcat/templates/package/read_base.html) and call the template helper function with different parameters: +You don't need to load the `dcat` plugin to use the `structured_data` plugin, but you can load them both to enable both functionalities. + +The plugin uses the `schemaorg` profile by default (see [Profiles](profiles.md#profiles)) to serialize the dataset to JSON-LD, which is then added to the dataset detail page. + +To use a custom profile, you have to override the Jinja template block called `structured_data` in [`templates/package/read_base.html`](https://github.com/ckan/ckanext-dcat/blob/master/ckanext/dcat/templates/structured_data/package/read_base.html) and call the template helper function with different parameters: {% block structured_data %} {% endblock %} -Example output of structured data in JSON-LD: + +Below is an example of the structured data in JSON-LD embedded in the dataset page source: ```html