forked from CenterForOpenScience/osf.io
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request CenterForOpenScience#10513 from cslzchen/feature/e…
…ng-5020-record-crud-api [ENG-5020] Implement CRUD API for CedarMetadataRecord
- Loading branch information
Showing
10 changed files
with
239 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import logging | ||
|
||
from rest_framework import permissions | ||
|
||
from api.base.utils import get_user_auth | ||
|
||
from osf.models import BaseFileNode, CedarMetadataRecord, Node, Registration | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class CedarMetadataRecordPermission(permissions.BasePermission): | ||
|
||
def has_object_permission(self, request, view, obj): | ||
|
||
assert isinstance(obj, CedarMetadataRecord), 'obj must be a CedarMetadataRecord' | ||
|
||
auth = get_user_auth(request) | ||
|
||
permission_source = obj.guid.referent | ||
if isinstance(permission_source, BaseFileNode): | ||
permission_source = permission_source.target | ||
elif not isinstance(permission_source, (Node, Registration)): | ||
return False | ||
|
||
if request.method in permissions.SAFE_METHODS: | ||
is_public = permission_source.is_public and obj.is_published | ||
return is_public or permission_source.can_view(auth) | ||
return permission_source.can_edit(auth) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
import logging | ||
|
||
from django.db import IntegrityError | ||
from rest_framework import serializers as ser | ||
|
||
from api.base.exceptions import InvalidModelValueError, JSONAPIException | ||
from api.base.serializers import JSONAPISerializer, LinksField, RelationshipField | ||
from api.base.utils import absolute_reverse | ||
|
||
from osf.exceptions import ValidationError | ||
from osf.models import CedarMetadataRecord, CedarMetadataTemplate, Guid | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class TargetRelationshipField(RelationshipField): | ||
|
||
def get_object(self, _id): | ||
return Guid.load(_id) | ||
|
||
def to_internal_value(self, data): | ||
return self.get_object(data) | ||
|
||
|
||
class CedarMetadataTemplateRelationshipField(RelationshipField): | ||
|
||
def get_object(self, _id): | ||
return CedarMetadataTemplate.load(_id) | ||
|
||
def to_internal_value(self, data): | ||
return self.get_object(data) | ||
|
||
|
||
class CedarMetadataRecordsSerializer(JSONAPISerializer): | ||
|
||
class Meta: | ||
type_ = 'cedar-metadata-records' | ||
|
||
filterable_fields = frozenset(['is_published']) | ||
|
||
id = ser.CharField(source='_id', read_only=True) | ||
|
||
target = RelationshipField( | ||
source='guid', | ||
related_view='guids:guid-detail', | ||
related_view_kwargs={'guids': '<guid._id>'}, | ||
# always_embed=True, | ||
read_only=True, | ||
) | ||
|
||
template = RelationshipField( | ||
related_view='cedar-metadata-templates:cedar-metadata-template-detail', | ||
related_view_kwargs={'template_id': '<template._id>'}, | ||
# always_embed=True, | ||
read_only=True, | ||
) | ||
|
||
metadata = ser.DictField(read_only=False) | ||
|
||
is_published = ser.BooleanField(read_only=False) | ||
|
||
links = LinksField({'self': 'get_absolute_url'}) | ||
|
||
def get_absolute_url(self, obj): | ||
return absolute_reverse('cedar-metadata-records:cedar-metadata-record-detail', kwargs={'record_id': obj._id}) | ||
|
||
def update(self, instance, validated_data): | ||
assert isinstance(instance, CedarMetadataRecord), 'instance must be a CedarMetadataRecord' | ||
for key, value in validated_data.items(): | ||
if key == 'metadata': | ||
instance.metadata = value | ||
elif key == 'is_published': | ||
instance.is_published = value | ||
else: | ||
continue # ignore other attributes | ||
instance.save() | ||
return instance | ||
|
||
|
||
class CedarMetadataRecordsCreateSerializer(CedarMetadataRecordsSerializer): | ||
|
||
target = TargetRelationshipField( | ||
source='guid', | ||
related_view='guids:guid-detail', | ||
related_view_kwargs={'guids': '<guid._id>'}, | ||
# always_embed=True, | ||
read_only=False, | ||
required=True, | ||
) | ||
|
||
template = CedarMetadataTemplateRelationshipField( | ||
related_view='cedar-metadata-templates:cedar-metadata-template-detail', | ||
related_view_kwargs={'template_id': '<template._id>'}, | ||
# always_embed=True, | ||
read_only=False, | ||
required=True, | ||
) | ||
|
||
metadata = ser.DictField(read_only=False, required=True) | ||
|
||
is_published = ser.BooleanField(read_only=False, required=True) | ||
|
||
def create(self, validated_data): | ||
|
||
guid = validated_data.pop('guid') | ||
template = validated_data.pop('template') | ||
metadata = validated_data.pop('metadata') | ||
is_published = validated_data.pop('is_published') | ||
record = CedarMetadataRecord(guid=guid, template=template, metadata=metadata, is_published=is_published) | ||
try: | ||
record.save() | ||
except ValidationError as e: | ||
raise InvalidModelValueError(detail=e.messages[0]) | ||
except IntegrityError: | ||
raise JSONAPIException(detail=f'Cedar metadata record already exists: guid=[{guid._id}], template=[{template._id}]') | ||
return record |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
from django.urls import re_path | ||
|
||
from api.cedar_metadata_records import views | ||
|
||
app_name = 'osf' | ||
|
||
urlpatterns = [ | ||
re_path(r'^$', views.CedarMetadataRecordList.as_view(), name=views.CedarMetadataRecordList.view_name), | ||
re_path(r'^(?P<record_id>[0-9A-Za-z]+)/$', views.CedarMetadataRecordDetail.as_view(), name=views.CedarMetadataRecordDetail.view_name), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
from __future__ import unicode_literals | ||
import logging | ||
|
||
from rest_framework import permissions as drf_permissions | ||
from rest_framework.exceptions import NotFound | ||
from rest_framework.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIView | ||
|
||
from api.base import permissions as base_permissions | ||
from api.base.filters import ListFilterMixin | ||
from api.base.parsers import ( | ||
JSONAPIMultipleRelationshipsParser, | ||
JSONAPIMultipleRelationshipsParserForRegularJSON, | ||
) | ||
from api.base.versioning import PrivateVersioning | ||
from api.base.views import JSONAPIBaseView | ||
from api.cedar_metadata_records.permissions import CedarMetadataRecordPermission | ||
from api.cedar_metadata_records.serializers import CedarMetadataRecordsSerializer, CedarMetadataRecordsCreateSerializer | ||
|
||
from framework.auth.oauth_scopes import CoreScopes | ||
|
||
from osf.models import CedarMetadataRecord | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class CedarMetadataRecordList(JSONAPIBaseView, ListCreateAPIView, ListFilterMixin): | ||
|
||
permission_classes = ( | ||
CedarMetadataRecordPermission, | ||
drf_permissions.IsAuthenticatedOrReadOnly, | ||
base_permissions.TokenHasScope, | ||
) | ||
required_read_scopes = [CoreScopes.CEDAR_METADATA_RECORD_READ] | ||
required_write_scopes = [CoreScopes.CEDAR_METADATA_RECORD_WRITE] | ||
|
||
serializer_class = CedarMetadataRecordsCreateSerializer | ||
parser_classes = (JSONAPIMultipleRelationshipsParser, JSONAPIMultipleRelationshipsParserForRegularJSON, ) | ||
model_class = CedarMetadataRecord | ||
|
||
# This view goes under the _/ namespace | ||
versioning_class = PrivateVersioning | ||
view_category = 'cedar-metadata-records' | ||
view_name = 'cedar-metadata-record-list' | ||
|
||
def get_default_queryset(self): | ||
return CedarMetadataRecord.objects.filter(is_published=True) | ||
|
||
def get_queryset(self): | ||
return self.get_queryset_from_request() | ||
|
||
|
||
class CedarMetadataRecordDetail(JSONAPIBaseView, RetrieveUpdateDestroyAPIView): | ||
|
||
permission_classes = ( | ||
CedarMetadataRecordPermission, | ||
drf_permissions.IsAuthenticatedOrReadOnly, | ||
base_permissions.TokenHasScope, | ||
) | ||
required_read_scopes = [CoreScopes.CEDAR_METADATA_RECORD_READ] | ||
required_write_scopes = [CoreScopes.CEDAR_METADATA_RECORD_WRITE] | ||
|
||
serializer_class = CedarMetadataRecordsSerializer | ||
|
||
# This view goes under the _/ namespace | ||
versioning_class = PrivateVersioning | ||
view_category = 'cedar-metadata-records' | ||
view_name = 'cedar-metadata-record-detail' | ||
|
||
def get_object(self): | ||
try: | ||
return CedarMetadataRecord.objects.get(_id=self.kwargs['record_id']) | ||
except CedarMetadataRecord.DoesNotExist: | ||
raise NotFound |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters