Skip to content

Commit

Permalink
add preprint institutions relationship list
Browse files Browse the repository at this point in the history
  • Loading branch information
John Tordoff committed Jul 1, 2024
1 parent b4bd45a commit e97f67d
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 39 deletions.
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
60 changes: 30 additions & 30 deletions api/preprints/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,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 @@ -34,11 +35,11 @@
NodeContributorDetailSerializer,
get_license_details,
NodeTagField,
update_institutions,

)
from api.base.metrics import MetricsSerializerMixin
from api.base.serializers import BaseAPISerializer
from api.taxonomies.serializers import TaxonomizableSerializerMixin
from framework.auth import Auth
from framework.exceptions import PermissionsError
from website.project import signals as project_signals
from osf.exceptions import NodeStateError, PreprintStateError
Expand Down Expand Up @@ -543,50 +544,49 @@ def update(self, instance, validated_data):
})


class PreprintsInstitutionRelationshipSerializer(BaseAPISerializer):
id = IDField(read_only=True, source='_id')
name = ser.CharField(read_only=True)
type = ser.SerializerMethodField(read_only=True)
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.absolute_api_v2_url

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

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

class Meta:
type_ = 'institutions'

def update(self, preprint, validated_data):
user = self.context['request'].user
try:
preprint.update_institutional_affiliation(
Auth(user),
institution_ids=[od['_id'] for od in validated_data['data']],
)
except ValidationError as e:
raise exceptions.ValidationError(list(e)[0])
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):
preprint = Preprint.load(self.context['view'].kwargs['preprint_id'])
instance = self.context['view'].get_object()
user = self.context['request'].user
data = self.context['request'].data['data']
try:
preprint.update_institutional_affiliation(
Auth(user),
institution_ids=[od['id'] for od in data],
)
except ValidationError as e:
raise exceptions.ValidationError(list(e)[0])
preprint = instance['self']
update_institutions(preprint, validated_data['data'], user, post=True)
preprint.save()
return preprint

return self.make_instance_obj(preprint)
26 changes: 21 additions & 5 deletions api/preprints/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
from api.institutions.serializers import InstitutionSerializer
from api.base.parsers import JSONAPIRelationshipParser
from api.base.parsers import JSONAPIRelationshipParserForRegularJSON
from api.preprints.serializers import PreprintsInstitutionRelationshipSerializer
from api.preprints.serializers import PreprintsInstitutionsRelationshipSerializer
from api.nodes.permissions import WriteOrPublicForRelationshipInstitutions


Expand Down Expand Up @@ -652,16 +652,16 @@ def get_queryset(self):
return self.get_resource().affiliated_institutions.all()


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

view_category = 'preprints'
Expand All @@ -678,3 +678,19 @@ def get_object(self):
}
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)
1 change: 0 additions & 1 deletion api/requests/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,3 @@ def has_object_permission(self, request, view, obj):
# Requesters may edit their comment or submit their request
return is_requester
return False

163 changes: 163 additions & 0 deletions api_tests/preprints/views/test_preprint_institutions_relationship.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import pytest

from api.base.settings.defaults import API_BASE
from osf_tests.factories import (
PreprintFactory,
AuthUserFactory,
InstitutionFactory,
)


@pytest.mark.django_db
class TestPreprintInstitutionsList:

@pytest.fixture()
def user(self):
return AuthUserFactory()

@pytest.fixture()
def admin_with_institutional_affilation(self, institution, preprint):
user = AuthUserFactory()
preprint.add_permission(user, 'admin')
user.add_or_update_affiliated_institution(institution)
return user

@pytest.fixture()
def no_auth_with_institutional_affilation(self, institution):
user = AuthUserFactory()
user.add_or_update_affiliated_institution(institution)
return user

@pytest.fixture()
def admin_without_institutional_affilation(self, institution, preprint):
user = AuthUserFactory()
preprint.add_permission(user, 'admin')
return user

@pytest.fixture()
def institution(self):
return InstitutionFactory()

@pytest.fixture()
def preprint(self):
return PreprintFactory()

@pytest.fixture()
def url(self, preprint):
return f'/{API_BASE}preprints/{preprint._id}/relationships/institutions/'

def test_update_affiliated_institutions_add(self, app, user, admin_with_institutional_affilation, admin_without_institutional_affilation, preprint, url,
institution):
update_institutions_payload = {
'data': [{'type': 'institutions', 'id': institution._id}]
}

res = app.put_json_api(
url,
update_institutions_payload,
auth=user.auth,
expect_errors=True
)
assert res.status_code == 403

res = app.put_json_api(
url,
update_institutions_payload,
auth=admin_without_institutional_affilation.auth,
expect_errors=True
)
assert res.status_code == 403
assert res.json['errors'][0]['detail'] == f'User needs to be affiliated with {institution.name}'

res = app.put_json_api(
url,
update_institutions_payload,
auth=admin_with_institutional_affilation.auth
)
assert res.status_code == 200

preprint.reload()
assert institution in preprint.affiliated_institutions.all()

log = preprint.logs.latest()
assert log.action == 'affiliated_institution_added'
assert log.params['institution'] == {
'id': institution._id,
'name': institution.name
}

def test_update_affiliated_institutions_remove(self, app, user, admin_with_institutional_affilation, no_auth_with_institutional_affilation, admin_without_institutional_affilation, preprint, url,
institution):

preprint.affiliated_institutions.add(institution)
preprint.save()

update_institutions_payload = {
'data': []
}

res = app.put_json_api(
url,
update_institutions_payload,
auth=user.auth,
expect_errors=True
)
assert res.status_code == 403

res = app.put_json_api(
url,
update_institutions_payload,
auth=no_auth_with_institutional_affilation.auth,
expect_errors=True
)
assert res.status_code == 403

res = app.put_json_api(
url,
update_institutions_payload,
auth=admin_without_institutional_affilation.auth,
expect_errors=True
)
assert res.status_code == 200 # you can always remove it you are an admin

res = app.put_json_api(
url,
update_institutions_payload,
auth=admin_with_institutional_affilation.auth
)
assert res.status_code == 200

preprint.reload()
assert institution not in preprint.affiliated_institutions.all()

log = preprint.logs.latest()
assert log.action == 'affiliated_institution_removed'
assert log.params['institution'] == {
'id': institution._id,
'name': institution.name
}

def test_preprint_institutions_list_get(self, app, user, admin_with_institutional_affilation, admin_without_institutional_affilation, preprint, url,
institution):
# For testing purposes
preprint.is_public = False
preprint.save()

res = app.get(url, expect_errors=True)
assert res.status_code == 401

res = app.get(url, auth=user.auth, expect_errors=True)
assert res.status_code == 403

res = app.get(url, auth=admin_without_institutional_affilation.auth, expect_errors=True)
assert res.status_code == 200

assert res.status_code == 200
assert not res.json['data']

preprint.add_affiliated_institution(institution, admin_with_institutional_affilation)
res = app.get(url, auth=admin_with_institutional_affilation.auth)
assert res.status_code == 200

assert res.json['data'][0]['id'] == institution._id
assert res.json['data'][0]['type'] == 'institutions'

0 comments on commit e97f67d

Please sign in to comment.