Skip to content

Commit e70c418

Browse files
committed
Add switch to enforce required_metadata_template
1 parent 94ce69f commit e70c418

File tree

4 files changed

+121
-8
lines changed

4 files changed

+121
-8
lines changed

api/collections/serializers.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import waffle
2+
13
from django.db import IntegrityError
24
from rest_framework import exceptions
35
from rest_framework import serializers as ser
46

7+
from osf import features
58
from osf.models import AbstractNode, Node, Collection, Guid, Registration
69
from osf.exceptions import ValidationError, NodeStateError
710
from api.base.serializers import LinksField, RelationshipField, LinkedNodesRelationshipSerializer, LinkedRegistrationsRelationshipSerializer, LinkedPreprintsRelationshipSerializer
@@ -426,6 +429,11 @@ def create(self, validated_data):
426429
raise exceptions.ValidationError('"creator" must be specified.')
427430
if not (creator.has_perm('write_collection', collection) or (hasattr(guid.referent, 'has_permission') and guid.referent.has_permission(creator, WRITE))):
428431
raise exceptions.PermissionDenied('Must have write permission on either collection or collected object to collect.')
432+
if waffle.switch_is_active(features.COLLECTION_SUBMISSION_WITH_CEDAR) and collection.provider_id:
433+
try:
434+
collection.provider.validate_required_metadata(guid.referent)
435+
except ValidationError as e:
436+
raise InvalidModelValueError(e.message)
429437
try:
430438
obj = collection.collect_object(guid.referent, creator, **validated_data)
431439
except ValidationError as e:
@@ -462,6 +470,11 @@ def create(self, validated_data):
462470
raise exceptions.ValidationError('"creator" must be specified.')
463471
if not (creator.has_perm('write_collection', collection) or (hasattr(guid.referent, 'has_permission') and guid.referent.has_permission(creator, WRITE))):
464472
raise exceptions.PermissionDenied('Must have write permission on either collection or collected object to collect.')
473+
if waffle.switch_is_active(features.COLLECTION_SUBMISSION_WITH_CEDAR) and collection.provider_id:
474+
try:
475+
collection.provider.validate_required_metadata(guid.referent)
476+
except ValidationError as e:
477+
raise InvalidModelValueError(e.message)
465478
try:
466479
obj = collection.collect_object(guid.referent, creator, **validated_data)
467480
except ValidationError as e:

api_tests/collections/test_views.py

Lines changed: 86 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,34 @@
1-
import pytest
21
from urllib.parse import urlparse
32

3+
import pytest
44
from django.utils.timezone import now
5+
from waffle.testutils import override_switch
56

67
from api.base.settings.defaults import API_BASE
78
from api.taxonomies.serializers import subjects_as_relationships_version
8-
from api_tests.subjects.mixins import UpdateSubjectsMixin, SubjectsFilterMixin, SubjectsListMixin, SubjectsRelationshipMixin
9+
from api_tests.share._utils import mock_update_share
10+
from api_tests.subjects.mixins import UpdateSubjectsMixin, SubjectsFilterMixin, SubjectsListMixin, \
11+
SubjectsRelationshipMixin
12+
from api_tests.utils import disconnected_from_listeners
913
from framework.auth.core import Auth
14+
from osf import features
15+
from osf.models import Collection, VersionedGuidMixin
16+
from osf.utils.permissions import ADMIN, WRITE, READ
17+
from osf.utils.sanitize import strip_html
1018
from osf_tests.factories import (
19+
CedarMetadataTemplateFactory,
1120
CollectionFactory,
21+
CollectionProviderFactory,
1222
NodeFactory,
1323
RegistrationFactory,
1424
PreprintFactory,
1525
ProjectFactory,
1626
AuthUserFactory,
1727
SubjectFactory,
1828
)
19-
from osf.models import Collection, VersionedGuidMixin
20-
from osf.utils.sanitize import strip_html
21-
from osf.utils.permissions import ADMIN, WRITE, READ
2229
from website.project.signals import contributor_removed
23-
from api_tests.utils import disconnected_from_listeners
24-
from api_tests.share._utils import mock_update_share
2530
from website.views import find_bookmark_collection
2631

27-
2832
url_collection_list = f'/{API_BASE}collections/'
2933

3034

@@ -4384,6 +4388,80 @@ def test_filters(self, app, collection_with_one_collection_submission, collectio
43844388
assert len(res.json['data']) == 1
43854389

43864390

4391+
@pytest.mark.django_db
4392+
class TestCollectionSubmissionWithCedarSwitch:
4393+
4394+
@pytest.fixture()
4395+
def cedar_template(self):
4396+
return CedarMetadataTemplateFactory(
4397+
schema_name='Test Schema',
4398+
cedar_id='https://cedar.example.com/template/1',
4399+
template_version=1,
4400+
)
4401+
4402+
@pytest.fixture()
4403+
def provider(self, cedar_template):
4404+
provider = CollectionProviderFactory()
4405+
provider.required_metadata_template = cedar_template
4406+
provider.save()
4407+
return provider
4408+
4409+
@pytest.fixture()
4410+
def collection(self, user_one, provider):
4411+
c = CollectionFactory(creator=user_one)
4412+
c.provider = provider
4413+
c.save()
4414+
return c
4415+
4416+
@pytest.fixture()
4417+
def collection_no_provider(self, user_one):
4418+
return CollectionFactory(creator=user_one)
4419+
4420+
@pytest.fixture()
4421+
def project(self, user_one):
4422+
return ProjectFactory(creator=user_one)
4423+
4424+
@pytest.fixture()
4425+
def url(self, collection):
4426+
return f'/{API_BASE}collections/{collection._id}/collected_metadata/'
4427+
4428+
@pytest.fixture()
4429+
def url_no_provider(self, collection_no_provider):
4430+
return f'/{API_BASE}collections/{collection_no_provider._id}/collected_metadata/'
4431+
4432+
@pytest.fixture()
4433+
def payload(self):
4434+
def make_collection_payload(**attributes):
4435+
return {
4436+
'data': {
4437+
'type': 'collected-metadata',
4438+
'attributes': attributes,
4439+
}
4440+
}
4441+
return make_collection_payload
4442+
4443+
def test_switch_active_no_provider_submission_succeeds(self, app, user_one, project, url_no_provider, payload):
4444+
with mock_update_share():
4445+
with override_switch(features.COLLECTION_SUBMISSION_WITH_CEDAR, active=True):
4446+
res = app.post_json_api(
4447+
url_no_provider,
4448+
payload(guid=project._id),
4449+
auth=user_one.auth,
4450+
)
4451+
assert res.status_code == 201
4452+
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+
)
4461+
assert res.status_code == 400
4462+
assert 'CEDAR metadata record' in res.json['errors'][0]['detail']
4463+
4464+
43874465
class TestCollectedMetaSubjectFiltering(SubjectsFilterMixin):
43884466
@pytest.fixture()
43894467
def project_one(self, user):

osf/features.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,8 @@ switches:
113113
name: populate_notification_types
114114
note: This is used to enable auto population of notification types.
115115
active: false
116+
117+
- flag_name: COLLECTION_SUBMISSION_WITH_CEDAR
118+
name: collection_submission_with_cedar
119+
note: When active, enforces that objects submitted to a collection have a CEDAR metadata record matching the provider's required_metadata_template.
120+
active: false

osf/models/provider.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,23 @@ def update_or_create_from_json(cls, provider_data, user):
165165
related_name='required_by_providers',
166166
)
167167

168+
def validate_required_metadata(self, obj):
169+
"""
170+
Raises ValidationError if obj does not have a CedarMetadataRecord for
171+
this provider's required_metadata_template.
172+
Does nothing when required_metadata_template is not set.
173+
"""
174+
if not self.required_metadata_template_id:
175+
return
176+
guid = obj.guids.first()
177+
if guid is None or not guid.cedar_metadata_records.filter(
178+
template_id=self.required_metadata_template_id
179+
).exists():
180+
raise ValidationError(
181+
f'Submitted object must have a CEDAR metadata record for template '
182+
f'"{self.required_metadata_template.schema_name}" to be submitted to this collection.'
183+
)
184+
168185
def __repr__(self):
169186
return ('(name={self.name!r}, default_license={self.default_license!r}, '
170187
'allow_submissions={self.allow_submissions!r}) with id {self.id!r}').format(self=self)

0 commit comments

Comments
 (0)