-
Notifications
You must be signed in to change notification settings - Fork 27
fix(ep_approval): Fix error handling, add test for single record flow #917
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,8 +9,7 @@ | |
| """CDS RDM service components.""" | ||
|
|
||
| from flask import current_app | ||
| from flask_principal import ActionNeed | ||
| from invenio_access import Permission | ||
| from invenio_access.permissions import system_user_id | ||
| from invenio_communities.proxies import current_communities | ||
| from invenio_drafts_resources.services.records.components import ServiceComponent | ||
| from invenio_i18n import gettext as _ | ||
|
|
@@ -130,22 +129,19 @@ def publish(self, identity, draft=None, record=None, **kwargs): | |
| class CommitteeApprovalComponent(ServiceComponent): | ||
| """Guard and sync committee approval identifiers. | ||
|
|
||
| 1. Blocks non-privileged users from adding/modifying/deleting ``apprn`` | ||
| scheme identifiers — these are system-managed only. | ||
| 2. Blocks non-privileged users from adding a ``cdsrn`` identifier whose | ||
| value matches any configured committee approval report-number pattern | ||
| (e.g. CERN-EP-*). | ||
| 3. Regenerates the ``apprn`` metadata identifier from parent committee_approval | ||
| on every save — only the public approved record carries it (detected by | ||
| ``source_internal_version`` on the parent). | ||
| 1. Blocks everyone except the system process from adding/modifying/deleting | ||
| ``apprn`` scheme identifiers — including admins via the UI. | ||
| 2. Regenerates the ``apprn`` metadata identifier from parent committee_approval | ||
| only when ``source_internal_version`` is set on the parent. This covers | ||
| two cases: | ||
| - Public approved copy in the two-record flow (views.py sets | ||
| ``source_internal_version`` to the internal record's recid). | ||
| - Single-record migration case (migrate_cdsrn_to_apprn.py sets | ||
| ``source_internal_version`` to the record's own recid). | ||
| The internal record in the normal flow never has ``source_internal_version`` | ||
| on its parent, so it never carries the apprn identifier. | ||
| """ | ||
|
|
||
| def _is_privileged(self, identity): | ||
| """Return True if the identity is system or has superuser access.""" | ||
| return identity.id == "system" or Permission( | ||
| ActionNeed("superuser-access") | ||
| ).allows(identity) | ||
|
|
||
| def _committee_approval_prefixes(self): | ||
| """Return the set of fixed prefixes from all configured committee communities. | ||
|
|
||
|
|
@@ -160,9 +156,9 @@ def _committee_approval_prefixes(self): | |
| prefixes.add(prefix) | ||
| return prefixes | ||
|
|
||
| def _validate_identifier_changes(self, identity, data, record): | ||
| """Raise ValidationError if the user is modifying protected identifiers.""" | ||
| if self._is_privileged(identity): | ||
| def _validate_identifier_changes(self, identity, data, record, errors): | ||
| """Raise ValidationError if a non-system identity modifies apprn.""" | ||
| if identity.id == system_user_id: | ||
| return | ||
|
|
||
| incoming_identifiers = (data.get("metadata") or {}).get("identifiers", []) | ||
|
|
@@ -177,7 +173,7 @@ def _validate_identifier_changes(self, identity, data, record): | |
| } | ||
| if incoming_apprn != stored_apprn: | ||
| error_msg = _( | ||
| "The 'apprn' identifier is system-managed and cannot be " | ||
| "The EP approval report number is system-managed and cannot be " | ||
| "added, modified, or removed manually." | ||
| ) | ||
|
|
||
|
|
@@ -201,59 +197,43 @@ def _validate_identifier_changes(self, identity, data, record): | |
|
|
||
| raise ValidationErrorWithMessageAsList(errors) | ||
|
|
||
| # Block cdsrn values that look like committee report numbers. | ||
| ep_prefixes = self._committee_approval_prefixes() | ||
| if ep_prefixes: | ||
| errors = [] | ||
| for index, ident in enumerate(incoming_identifiers): | ||
| if ident.get("scheme") == "cdsrn": | ||
| val = ident.get("identifier", "") | ||
| if any(val.startswith(p) for p in ep_prefixes): | ||
| errors.append( | ||
| { | ||
| "field": f"metadata.identifiers.{index}.identifier", | ||
| "messages": [ | ||
| _( | ||
| f"The value '{val}' matches an EP approval " | ||
| "report number pattern and cannot be used as " | ||
| "a CDS report number." | ||
| ) | ||
| ], | ||
| } | ||
| ) | ||
| if errors: | ||
| raise ValidationErrorWithMessageAsList(errors) | ||
| def _should_sync_apprn(self, record, committee_approval): | ||
| """Return True if apprn should be synced with parent committee_approval.""" | ||
| reportnumber = committee_approval.get("reportnumber") | ||
| approved_internal = committee_approval.get("approved_internal_version") | ||
| source_internal = committee_approval.get("source_internal_version") | ||
| if not reportnumber or not source_internal: | ||
| return False | ||
| # Migrated case: both flags point at the same version, so only the current version carries apprn | ||
| if approved_internal == source_internal: | ||
| return record["id"] == source_internal | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I understand correctly, that wil regenerate the apprn only for the accepted version. The truth is that all the newer versions should have the apprn number |
||
| # Public copy: source points back to a different (internal) record, always sync | ||
| return True | ||
|
Comment on lines
+208
to
+211
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. isnt this always True? |
||
|
|
||
| def _regenerate_apprn_identifier(self, record, data): | ||
| """Keep apprn in metadata.identifiers in sync with parent committee_approval. | ||
|
|
||
| The apprn identifier is only added when ``source_internal_version`` is present | ||
| on the parent — that key is set exclusively on the public approved record's | ||
| parent by the ``publish_public_record`` view. | ||
| """ | ||
| ea = ( | ||
| """Keep apprn in metadata.identifiers in sync with parent committee_approval.""" | ||
| committee_approval = ( | ||
| (record.parent.get("permission_flags") if record.parent else None) or {} | ||
| ).get("committee_approval") or {} | ||
| reportnumber = ea.get("reportnumber") | ||
| source_internal = ea.get("source_internal_version") | ||
| reportnumber = committee_approval.get("reportnumber") | ||
| identifiers = [ | ||
| i | ||
| for i in (data.get("metadata") or {}).get("identifiers", []) | ||
| if i.get("scheme") != "apprn" | ||
| ] | ||
| if reportnumber and source_internal: | ||
| if self._should_sync_apprn(record, committee_approval): | ||
| identifiers = [ | ||
| {"scheme": "apprn", "identifier": reportnumber} | ||
| ] + identifiers | ||
| data.setdefault("metadata", {})["identifiers"] = identifiers | ||
|
|
||
| def create(self, identity, data=None, record=None, errors=None, **kwargs): | ||
| """Validate apprn identifier on draft creation.""" | ||
| self._validate_identifier_changes(identity, data, record) | ||
| self._validate_identifier_changes(identity, data, record, errors) | ||
|
|
||
| def update_draft(self, identity, data=None, record=None, errors=None, **kwargs): | ||
| """Validate and regenerate apprn identifier on draft update.""" | ||
| self._validate_identifier_changes(identity, data, record) | ||
| self._validate_identifier_changes(identity, data, record, errors) | ||
| self._regenerate_apprn_identifier(record, data) | ||
|
|
||
| def publish(self, identity, draft=None, record=None, **kwargs): | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
here we cannot hardcode the EP label as we would like to have this configurable. Maybe we added a label fro this in the config.