diff --git a/changes/1484.feature b/changes/1484.feature new file mode 100644 index 000000000..c88896fae --- /dev/null +++ b/changes/1484.feature @@ -0,0 +1 @@ +Added validation to the `digital_object_identifier` Dataset field. diff --git a/ckanext/canada/i18n/ckanext-canada.pot b/ckanext/canada/i18n/ckanext-canada.pot index 7dc11bb30..fa7fa8f03 100644 --- a/ckanext/canada/i18n/ckanext-canada.pot +++ b/ckanext/canada/i18n/ckanext-canada.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: ckanext-canada 0.4.0\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2024-06-25 17:26+0000\n" +"POT-Creation-Date: 2024-06-25 18:45+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -255,12 +255,12 @@ msgstr "" msgid "Suggestion Status" msgstr "" -#: ckanext/canada/plugins.py:899 +#: ckanext/canada/plugins.py:901 #: ckanext/canada/templates/public/package/snippets/resource_form.html:8 msgid "Previous" msgstr "" -#: ckanext/canada/plugins.py:899 +#: ckanext/canada/plugins.py:901 msgid "Next" msgstr "" @@ -546,75 +546,79 @@ msgstr "" msgid "Open Government Portal (staging)" msgstr "" -#: ckanext/canada/validators.py:118 +#: ckanext/canada/validators.py:151 #, python-format msgid "Tag \"%s\" length is less than minimum %s" msgstr "" -#: ckanext/canada/validators.py:122 +#: ckanext/canada/validators.py:155 #, python-format msgid "Tag \"%s\" length is more than maximum %i" msgstr "" -#: ckanext/canada/validators.py:126 +#: ckanext/canada/validators.py:159 #, python-format msgid "Tag \"%s\" may not contain consecutive spaces" msgstr "" -#: ckanext/canada/validators.py:133 +#: ckanext/canada/validators.py:166 #, python-format msgid "Tag \"%s\" may not contain unprintable character U+%04x" msgstr "" -#: ckanext/canada/validators.py:137 +#: ckanext/canada/validators.py:170 #, python-format msgid "Tag \"%s\" may not contain separator charater U+%04x" msgstr "" -#: ckanext/canada/validators.py:153 +#: ckanext/canada/validators.py:186 msgid "Badly formed hexadecimal UUID string" msgstr "" #: ckanext/canada/templates/internal/user/new_user_form.html:6 -#: ckanext/canada/validators.py:165 ckanext/canada/validators.py:167 +#: ckanext/canada/validators.py:198 ckanext/canada/validators.py:200 msgid "Please enter a valid email address." msgstr "" -#: ckanext/canada/validators.py:182 +#: ckanext/canada/validators.py:215 msgid "Invalid GeoJSON" msgstr "" -#: ckanext/canada/validators.py:259 +#: ckanext/canada/validators.py:292 msgid "Date may not be in the future when this record is marked ready to publish" msgstr "" -#: ckanext/canada/validators.py:336 +#: ckanext/canada/validators.py:369 msgid "Date format incorrect. Expecting YYYY-MM-DD" msgstr "" -#: ckanext/canada/validators.py:350 ckanext/canada/validators.py:364 +#: ckanext/canada/validators.py:383 ckanext/canada/validators.py:397 msgid "Must be a Unicode string value" msgstr "" -#: ckanext/canada/validators.py:371 ckanext/canada/validators.py:382 +#: ckanext/canada/validators.py:404 ckanext/canada/validators.py:415 msgid "Must be a JSON string" msgstr "" -#: ckanext/canada/validators.py:378 +#: ckanext/canada/validators.py:411 msgid "JSON object must contain \"en\" key" msgstr "" -#: ckanext/canada/validators.py:380 +#: ckanext/canada/validators.py:413 msgid "JSON object must contain \"fr\" key" msgstr "" -#: ckanext/canada/validators.py:532 +#: ckanext/canada/validators.py:565 #, python-format msgid "" "Cannot change value of registry_access field from '%s' to '%s'. This field is" " read-only." msgstr "" +#: ckanext/canada/validators.py:573 +msgid "Invalid value for a digital object identifier." +msgstr "" + #: ckanext/canada/view.py:99 msgid "Note
{0} is now logged in" msgstr "" diff --git a/ckanext/canada/i18n/en/LC_MESSAGES/ckanext-canada.po b/ckanext/canada/i18n/en/LC_MESSAGES/ckanext-canada.po index 697968923..705975fc3 100644 --- a/ckanext/canada/i18n/en/LC_MESSAGES/ckanext-canada.po +++ b/ckanext/canada/i18n/en/LC_MESSAGES/ckanext-canada.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: CKAN\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2024-06-25 17:26+0000\n" +"POT-Creation-Date: 2024-06-25 18:45+0000\n" "PO-Revision-Date: 2014-01-23 13:04+0000\n" "Last-Translator: Sean Hammond \n" "Language: en\n" @@ -263,12 +263,12 @@ msgstr "" msgid "Suggestion Status" msgstr "" -#: ckanext/canada/plugins.py:899 +#: ckanext/canada/plugins.py:901 #: ckanext/canada/templates/public/package/snippets/resource_form.html:8 msgid "Previous" msgstr "" -#: ckanext/canada/plugins.py:899 +#: ckanext/canada/plugins.py:901 msgid "Next" msgstr "" @@ -556,75 +556,79 @@ msgstr "" msgid "Open Government Portal (staging)" msgstr "" -#: ckanext/canada/validators.py:118 +#: ckanext/canada/validators.py:151 #, python-format msgid "Tag \"%s\" length is less than minimum %s" msgstr "" -#: ckanext/canada/validators.py:122 +#: ckanext/canada/validators.py:155 #, python-format msgid "Tag \"%s\" length is more than maximum %i" msgstr "" -#: ckanext/canada/validators.py:126 +#: ckanext/canada/validators.py:159 #, python-format msgid "Tag \"%s\" may not contain consecutive spaces" msgstr "" -#: ckanext/canada/validators.py:133 +#: ckanext/canada/validators.py:166 #, python-format msgid "Tag \"%s\" may not contain unprintable character U+%04x" msgstr "" -#: ckanext/canada/validators.py:137 +#: ckanext/canada/validators.py:170 #, python-format msgid "Tag \"%s\" may not contain separator charater U+%04x" msgstr "" -#: ckanext/canada/validators.py:153 +#: ckanext/canada/validators.py:186 msgid "Badly formed hexadecimal UUID string" msgstr "" #: ckanext/canada/templates/internal/user/new_user_form.html:6 -#: ckanext/canada/validators.py:165 ckanext/canada/validators.py:167 +#: ckanext/canada/validators.py:198 ckanext/canada/validators.py:200 msgid "Please enter a valid email address." msgstr "" -#: ckanext/canada/validators.py:182 +#: ckanext/canada/validators.py:215 msgid "Invalid GeoJSON" msgstr "" -#: ckanext/canada/validators.py:259 +#: ckanext/canada/validators.py:292 msgid "Date may not be in the future when this record is marked ready to publish" msgstr "" -#: ckanext/canada/validators.py:336 +#: ckanext/canada/validators.py:369 msgid "Date format incorrect. Expecting YYYY-MM-DD" msgstr "" -#: ckanext/canada/validators.py:350 ckanext/canada/validators.py:364 +#: ckanext/canada/validators.py:383 ckanext/canada/validators.py:397 msgid "Must be a Unicode string value" msgstr "" -#: ckanext/canada/validators.py:371 ckanext/canada/validators.py:382 +#: ckanext/canada/validators.py:404 ckanext/canada/validators.py:415 msgid "Must be a JSON string" msgstr "" -#: ckanext/canada/validators.py:378 +#: ckanext/canada/validators.py:411 msgid "JSON object must contain \"en\" key" msgstr "" -#: ckanext/canada/validators.py:380 +#: ckanext/canada/validators.py:413 msgid "JSON object must contain \"fr\" key" msgstr "" -#: ckanext/canada/validators.py:532 +#: ckanext/canada/validators.py:565 #, python-format msgid "" "Cannot change value of registry_access field from '%s' to '%s'. This " "field is read-only." msgstr "" +#: ckanext/canada/validators.py:573 +msgid "Invalid value for a digital object identifier." +msgstr "" + #: ckanext/canada/view.py:99 msgid "Note
{0} is now logged in" msgstr "" diff --git a/ckanext/canada/i18n/fr/LC_MESSAGES/ckanext-canada.po b/ckanext/canada/i18n/fr/LC_MESSAGES/ckanext-canada.po index 87cc1715b..d0ddfcd05 100644 --- a/ckanext/canada/i18n/fr/LC_MESSAGES/ckanext-canada.po +++ b/ckanext/canada/i18n/fr/LC_MESSAGES/ckanext-canada.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2024-06-25 17:26+0000\n" +"POT-Creation-Date: 2024-06-25 18:45+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: fr\n" @@ -275,12 +275,12 @@ msgstr "Juridiction" msgid "Suggestion Status" msgstr "État de la suggestion" -#: ckanext/canada/plugins.py:899 +#: ckanext/canada/plugins.py:901 #: ckanext/canada/templates/public/package/snippets/resource_form.html:8 msgid "Previous" msgstr "Précédent" -#: ckanext/canada/plugins.py:899 +#: ckanext/canada/plugins.py:901 msgid "Next" msgstr "Suivant" @@ -581,71 +581,71 @@ msgstr "Portail du gouvernement ouvert" msgid "Open Government Portal (staging)" msgstr "Portail du gouvernement ouvert (stadification)" -#: ckanext/canada/validators.py:118 +#: ckanext/canada/validators.py:151 #, python-format msgid "Tag \"%s\" length is less than minimum %s" msgstr "La longueur de la balise \"%s\" est inférieure au minimum %s" -#: ckanext/canada/validators.py:122 +#: ckanext/canada/validators.py:155 #, python-format msgid "Tag \"%s\" length is more than maximum %i" msgstr "La longueur de la balise \"%s\" est supérieure au maximum %i" -#: ckanext/canada/validators.py:126 +#: ckanext/canada/validators.py:159 #, python-format msgid "Tag \"%s\" may not contain consecutive spaces" msgstr "La balise \"%s\" ne peut pas contenir d’espaces consécutifs" -#: ckanext/canada/validators.py:133 +#: ckanext/canada/validators.py:166 #, python-format msgid "Tag \"%s\" may not contain unprintable character U+%04x" msgstr "La balise \"%s\" ne peut pas contenir de caractères non imprimables U+%04x" -#: ckanext/canada/validators.py:137 +#: ckanext/canada/validators.py:170 #, python-format msgid "Tag \"%s\" may not contain separator charater U+%04x" msgstr "La balise \"%s\"ne peut pas contenir de caractères séparateurs U+%04x" -#: ckanext/canada/validators.py:153 +#: ckanext/canada/validators.py:186 msgid "Badly formed hexadecimal UUID string" msgstr "Chaîne UUID hexadécimale mal formée" #: ckanext/canada/templates/internal/user/new_user_form.html:6 -#: ckanext/canada/validators.py:165 ckanext/canada/validators.py:167 +#: ckanext/canada/validators.py:198 ckanext/canada/validators.py:200 msgid "Please enter a valid email address." msgstr "S’il vous plaît, mettez une adresse email valide." -#: ckanext/canada/validators.py:182 +#: ckanext/canada/validators.py:215 msgid "Invalid GeoJSON" msgstr "GeoJSON invalide" -#: ckanext/canada/validators.py:259 +#: ckanext/canada/validators.py:292 msgid "Date may not be in the future when this record is marked ready to publish" msgstr "" "Une fois que ce document est marqué comme étant prêt à publier, la date " "ne peut pas être dans le futur" -#: ckanext/canada/validators.py:336 +#: ckanext/canada/validators.py:369 msgid "Date format incorrect. Expecting YYYY-MM-DD" msgstr "Le format de la date est incorrect. Il devrait être AAAA-MM-JJ" -#: ckanext/canada/validators.py:350 ckanext/canada/validators.py:364 +#: ckanext/canada/validators.py:383 ckanext/canada/validators.py:397 msgid "Must be a Unicode string value" msgstr "Doit être une chaîne de caractères Unicode" -#: ckanext/canada/validators.py:371 ckanext/canada/validators.py:382 +#: ckanext/canada/validators.py:404 ckanext/canada/validators.py:415 msgid "Must be a JSON string" msgstr "Doit être une chaîne JSON" -#: ckanext/canada/validators.py:378 +#: ckanext/canada/validators.py:411 msgid "JSON object must contain \"en\" key" msgstr "L’objet JSON doit contenir la clé \"en\"" -#: ckanext/canada/validators.py:380 +#: ckanext/canada/validators.py:413 msgid "JSON object must contain \"fr\" key" msgstr "L’objet JSON doit contenir la clé \"fr\"" -#: ckanext/canada/validators.py:532 +#: ckanext/canada/validators.py:565 #, python-format msgid "" "Cannot change value of registry_access field from '%s' to '%s'. This " @@ -654,6 +654,10 @@ msgstr "" "Impossible de modifier la valeur du champ d’accès au registre de '%s' à " "'%s'. Ce champ ne sert qu’à des fins de lecture." +#: ckanext/canada/validators.py:573 +msgid "Invalid value for a digital object identifier." +msgstr "Valeur non valide pour un identifiant d’objet numérique." + #: ckanext/canada/view.py:99 msgid "Note
{0} is now logged in" msgstr "Note
{0} est maintenant connecté" diff --git a/ckanext/canada/plugins.py b/ckanext/canada/plugins.py index a97bf2a7b..d45e8c480 100755 --- a/ckanext/canada/plugins.py +++ b/ckanext/canada/plugins.py @@ -862,6 +862,8 @@ def get_validators(self): validators.canada_output_none, 'protect_registry_access': validators.protect_registry_access, + 'digital_object_identifier': + validators.digital_object_identifier, } diff --git a/ckanext/canada/schemas/presets.yaml b/ckanext/canada/schemas/presets.yaml index 400be5296..f14a96580 100644 --- a/ckanext/canada/schemas/presets.yaml +++ b/ckanext/canada/schemas/presets.yaml @@ -2329,7 +2329,7 @@ presets: # Field = Digital Object Identifier (DOI). -# {The Digital Object Identifier assigned to the dataset. For more information visit: http://cisti-icist.nrc-cnrc.gc.ca/eng/services/cisti/datacite-canada/index.html} +# {The Digital Object Identifier assigned to the dataset. For more information visit: https://support.datacite.org/docs/doi-basics} - preset_name: canada_digital_object_identifier values: field_name: digital_object_identifier @@ -2337,9 +2337,10 @@ presets: en: Digital Object Identifier (DOI) fr: Identificateur d’objet numérique help_text: - en: "The Digital Object Identifier assigned to the dataset. For more information visit: http://cisti-icist.nrc-cnrc.gc.ca/eng/services/cisti/datacite-canada/index.html" - fr: "L'identificateur d'objet numérique assigné au jeu de données. Pour obtenir de plus amples renseignements, veuillez consulter le site http://cisti-icist.nrc-cnrc.gc.ca/fra/services/icist/datacite-canada/index.html" - validators: scheming_required string_safe + en: "The Digital Object Identifier assigned to the dataset. For more information visit: https://support.datacite.org/docs/doi-basics" + fr: "L'identificateur d'objet numérique assigné au jeu de données. Pour obtenir de plus amples renseignements, veuillez consulter le site https://support.datacite.org/docs/doi-basics" + validators: scheming_required string_safe digital_object_identifier + display_snippet: doi.html # Field = Jurisdiction. # Default displayed value = Federal. diff --git a/ckanext/canada/templates/public/scheming/display_snippets/doi.html b/ckanext/canada/templates/public/scheming/display_snippets/doi.html new file mode 100644 index 000000000..abebc0657 --- /dev/null +++ b/ckanext/canada/templates/public/scheming/display_snippets/doi.html @@ -0,0 +1,7 @@ +{%- set value = data[field.field_name] -%} + +{% if value.startswith('10') %}{# backwards compatibility for existing data #} + {{ value }} +{% else %} + {{ value }} +{% endif %} diff --git a/ckanext/canada/tests/test_validators.py b/ckanext/canada/tests/test_validators.py index a10788f6b..62b2b6d96 100644 --- a/ckanext/canada/tests/test_validators.py +++ b/ckanext/canada/tests/test_validators.py @@ -514,6 +514,81 @@ def test_validation_options(self): resource = self.sysadmin_action.resource_create(**resource_data) assert 'validation_options' not in resource or resource['validation_options'] == None + def test_digital_object_identifier(self): + "DOI values should conform to the standard DOI practices." + pkg = self.sysadmin_action.package_create(**self.complete_pkg) + + # valid DOI + pkg['digital_object_identifier'] = '10.1000/xyz-123' + up_pkg = self.sysadmin_action.package_update(**pkg) + assert up_pkg['digital_object_identifier'] == '10.1000/xyz-123' + + # valid DOI + pkg['digital_object_identifier'] = '10.1016.12.31/nature.S0735-1097(98)2000/12/31/34:7-7' + up_pkg = self.sysadmin_action.package_update(**pkg) + assert up_pkg['digital_object_identifier'] == '10.1016.12.31/nature.S0735-1097(98)2000/12/31/34:7-7' + + # valid DOI + pkg['digital_object_identifier'] = '10.1002/(SICI)1522-2594(199911)42:5<952::AID-MRM16>3.0.CO;2-S' + up_pkg = self.sysadmin_action.package_update(**pkg) + assert up_pkg['digital_object_identifier'] == '10.1002/(SICI)1522-2594(199911)42:5<952::AID-MRM16>3.0.CO;2-S' + + # invalid DOI + pkg['digital_object_identifier'] = '7.0182/(TBS-SCT)1522-2594' + with pytest.raises(ValidationError) as ve: + self.normal_action.package_update(**pkg) + err = ve.value.error_dict + assert 'digital_object_identifier' in err + assert err['digital_object_identifier'] == ['Invalid value for a digital object identifier.'] + + # invalid DOI + pkg['digital_object_identifier'] = '10.01.02/(TBS-SCT)1522-2594' + with pytest.raises(ValidationError) as ve: + self.normal_action.package_update(**pkg) + err = ve.value.error_dict + assert 'digital_object_identifier' in err + assert err['digital_object_identifier'] == ['Invalid value for a digital object identifier.'] + + # invalid DOI + pkg['digital_object_identifier'] = '10.1016/(TBS-SCT)152???2-2594' + with pytest.raises(ValidationError) as ve: + self.normal_action.package_update(**pkg) + err = ve.value.error_dict + assert 'digital_object_identifier' in err + assert err['digital_object_identifier'] == ['Invalid value for a digital object identifier.'] + + # invalid DOI + pkg['digital_object_identifier'] = '10.1016/(TBS-SCT)152&&&2-2594' + with pytest.raises(ValidationError) as ve: + self.normal_action.package_update(**pkg) + err = ve.value.error_dict + assert 'digital_object_identifier' in err + assert err['digital_object_identifier'] == ['Invalid value for a digital object identifier.'] + + # invalid DOI + pkg['digital_object_identifier'] = '10.1016/(TBS-SCT)152"""2-2594' + with pytest.raises(ValidationError) as ve: + self.normal_action.package_update(**pkg) + err = ve.value.error_dict + assert 'digital_object_identifier' in err + assert err['digital_object_identifier'] == ['Invalid value for a digital object identifier.'] + + # invalid DOI + pkg['digital_object_identifier'] = "10.1016/(TBS-SCT)152'''2-2594" + with pytest.raises(ValidationError) as ve: + self.normal_action.package_update(**pkg) + err = ve.value.error_dict + assert 'digital_object_identifier' in err + assert err['digital_object_identifier'] == ['Invalid value for a digital object identifier.'] + + # invalid DOI + pkg['digital_object_identifier'] = "This is extremely not a DOI number" + with pytest.raises(ValidationError) as ve: + self.normal_action.package_update(**pkg) + err = ve.value.error_dict + assert 'digital_object_identifier' in err + assert err['digital_object_identifier'] == ['Invalid value for a digital object identifier.'] + class TestSysadminUpdate(CanadaTestBase): @classmethod diff --git a/ckanext/canada/validators.py b/ckanext/canada/validators.py index e15e37234..71f176268 100644 --- a/ckanext/canada/validators.py +++ b/ckanext/canada/validators.py @@ -31,6 +31,39 @@ MIN_TAG_LENGTH = 2 MAX_TAG_LENGTH = 140 # because twitter +# DOI Validation +# +# See: https://www.doi.org/the-identifier/resources/handbook/ +# As of 2012, assigned registrant codes can be 3 or 4 digits long. +# NOTE: this does not support ShortDOI (https://shortdoi.org/) +# +# / +# +# prefix: refers to the DOI namespace (a namespace is allocated to a given service provider). +# The prefix can contain only numeric values and the "." character which is used to +# delimit a hierarchical level in the namespace allocation: a one-delimiter prefix +# (for example, "10.1000") derives from a zero-delimiter prefix ("10"). +# The prefix 10 is allocated to the DOI Foundation. +# +# suffix: is a unique local name in the namespace. Any Unicode 2.0 character can be used +# in the suffix (there is no practical limitation on the length of a DOI name). +# This unique string may be an existing identifier, or any unique string chosen +# by the Registration Agency or the referent owner (registrant). Some limitations +# should be applied due to DOI HTTPS Links. +# +# https limitations: double quotes (") +# single quotes (') +# & +# ? +# +# Examples: 10.1000/xyz-123 +# 10.1109/5.771073 +# 10.231/JIM.0b013e31820bab4c +# 10.1016.12.31/nature.S0735-1097(98)2000/12/31/34:7-7 +# 10.1002/(SICI)1522-2594(199911)42:5<952::AID-MRM16>3.0.CO;2-S +# +doi_match = re.compile(r'\b(10[.][0-9]{3,}(?:[.][0-9]+)*/(?:(?!["&\'\?])\S)+)$\b') + def protect_portal_release_date(key, data, errors, context): """ @@ -531,3 +564,11 @@ def protect_registry_access(key, data, errors, context): " from '%s' to '%s'. This field is read-only." % (original, value))) raise StopOnError + + +def digital_object_identifier(value, context): + if not value or value is missing: + return value + if not re.match(doi_match, value): + raise Invalid(_('Invalid value for a digital object identifier.')) + return value