Skip to content

Commit

Permalink
Change PATCH to PUT request
Browse files Browse the repository at this point in the history
  • Loading branch information
John Tordoff committed Jun 24, 2024
1 parent 827c4d1 commit 6d94922
Show file tree
Hide file tree
Showing 7 changed files with 228 additions and 122 deletions.
2 changes: 1 addition & 1 deletion api/base/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class JSONAPIParser(JSONParser):
@staticmethod
def get_relationship(data, related_resource):
target_type = data.get('type')
if not target_type and data:
if not target_type:
raise JSONAPIException(
source={'pointer': 'data/relationships/{}/data/type'.format(related_resource)},
detail=NO_TYPE_ERROR,
Expand Down
5 changes: 5 additions & 0 deletions api/institutions/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,3 +292,8 @@ 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: 12 additions & 0 deletions api/preprints/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,18 @@ def has_object_permission(self, request, view, obj):
return True


class PreprintInstitutions(PreprintPublishedOrAdmin):

def has_object_permission(self, request, view, obj):
auth = get_user_auth(request)
if not auth.user:
raise exceptions.NotAuthenticated(detail='User must has no authentication.')

if not obj['self'].has_permission(auth.user, osf_permissions.WRITE):
raise exceptions.PermissionDenied(detail='User must have admin or write permissions to the preprint.')
return True


class ContributorDetailPermissions(PreprintPublishedOrAdmin):
"""Permissions for preprint contributor detail page."""

Expand Down
51 changes: 42 additions & 9 deletions api/preprints/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,13 @@
NodeTagField,
)
from api.base.metrics import MetricsSerializerMixin
from api.base.serializers import BaseAPISerializer
from api.institutions.serializers import InstitutionRelated
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
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 @@ -391,12 +392,6 @@ def update(self, preprint, validated_data):
except PreprintStateError as e:
raise exceptions.ValidationError(detail=str(e))

if 'affiliated_institutions' in validated_data:
try:
preprint.update_institutional_affiliation(auth, validated_data['affiliated_institutions'])
except Exception as e:
raise exceptions.ValidationError(detail=str(e))

if published is not None:
if not preprint.primary_file:
raise exceptions.ValidationError(detail='A valid primary_file must be set before publishing a preprint.')
Expand Down Expand Up @@ -547,3 +542,41 @@ def update(self, instance, validated_data):
links = LinksField({
'self': 'get_self_url',
})


class PreprintsInstitutionsSerializer(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 + 'institutions/'

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

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
preprint.update_institutional_affiliation(
Auth(user),
institution_ids=[od['_id'] for od in validated_data['data']],
)
preprint.save()

return self.make_instance_obj(preprint)

def create(self, validated_data):
instance = self.context['view'].get_object()
return self.update(instance, validated_data)
57 changes: 36 additions & 21 deletions api/preprints/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@
from api.base.views import JSONAPIBaseView, WaterButlerMixin
from api.base.filters import ListFilterMixin, PreprintFilterMixin
from api.base.parsers import (
JSONAPIOnetoOneRelationshipParser,
JSONAPIOnetoOneRelationshipParserForRegularJSON,
JSONAPIRelationshipParser,
JSONAPIRelationshipParserForRegularJSON,
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 @@ -48,10 +49,14 @@
PreprintPublishedOrAdmin,
PreprintPublishedOrWrite,
ModeratorIfNeverPublicWithdrawn,
PreprintFilesPermissions,
PreprintInstitutions,
)
from api.nodes.permissions import (
AdminOrPublic,
ContributorDetailPermissions,
PreprintFilesPermissions,
)
from api.preprints.serializers import PreprintsInstitutionsSerializer
from api.nodes.permissions import (
ContributorOrPublic,
)
Expand All @@ -62,9 +67,6 @@
from api.base.metrics import PreprintMetricsViewMixin
from osf.metrics import PreprintDownload, PreprintView

from api.institutions.serializers import InstitutionSerializer
from osf.models import Institution


class PreprintMixin(NodeMixin):
serializer_class = PreprintSerializer
Expand Down Expand Up @@ -620,28 +622,41 @@ def get_queryset(self):
return self.get_queryset_from_request()


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

required_read_scopes = [CoreScopes.PREPRINTS_READ, CoreScopes.INSTITUTION_READ]
required_write_scopes = [CoreScopes.NULL]
serializer_class = InstitutionSerializer
required_read_scopes = [CoreScopes.NODE_BASE_READ]
required_write_scopes = [CoreScopes.NODE_BASE_WRITE]
serializer_class = PreprintsInstitutionsSerializer
parser_classes = (JSONAPIRelationshipParser, JSONAPIRelationshipParserForRegularJSON, )

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

ordering = ('-id',)

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

def get_queryset(self):
resource = self.get_preprint()
return resource.affiliated_institutions.all() or []
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()
91 changes: 0 additions & 91 deletions api_tests/preprints/views/test_preprint_detail.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,11 +259,6 @@ def institution(self):
def url(self, preprint):
return '/{}preprints/{}/'.format(API_BASE, preprint._id)

@pytest.fixture()
def user_with_institutional_affilation(self, user, institution):
user.add_or_update_affiliated_institution(institution)
return user

@pytest.fixture()
def subject(self):
return SubjectFactory()
Expand Down Expand Up @@ -1161,92 +1156,6 @@ def test_sloan_updates(self, app, user, preprint, url):
assert preprint.has_prereg_links == 'no'
assert preprint.why_no_prereg == 'My dog ate it.'

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

res = app.patch_json_api(
url,
update_institutions_payload,
auth=user.auth,
expect_errors=True
)
assert res.status_code == 200

res = app.patch_json_api(
url,
update_institutions_payload,
auth=user_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, user_with_institutional_affilation, preprint, url,
institution):
# First, add the institution to the preprint to ensure it exists for removal
preprint.affiliated_institutions.add(institution)
preprint.save()

update_institutions_payload = {
'data': {
'type': 'preprints',
'id': preprint._id,
'relationships': {
'affiliated_institutions': {
'type': 'institutions',
'data': []
}
}
}
}

# Attempt to remove the institution with the first user and check for 200 status code
res = app.patch_json_api(
url,
update_institutions_payload,
auth=user.auth,
expect_errors=True
)
assert res.status_code == 200

# Attempt to remove the institution with the second user and check for 200 status code
res = app.patch_json_api(
url,
update_institutions_payload,
auth=user_with_institutional_affilation.auth
)
assert res.status_code == 200

# Reload preprint and check that the institution has been removed
preprint.reload()
assert institution not in preprint.affiliated_institutions.all()

# Verify that the correct log entry has been created
log = preprint.logs.latest()
assert log.action == 'affiliated_institution_removed'
assert log.params['institution'] == {
'id': institution._id,
'name': institution.name
}


@pytest.mark.django_db
class TestPreprintUpdateSubjects(UpdateSubjectsMixin):
Expand Down
Loading

0 comments on commit 6d94922

Please sign in to comment.