Skip to content
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

[ENG-5845] Preprint Institutions Relationship #10659

Merged
14 changes: 14 additions & 0 deletions api/base/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
from framework.auth.cas import CasResponse

from osf.models import ApiOAuth2Application, ApiOAuth2PersonalToken
from osf.utils import permissions as osf_permissions
from website.util.sanitize import is_iterable_but_not_string
from api.base.utils import get_user_auth


# Implementation built on django-oauth-toolkit, but with more granular control over read+write permissions
Expand Down Expand Up @@ -158,3 +160,15 @@ def has_object_permission(self, request, view, obj):
obj = self.get_object(request, view, obj)
return super(Perm, self).has_object_permission(request, view, obj)
return Perm


class WriteOrPublicForRelationshipInstitutions(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
assert isinstance(obj, dict)
auth = get_user_auth(request)
resource = obj['self']

if request.method in permissions.SAFE_METHODS:
return resource.is_public or resource.can_view(auth)
else:
return resource.has_permission(auth.user, osf_permissions.WRITE)
6 changes: 6 additions & 0 deletions api/institutions/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,3 +292,9 @@ def get_absolute_url(self, obj):
'version': 'v2',
},
)


class InstitutionRelated(JSONAPIRelationshipSerializer):
id = ser.CharField(source='_id', required=False, allow_null=True)
class Meta:
type_ = 'institutions'
12 changes: 0 additions & 12 deletions api/nodes/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,18 +295,6 @@ def has_object_permission(self, request, view, obj):
return True


class WriteOrPublicForRelationshipInstitutions(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
assert isinstance(obj, dict)
auth = get_user_auth(request)
node = obj['self']

if request.method in permissions.SAFE_METHODS:
return node.is_public or node.can_view(auth)
else:
return node.has_permission(auth.user, osf_permissions.WRITE)


class ReadOnlyIfRegistration(permissions.BasePermission):
"""Makes PUT and POST forbidden for registrations."""

Expand Down
8 changes: 2 additions & 6 deletions api/nodes/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
)
from api.base.serializers import (
VersionedDateTimeField, HideIfRegistration, IDField,
JSONAPIRelationshipSerializer,
JSONAPISerializer, LinksField,
NodeFileHyperLinkField, RelationshipField,
ShowIfVersion, TargetTypeField, TypeField,
Expand Down Expand Up @@ -1467,13 +1466,10 @@ def get_storage_addons_url(self, obj):
},
)

class InstitutionRelated(JSONAPIRelationshipSerializer):
id = ser.CharField(source='_id', required=False, allow_null=True)
class Meta:
type_ = 'institutions'


class NodeInstitutionsRelationshipSerializer(BaseAPISerializer):
from api.institutions.serializers import InstitutionRelated # Avoid circular import

data = ser.ListField(child=InstitutionRelated())
links = LinksField({
'self': 'get_self_url',
Expand Down
2 changes: 1 addition & 1 deletion api/nodes/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
WaterButlerMixin,
)
from api.base.waffle_decorators import require_flag
from api.base.permissions import WriteOrPublicForRelationshipInstitutions
from api.cedar_metadata_records.serializers import CedarMetadataRecordsListSerializer
from api.cedar_metadata_records.utils import can_view_record
from api.citations.utils import render_citation
Expand Down Expand Up @@ -87,7 +88,6 @@
NodeGroupDetailPermissions,
IsContributorOrGroupMember,
AdminDeletePermissions,
WriteOrPublicForRelationshipInstitutions,
ExcludeWithdrawals,
NodeLinksShowIfVersion,
ReadOnlyIfWithdrawn,
Expand Down
49 changes: 46 additions & 3 deletions api/preprints/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from api.base.exceptions import Conflict, JSONAPIException
from api.base.serializers import (
BaseAPISerializer,
JSONAPISerializer,
IDField,
TypeField,
Expand All @@ -33,12 +34,14 @@
NodeContributorDetailSerializer,
get_license_details,
NodeTagField,
update_institutions,

)
from api.base.metrics import MetricsSerializerMixin
from api.taxonomies.serializers import TaxonomizableSerializerMixin
from framework.exceptions import PermissionsError
from website.project import signals as project_signals
from osf.exceptions import NodeStateError
from osf.exceptions import NodeStateError, PreprintStateError
from osf.models import (
BaseFileNode,
Preprint,
Expand All @@ -48,8 +51,6 @@
)
from osf.utils import permissions as osf_permissions

from osf.exceptions import PreprintStateError


class PrimaryFileRelationshipField(RelationshipField):
def get_object(self, file_id):
Expand Down Expand Up @@ -530,3 +531,45 @@ def update(self, instance, validated_data):
links = LinksField({
'self': 'get_self_url',
})


class PreprintsInstitutionsRelationshipSerializer(BaseAPISerializer):
from api.institutions.serializers import InstitutionRelated # Avoid circular import
data = ser.ListField(child=InstitutionRelated())

links = LinksField({
'self': 'get_self_url',
'html': 'get_related_url',
})

def get_self_url(self, obj):
return obj['self'].absolute_api_v2_url

def get_related_url(self, obj):
return f"{obj['self'].absolute_api_v2_url}institutions/"

class Meta:
type_ = 'institutions'

def make_instance_obj(self, obj):
return {
'data': obj.affiliated_institutions.all(),
'self': obj,
}

def update(self, instance, validated_data):
preprint = instance['self']
user = self.context['request'].user
update_institutions(preprint, validated_data['data'], user)
preprint.save()

return self.make_instance_obj(preprint)

def create(self, validated_data):
instance = self.context['view'].get_object()
user = self.context['request'].user
preprint = instance['self']
update_institutions(preprint, validated_data['data'], user, post=True)
preprint.save()

return self.make_instance_obj(preprint)
1 change: 1 addition & 0 deletions api/preprints/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@
re_path(r'^(?P<preprint_id>\w+)/requests/$', views.PreprintRequestListCreate.as_view(), name=views.PreprintRequestListCreate.view_name),
re_path(r'^(?P<preprint_id>\w+)/subjects/$', views.PreprintSubjectsList.as_view(), name=views.PreprintSubjectsList.view_name),
re_path(r'^(?P<preprint_id>\w+)/institutions/$', views.PreprintInstitutionsList.as_view(), name=views.PreprintInstitutionsList.view_name),
re_path(r'^(?P<preprint_id>\w+)/relationships/institutions/$', views.PreprintInstitutionsRelationshipList.as_view(), name=views.PreprintInstitutionsRelationshipList.view_name),
]
51 changes: 47 additions & 4 deletions api/preprints/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
JSONAPIMultipleRelationshipsParserForRegularJSON,
JSONAPIOnetoOneRelationshipParser,
JSONAPIOnetoOneRelationshipParserForRegularJSON,
JSONAPIRelationshipParser,
JSONAPIRelationshipParserForRegularJSON,
)
from api.base.utils import absolute_reverse, get_user_auth, get_object_or_error
from api.base import permissions as base_permissions
Expand All @@ -39,6 +41,7 @@
PreprintStorageProviderSerializer,
PreprintNodeRelationshipSerializer,
PreprintContributorsCreateSerializer,
PreprintsInstitutionsRelationshipSerializer,
)
from api.files.serializers import OsfStorageFileSerializer
from api.nodes.serializers import (
cslzchen marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -47,6 +50,7 @@

from api.identifiers.views import IdentifierList
from api.identifiers.serializers import PreprintIdentifierSerializer
from api.institutions.serializers import InstitutionSerializer
from api.nodes.views import NodeMixin, NodeContributorsList, NodeContributorDetail, NodeFilesList, NodeStorageProvidersList, NodeStorageProvider
from api.preprints.permissions import (
PreprintPublishedOrAdmin,
Expand All @@ -57,16 +61,14 @@
PreprintFilesPermissions,
PreprintInstitutionPermissionList,
)
from api.nodes.permissions import (
ContributorOrPublic,
)
from api.nodes.permissions import ContributorOrPublic
from api.base.permissions import WriteOrPublicForRelationshipInstitutions
from api.requests.permissions import PreprintRequestPermission
from api.requests.serializers import PreprintRequestSerializer, PreprintRequestCreateSerializer
from api.requests.views import PreprintRequestMixin
from api.subjects.views import BaseResourceSubjectsList
from api.base.metrics import PreprintMetricsViewMixin
from osf.metrics import PreprintDownload, PreprintView
from api.institutions.serializers import InstitutionSerializer


class PreprintMixin(NodeMixin):
Expand Down Expand Up @@ -647,3 +649,44 @@ def get_resource(self):

def get_queryset(self):
return self.get_resource().affiliated_institutions.all()


class PreprintInstitutionsRelationshipList(JSONAPIBaseView, generics.RetrieveUpdateDestroyAPIView, generics.CreateAPIView, PreprintMixin):
""" """
permission_classes = (
drf_permissions.IsAuthenticatedOrReadOnly,
base_permissions.TokenHasScope,
WriteOrPublicForRelationshipInstitutions,
)
required_read_scopes = [CoreScopes.PREPRINTS_READ]
required_write_scopes = [CoreScopes.PREPRINTS_WRITE]
serializer_class = PreprintsInstitutionsRelationshipSerializer
parser_classes = (JSONAPIRelationshipParser, JSONAPIRelationshipParserForRegularJSON, )

view_category = 'preprints'
view_name = 'preprint-relationships-institutions'

def get_resource(self):
return self.get_preprint(check_object_permissions=False)

def get_object(self):
preprint = self.get_resource()
obj = {
'data': preprint.affiliated_institutions.all(),
'self': preprint,
}
self.check_object_permissions(self.request, obj)
return obj

def perform_destroy(self, instance):
data = self.request.data['data']
user = self.request.user
current_insts = {inst._id: inst for inst in instance['data']}
node = instance['self']

for val in data:
if val['id'] in current_insts:
if not user.is_affiliated_with_institution(current_insts[val['id']]) and not node.has_permission(user, 'admin'):
cslzchen marked this conversation as resolved.
Show resolved Hide resolved
raise PermissionDenied
node.remove_affiliated_institution(inst=current_insts[val['id']], user=user)
node.save()
Loading
Loading