Skip to content

Commit 6ac878f

Browse files
authored
Merge pull request #11735 from Vlad0n20/feature/ENG-9827
[ENG-9827] - save collection-submission custom metadata to CedarMetadataRecord on create/update
2 parents 02f4a46 + b30e3e3 commit 6ac878f

6 files changed

Lines changed: 112 additions & 49 deletions

File tree

api_tests/collections/test_views.py

Lines changed: 64 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from api_tests.subjects.mixins import UpdateSubjectsMixin, SubjectsFilterMixin, SubjectsListMixin, \
1111
SubjectsRelationshipMixin
1212
from api_tests.utils import disconnected_from_listeners
13+
from tests.utils import capture_notifications
1314
from framework.auth.core import Auth
1415
from osf import features
1516
from osf.models import Collection, VersionedGuidMixin
@@ -4450,16 +4451,70 @@ def test_switch_active_no_provider_submission_succeeds(self, app, user_one, proj
44504451
)
44514452
assert res.status_code == 201
44524453

4453-
def test_switch_active_missing_cedar_record_submission_fails(self, app, user_one, project, url, payload):
4454-
with override_switch(features.COLLECTION_SUBMISSION_WITH_CEDAR, active=True):
4455-
res = app.post_json_api(
4456-
url,
4457-
payload(guid=project._id),
4458-
auth=user_one.auth,
4459-
expect_errors=True,
4460-
)
4454+
def test_switch_active_submission_without_cedar_record_fails(
4455+
self, app, user_one, project, url, payload, cedar_template):
4456+
with capture_notifications(expect_none=True):
4457+
with mock_update_share():
4458+
with override_switch(features.COLLECTION_SUBMISSION_WITH_CEDAR, active=True):
4459+
res = app.post_json_api(
4460+
url,
4461+
payload(guid=project._id),
4462+
auth=user_one.auth,
4463+
expect_errors=True,
4464+
)
44614465
assert res.status_code == 400
4462-
assert 'CEDAR metadata record' in res.json['errors'][0]['detail']
4466+
4467+
def test_switch_active_submission_with_cedar_record_succeeds(
4468+
self, app, user_one, project, url, payload, cedar_template):
4469+
from osf.models import CedarMetadataRecord
4470+
CedarMetadataRecord.objects.create(
4471+
guid=project.guids.first(),
4472+
template=cedar_template,
4473+
metadata={'title': 'Test'},
4474+
is_published=True,
4475+
)
4476+
with capture_notifications():
4477+
with mock_update_share():
4478+
with override_switch(features.COLLECTION_SUBMISSION_WITH_CEDAR, active=True):
4479+
res = app.post_json_api(
4480+
url,
4481+
payload(guid=project._id),
4482+
auth=user_one.auth,
4483+
)
4484+
assert res.status_code == 201
4485+
4486+
def test_switch_inactive_submission_without_cedar_record_succeeds(
4487+
self, app, user_one, project, url, payload, cedar_template):
4488+
with capture_notifications():
4489+
with mock_update_share():
4490+
with override_switch(features.COLLECTION_SUBMISSION_WITH_CEDAR, active=False):
4491+
res = app.post_json_api(url, payload(guid=project._id), auth=user_one.auth)
4492+
assert res.status_code == 201
4493+
4494+
def test_switch_active_update_does_not_alter_cedar_record(
4495+
self, app, user_one, project, url, payload, cedar_template, collection):
4496+
from osf.models import CedarMetadataRecord
4497+
original_metadata = {'title': 'Original'}
4498+
CedarMetadataRecord.objects.create(
4499+
guid=project.guids.first(),
4500+
template=cedar_template,
4501+
metadata=original_metadata,
4502+
is_published=True,
4503+
)
4504+
collection.status_choices = ['pending', 'approved']
4505+
collection.save()
4506+
with capture_notifications():
4507+
with mock_update_share():
4508+
with override_switch(features.COLLECTION_SUBMISSION_WITH_CEDAR, active=True):
4509+
res = app.post_json_api(url, payload(guid=project._id, status='pending'), auth=user_one.auth)
4510+
assert res.status_code == 201
4511+
4512+
detail_url = f'/{API_BASE}collections/{collection._id}/collected_metadata/{project._id}/'
4513+
with override_switch(features.COLLECTION_SUBMISSION_WITH_CEDAR, active=True):
4514+
app.patch_json_api(detail_url, payload(status='approved'), auth=user_one.auth)
4515+
4516+
record = CedarMetadataRecord.objects.get(guid__in=project.guids.all(), template=cedar_template)
4517+
assert record.metadata == original_metadata
44634518

44644519

44654520
class TestCollectedMetaSubjectFiltering(SubjectsFilterMixin):

osf/models/cedar_metadata.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
1+
from django.core.exceptions import ValidationError
12
from django.db import models
3+
from jsonschema import validate as jsonschema_validate, ValidationError as JsonSchemaValidationError
24

35
from osf.models.base import BaseModel, ObjectIDMixin
46
from osf.utils.datetime_aware_jsonfield import DateTimeAwareJSONField
57

8+
# Fields generated by the CEDAR server on instance creation — frontend never sends them
9+
CEDAR_SERVER_GENERATED_FIELDS = frozenset({
10+
'@id',
11+
'pav:createdOn',
12+
'pav:createdBy',
13+
'pav:lastUpdatedOn',
14+
'oslc:modifiedBy',
15+
})
16+
617

718
class CedarMetadataTemplate(ObjectIDMixin, BaseModel):
819
schema_name = models.CharField(max_length=255, default=None)
@@ -47,6 +58,22 @@ def get_template_name(self):
4758
def get_template_version(self):
4859
return self.template.template_version
4960

61+
def clean(self):
62+
if self.is_published:
63+
schema = dict(self.template.template)
64+
if 'required' in schema:
65+
schema['required'] = [
66+
f for f in schema['required']
67+
if f not in CEDAR_SERVER_GENERATED_FIELDS
68+
]
69+
metadata = {k: v for k, v in self.metadata.items() if v != {}}
70+
try:
71+
jsonschema_validate(metadata, schema)
72+
except JsonSchemaValidationError as e:
73+
raise ValidationError(
74+
f'CEDAR metadata does not validate against template "{self.template.schema_name}": {e.message}'
75+
)
76+
5077
def save(self, *args, **kwargs):
78+
super().save(*args, **kwargs)
5179
self.guid.referent.update_search()
52-
return super().save(*args, **kwargs)

osf/models/collection_submission.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222

2323
logger = logging.getLogger(__name__)
2424

25-
2625
class CollectionSubmission(TaxonomizableMixin, BaseModel):
2726
primary_identifier_name = 'guid___id'
2827

osf/models/provider.py

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import json
22
import requests
3-
from jsonschema import validate as jsonschema_validate, ValidationError as JsonSchemaValidationError
43

54
from django.apps import apps
65
from django.contrib.postgres import fields
@@ -21,7 +20,6 @@
2120
from .brand import Brand
2221
from .citation import CitationStyle
2322
from .licenses import NodeLicense
24-
from .cedar_metadata import CedarMetadataRecord
2523
from .storage import ProviderAssetFile
2624
from .subject import Subject
2725
from osf.utils.datetime_aware_jsonfield import DateTimeAwareJSONField
@@ -208,6 +206,24 @@ def top_level_subjects(self):
208206
def readable_type(self):
209207
raise NotImplementedError
210208

209+
def validate_required_metadata(self, obj):
210+
"""
211+
Raises ValidationError if obj does not have a published CedarMetadataRecord for
212+
this provider's required_metadata_template.
213+
Does nothing when required_metadata_template is not set.
214+
"""
215+
if not self.required_metadata_template_id:
216+
return
217+
guid = obj.guids.first()
218+
if guid is None or not guid.cedar_metadata_records.filter(
219+
template_id=self.required_metadata_template_id,
220+
is_published=True,
221+
).exists():
222+
raise ValidationError(
223+
f'Submitted object must have a published CEDAR metadata record for template '
224+
f'"{self.required_metadata_template.schema_name}" to be submitted to this collection.'
225+
)
226+
211227
def get_asset_url(self, name):
212228
""" Helper that returns an associated ProviderAssetFile's url, or None
213229
@@ -259,27 +275,6 @@ def setup_share_source(self, provider_home_page):
259275

260276
self.save()
261277

262-
def validate_required_metadata(self, osf_obj):
263-
if not self.required_metadata_template:
264-
return
265-
266-
record = CedarMetadataRecord.objects.filter(
267-
guid__in=osf_obj.guids.all(),
268-
template=self.required_metadata_template,
269-
is_published=True,
270-
).first()
271-
272-
if record is None:
273-
raise ValidationError(
274-
f'Object must have a published CEDAR metadata record for the required template '
275-
f'"{self.required_metadata_template.schema_name}".'
276-
)
277-
278-
try:
279-
jsonschema_validate(record.metadata, self.required_metadata_template.template)
280-
except JsonSchemaValidationError as e:
281-
raise ValidationError(e.message)
282-
283278

284279
class CollectionProvider(AbstractProvider):
285280
DEFAULT_SUBSCRIPTIONS = [

osf_tests/test_collection_submission.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -833,7 +833,8 @@ def test_share_update_cedar_metadata_record(self, unmoderated_collection_submiss
833833
},
834834
'identifier': {}
835835
}
836-
with mock.patch('api.share.utils.pls_send_trove_record'):
836+
with mock.patch('api.share.utils.pls_send_trove_record'), \
837+
mock.patch('api.share.utils.share_update_cedar_metadata_record'):
837838
record = CedarMetadataRecord.objects.create(
838839
guid=unmoderated_collection_submission_public.guid,
839840
template=cedar_template,

osf_tests/test_validate_required_metadata.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -84,20 +84,6 @@ def test_published_valid_record_passes(self, provider, cedar_template, preprint)
8484

8585
provider.validate_required_metadata(preprint)
8686

87-
def test_published_invalid_record_raises(self, provider, cedar_template, preprint):
88-
provider.required_metadata_template = cedar_template
89-
provider.save()
90-
91-
CedarMetadataRecord.objects.create(
92-
guid=preprint.guids.first(),
93-
template=cedar_template,
94-
metadata={'title': 123},
95-
is_published=True,
96-
)
97-
98-
with pytest.raises(ValidationError):
99-
provider.validate_required_metadata(preprint)
100-
10187
def test_record_for_wrong_template_raises(self, provider, cedar_template, preprint):
10288
provider.required_metadata_template = cedar_template
10389
provider.save()

0 commit comments

Comments
 (0)