diff --git a/api/users/urls.py b/api/users/urls.py index 8e273368e44..73fb5c045e3 100644 --- a/api/users/urls.py +++ b/api/users/urls.py @@ -16,6 +16,7 @@ re_path(r'^(?P\w+)/nodes/$', views.UserNodes.as_view(), name=views.UserNodes.view_name), re_path(r'^(?P\w+)/groups/$', views.UserGroups.as_view(), name=views.UserGroups.view_name), re_path(r'^(?P\w+)/preprints/$', views.UserPreprints.as_view(), name=views.UserPreprints.view_name), + re_path(r'^(?P\w+)/draft_preprints/$', views.UserPreprints.as_view(), name=views.UserPreprints.view_name), re_path(r'^(?P\w+)/registrations/$', views.UserRegistrations.as_view(), name=views.UserRegistrations.view_name), re_path(r'^(?P\w+)/settings/$', views.UserSettings.as_view(), name=views.UserSettings.view_name), re_path(r'^(?P\w+)/quickfiles/$', views.UserQuickFiles.as_view(), name=views.UserQuickFiles.view_name), diff --git a/api/users/views.py b/api/users/views.py index 325619d517d..b27bb5d48d6 100644 --- a/api/users/views.py +++ b/api/users/views.py @@ -413,6 +413,46 @@ def get_queryset(self): return self.get_queryset_from_request() +class UserDraftPreprints(JSONAPIBaseView, generics.ListAPIView, UserMixin, PreprintFilterMixin): + """The documentation for this endpoint can be found [here](https://developer.osf.io/). + """ + + permission_classes = ( + drf_permissions.IsAuthenticatedOrReadOnly, + base_permissions.TokenHasScope, + ) + + ordering = ('-created') + model_class = AbstractNode + + required_read_scopes = [CoreScopes.USERS_READ, CoreScopes.NODE_PREPRINTS_READ] + required_write_scopes = [CoreScopes.USERS_WRITE, CoreScopes.NODE_PREPRINTS_WRITE] + + serializer_class = PreprintSerializer + view_category = 'users' + view_name = 'user-preprints' + + def get_default_queryset(self): + # the user who is requesting + auth = get_user_auth(self.request) + auth_user = getattr(auth, 'user', None) + + # the user data being requested + target_user = self.get_user(check_permissions=False) + + return self.preprints_queryset( + Preprint.objects.filter( + _contributors__guids___id=target_user._id, + machine_state='initial' + ), + auth_user, + allow_contribs=False + ) + + def get_queryset(self): + return self.get_queryset_from_request() + + class UserInstitutions(JSONAPIBaseView, generics.ListAPIView, UserMixin): """The documentation for this endpoint can be found [here](https://developer.osf.io/#operation/users_institutions_list). """ diff --git a/api_tests/users/views/test_user_draft_preprint.py b/api_tests/users/views/test_user_draft_preprint.py new file mode 100644 index 00000000000..5e29bd5fcd0 --- /dev/null +++ b/api_tests/users/views/test_user_draft_preprint.py @@ -0,0 +1,153 @@ +import pytest +from osf.utils.permissions import WRITE +from osf_tests.factories import ( + PreprintFactory, + AuthUserFactory, + ProjectFactory, + SubjectFactory, + PreprintProviderFactory, +) +from api.base.settings.defaults import API_BASE + + +@pytest.mark.django_db +class TestPreprintDraftList: + + @pytest.fixture() + def admin(self): + return AuthUserFactory() + + @pytest.fixture() + def write_contrib(self): + return AuthUserFactory() + + @pytest.fixture() + def non_contrib(self): + return AuthUserFactory() + + @pytest.fixture() + def public_project(self, admin): + return ProjectFactory(creator=admin, is_public=True) + + @pytest.fixture() + def private_project(self, admin): + return ProjectFactory(creator=admin, is_public=False) + + @pytest.fixture() + def subject(self): + return SubjectFactory() + + @pytest.fixture() + def provider(self): + return PreprintProviderFactory() + + @pytest.fixture() + def unpublished_preprint(self, admin, provider, subject, public_project): + return PreprintFactory( + creator=admin, + filename='toe_socks_and_sunrises.pdf', + provider=provider, + subjects=[[subject._id]], + is_published=False, + machine_state='initial' + ) + + @pytest.fixture() + def private_preprint(self, admin, provider, subject, private_project, write_contrib): + preprint = PreprintFactory( + creator=admin, + filename='toe_socks_and_sunrises.pdf', + provider=provider, + subjects=[[subject._id]], + is_published=True, + is_public=False, + machine_state='accepted' + ) + preprint.add_contributor(write_contrib, permissions=WRITE) + preprint.is_public = False + preprint.save() + return preprint + + @pytest.fixture() + def published_preprint(self, admin, provider, subject, write_contrib): + preprint = PreprintFactory( + creator=admin, + filename='toe_socks_and_sunrises.pdf', + provider=provider, + subjects=[[subject._id]], + is_published=True, + is_public=True, + machine_state='accepted' + ) + preprint.add_contributor(write_contrib, permissions=WRITE) + return preprint + + @pytest.fixture() + def abandoned_private_preprint(self, admin, provider, subject, private_project): + return PreprintFactory( + creator=admin, + filename='toe_socks_and_sunrises.pdf', + provider=provider, + subjects=[[subject._id]], + project=private_project, + is_published=False, + is_public=False, + machine_state='initial' + ) + + @pytest.fixture() + def abandoned_public_preprint(self, admin, provider, subject, public_project): + return PreprintFactory( + creator=admin, + filename='toe_socks_and_sunrises.pdf', + provider=provider, + subjects=[[subject._id]], + project=public_project, + is_published=False, + is_public=True, + machine_state='initial' + ) + + def test_authorized_in_gets_200(self, app, admin, preprint): + url = f'/{API_BASE}users/{admin._id}/draft_preprints/' + res = app.get(url, auth=admin.auth) + assert res.status_code == 200 + + def test_anonymous_gets_401(self, app, admin): + url = f'/{API_BASE}users/{admin._id}/draft_preprints/' + res = app.get(url) + assert res.status_code == 401 + + def test_get_preprints_403(self, app, admin, non_contrib, preprint): + url = f'/{API_BASE}users/{admin._id}/draft_preprints/' + res = app.get(url, auth=non_contrib.auth) + assert res.status_code == 401 + + def test_get_projects_not_logged_in(self, app, preprint, admin, project_public, project_private): + res = app.get(f'/{API_BASE}users/{admin._id}/draft_preprints/') + node_json = res.json['data'] + + ids = [each['id'] for each in node_json] + assert preprint._id in ids + assert project_public._id not in ids + assert project_private._id not in ids + + def test_get_projects_logged_in_as_different_user(self, app, admin, write_contrib, preprint, project_public, project_private): + res = app.get( + f'/{API_BASE}users/{admin._id}/draft_preprints/', + auth=write_contrib.auth + ) + node_json = res.json['data'] + + ids = [each['id'] for each in node_json] + assert preprint._id in ids + assert project_public._id not in ids + assert project_private._id not in ids + + def test_abandoned_preprint_in_results(self, app, admin, abandoned_public_preprint, abandoned_private_preprint, published_preprint): + res = app.get( + f'/{API_BASE}users/{admin._id}/draft_preprints/', + auth=admin.auth + ) + actual = [result['id'] for result in res.json['data']] + assert abandoned_public_preprint._id in actual