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

[Don't Merge] [ENG-5826] Proof-of-Concept / Analysis: Preprints Institution Affiliation #10646

6 changes: 3 additions & 3 deletions api/nodes/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,12 +299,12 @@ class WriteOrPublicForRelationshipInstitutions(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
assert isinstance(obj, dict)
auth = get_user_auth(request)
node = obj['self']
resource = obj['self']

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


class ReadOnlyIfRegistration(permissions.BasePermission):
Expand Down
15 changes: 15 additions & 0 deletions api/preprints/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,18 @@ def has_object_permission(self, request, view, obj):
raise exceptions.PermissionDenied(detail='Withdrawn preprints may not be edited')
return True
raise exceptions.NotFound


class PreprintInstitutionPermission(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if obj.is_public:
return True

auth = get_user_auth(request)
if not auth.user:
return False

if request.method in permissions.SAFE_METHODS:
return obj.has_permission(auth.user, 'read')
else:
return obj.has_permission(auth.user, 'write')
66 changes: 63 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 @@ -20,6 +21,7 @@
WaterbutlerLink,
HideIfPreprint,
LinkedNodesRelationshipSerializer,
JSONAPIRelationshipSerializer,
)
from api.base.utils import absolute_reverse, get_user_auth
from api.base.parsers import NO_DATA_ERROR
Expand All @@ -33,12 +35,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 +52,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 @@ -190,6 +192,16 @@ class PreprintSerializer(TaxonomizableSerializerMixin, MetricsSerializerMixin, J
related_view_kwargs={'preprint_id': '<_id>'},
))

affiliated_institutions = RelationshipField(
related_view='preprints:preprint-institutions',
related_view_kwargs={'preprint_id': '<_id>'},
self_view='preprints:preprint-institutions',
self_view_kwargs={'preprint_id': '<_id>'},
read_only=False,
required=False,
allow_null=True,
)

links = LinksField(
{
'self': 'get_preprint_url',
Expand Down Expand Up @@ -530,3 +542,51 @@ def update(self, instance, validated_data):
links = LinksField({
'self': 'get_self_url',
})


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

class Meta:
type_ = 'institutions'


class PreprintsInstitutionsRelationshipSerializer(BaseAPISerializer):
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)
2 changes: 2 additions & 0 deletions api/preprints/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@
re_path(r'^(?P<preprint_id>\w+)/review_actions/$', views.PreprintActionList.as_view(), name=views.PreprintActionList.view_name),
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),
]
88 changes: 84 additions & 4 deletions api/preprints/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@
from rest_framework import permissions as drf_permissions

from framework.auth.oauth_scopes import CoreScopes
from osf.models import ReviewAction, Preprint, PreprintContributor
from osf.models import (
ReviewAction,
Preprint,
PreprintContributor,
Institution,
)
from osf.utils.requests import check_select_for_update

from api.actions.permissions import ReviewActionPermission
Expand All @@ -17,12 +22,11 @@
from api.base.views import JSONAPIBaseView, WaterButlerMixin
from api.base.filters import ListFilterMixin, PreprintFilterMixin
from api.base.parsers import (
JSONAPIOnetoOneRelationshipParser,
JSONAPIOnetoOneRelationshipParserForRegularJSON,
JSONAPIMultipleRelationshipsParser,
JSONAPIMultipleRelationshipsParserForRegularJSON,
JSONAPIOnetoOneRelationshipParser,
JSONAPIOnetoOneRelationshipParserForRegularJSON,
)

from api.base.utils import absolute_reverse, get_user_auth, get_object_or_error
from api.base import permissions as base_permissions
from api.citations.utils import render_citation
Expand All @@ -41,6 +45,7 @@
NodeCitationStyleSerializer,
)


from api.identifiers.views import IdentifierList
from api.identifiers.serializers import PreprintIdentifierSerializer
from api.nodes.views import NodeMixin, NodeContributorsList, NodeContributorDetail, NodeFilesList, NodeStorageProvidersList, NodeStorageProvider
Expand All @@ -51,6 +56,7 @@
AdminOrPublic,
ContributorDetailPermissions,
PreprintFilesPermissions,
PreprintInstitutionPermission,
)
from api.nodes.permissions import (
ContributorOrPublic,
Expand All @@ -61,6 +67,12 @@
from api.subjects.views import BaseResourceSubjectsList
from api.base.metrics import PreprintMetricsViewMixin
from osf.metrics import PreprintDownload, PreprintView
from api.institutions.serializers import InstitutionSerializer
from api.base.parsers import JSONAPIRelationshipParser
from api.base.parsers import JSONAPIRelationshipParserForRegularJSON
from api.preprints.serializers import PreprintsInstitutionsRelationshipSerializer
from api.nodes.permissions import WriteOrPublicForRelationshipInstitutions


class PreprintMixin(NodeMixin):
serializer_class = PreprintSerializer
Expand Down Expand Up @@ -614,3 +626,71 @@ def get_default_queryset(self):

def get_queryset(self):
return self.get_queryset_from_request()


class PreprintInstitutionsList(JSONAPIBaseView, generics.ListAPIView, ListFilterMixin, PreprintMixin):
"""The documentation for this endpoint can be found [here](https://developer.osf.io/#operation/preprint_institutions_list).
"""
permission_classes = (
drf_permissions.IsAuthenticatedOrReadOnly,
base_permissions.TokenHasScope,
PreprintInstitutionPermission,
)

required_read_scopes = [CoreScopes.PREPRINTS_READ, CoreScopes.INSTITUTION_READ]
required_write_scopes = [CoreScopes.NULL]
serializer_class = InstitutionSerializer

model = Institution
view_category = 'preprints'
view_name = 'preprint-institutions'

def get_resource(self):
return self.get_preprint()

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'):
raise PermissionDenied
node.remove_affiliated_institution(inst=current_insts[val['id']], user=user)
node.save()

def create(self, *args, **kwargs):
return super().create(*args, **kwargs)
Loading
Loading