From cb4d4ad9e7ac04a3e9555be4120d7f87fb9a5342 Mon Sep 17 00:00:00 2001 From: Ihor Sokhan Date: Tue, 26 Aug 2025 16:31:04 +0300 Subject: [PATCH 01/10] added edit contributors button and basic table --- admin/nodes/views.py | 2 + admin/preprints/views.py | 2 + admin/templates/nodes/contributors.html | 1 + admin/templates/nodes/edit_contributors.html | 42 ++++++++++++++++++++ admin/templates/preprints/contributors.html | 1 + 5 files changed, 48 insertions(+) create mode 100644 admin/templates/nodes/edit_contributors.html diff --git a/admin/nodes/views.py b/admin/nodes/views.py index db0f0119f18..de16ca5176b 100644 --- a/admin/nodes/views.py +++ b/admin/nodes/views.py @@ -109,6 +109,8 @@ def get_context_data(self, **kwargs): 'SPAM_STATUS': SpamStatus, 'STORAGE_LIMITS': settings.StorageLimits, 'node': node, + # to edit contributors we should have guid as django prohibits _id usage as it starts with an underscore + 'annotated_contributors': node.contributors.annotate(guid=F('guids___id')), 'children': children, }) diff --git a/admin/preprints/views.py b/admin/preprints/views.py index ef7d1860e76..056a50be0ff 100644 --- a/admin/preprints/views.py +++ b/admin/preprints/views.py @@ -75,6 +75,8 @@ def get_context_data(self, **kwargs): preprint = self.get_object() return super().get_context_data(**{ 'preprint': preprint, + # to edit contributors we should have guid as django prohibits _id usage as it starts with an underscore + 'annotated_contributors': preprint.contributors.annotate(guid=F('guids___id')), 'SPAM_STATUS': SpamStatus, 'change_provider_form': ChangeProviderForm(instance=preprint), 'change_machine_state_form': MachineStateForm(instance=preprint), diff --git a/admin/templates/nodes/contributors.html b/admin/templates/nodes/contributors.html index 945e0bf7578..4e5c14f0a54 100644 --- a/admin/templates/nodes/contributors.html +++ b/admin/templates/nodes/contributors.html @@ -57,6 +57,7 @@

Removing contributor: {{ user.username }}

{% endfor %} + {% include 'nodes/edit_contributors.html' with contributors=annotated_contributors %} \ No newline at end of file diff --git a/admin/templates/nodes/edit_contributors.html b/admin/templates/nodes/edit_contributors.html new file mode 100644 index 00000000000..645617648ce --- /dev/null +++ b/admin/templates/nodes/edit_contributors.html @@ -0,0 +1,42 @@ + + Edit Contributors + + diff --git a/admin/templates/preprints/contributors.html b/admin/templates/preprints/contributors.html index d225994f388..1731d62b7ff 100644 --- a/admin/templates/preprints/contributors.html +++ b/admin/templates/preprints/contributors.html @@ -53,5 +53,6 @@

Removing contributor: {{ user.username }}

{% endfor %} + {% include 'nodes/edit_contributors.html' with contributors=annotated_contributors %} \ No newline at end of file From bf991d8d7313b2c39c4341dfc107d2354a258b84 Mon Sep 17 00:00:00 2001 From: Ihor Sokhan Date: Fri, 29 Aug 2025 15:26:23 +0300 Subject: [PATCH 02/10] added edit pop-up data display --- admin/nodes/views.py | 5 +++-- admin/preprints/views.py | 4 +++- admin/templates/nodes/contributors.html | 2 +- admin/templates/nodes/edit_contributors.html | 18 ++++++++++++++---- admin/templates/preprints/contributors.html | 2 +- 5 files changed, 22 insertions(+), 9 deletions(-) diff --git a/admin/nodes/views.py b/admin/nodes/views.py index de16ca5176b..70da5be21f2 100644 --- a/admin/nodes/views.py +++ b/admin/nodes/views.py @@ -47,7 +47,7 @@ REINDEX_SHARE, REINDEX_ELASTIC, ) -from osf.utils.permissions import ADMIN +from osf.utils.permissions import ADMIN, API_CONTRIBUTOR_PERMISSIONS from scripts.approve_registrations import approve_past_pendings @@ -110,8 +110,9 @@ def get_context_data(self, **kwargs): 'STORAGE_LIMITS': settings.StorageLimits, 'node': node, # to edit contributors we should have guid as django prohibits _id usage as it starts with an underscore - 'annotated_contributors': node.contributors.annotate(guid=F('guids___id')), + 'annotated_contributors': node.contributor_set.prefetch_related('user__guids').annotate(guid=F('user__guids___id')), 'children': children, + 'permissions': API_CONTRIBUTOR_PERMISSIONS, }) return context diff --git a/admin/preprints/views.py b/admin/preprints/views.py index 056a50be0ff..1fc872ac8d9 100644 --- a/admin/preprints/views.py +++ b/admin/preprints/views.py @@ -48,6 +48,7 @@ UNFLAG_SPAM, ) from osf.utils.workflows import DefaultStates +from osf.utils.permissions import API_CONTRIBUTOR_PERMISSIONS from website import search from website.files.utils import copy_files from website.preprints.tasks import on_preprint_updated @@ -76,10 +77,11 @@ def get_context_data(self, **kwargs): return super().get_context_data(**{ 'preprint': preprint, # to edit contributors we should have guid as django prohibits _id usage as it starts with an underscore - 'annotated_contributors': preprint.contributors.annotate(guid=F('guids___id')), + 'annotated_contributors': preprint.preprintcontributor_set.prefetch_related('user__guids').annotate(guid=F('user__guids___id')), 'SPAM_STATUS': SpamStatus, 'change_provider_form': ChangeProviderForm(instance=preprint), 'change_machine_state_form': MachineStateForm(instance=preprint), + 'permissions': API_CONTRIBUTOR_PERMISSIONS, }, **kwargs) diff --git a/admin/templates/nodes/contributors.html b/admin/templates/nodes/contributors.html index 4e5c14f0a54..139f0e9885f 100644 --- a/admin/templates/nodes/contributors.html +++ b/admin/templates/nodes/contributors.html @@ -57,7 +57,7 @@

Removing contributor: {{ user.username }}

{% endfor %} - {% include 'nodes/edit_contributors.html' with contributors=annotated_contributors %} + {% include 'nodes/edit_contributors.html' with contributors=annotated_contributors resource=node %} \ No newline at end of file diff --git a/admin/templates/nodes/edit_contributors.html b/admin/templates/nodes/edit_contributors.html index 645617648ce..b1be519078b 100644 --- a/admin/templates/nodes/edit_contributors.html +++ b/admin/templates/nodes/edit_contributors.html @@ -23,10 +23,20 @@

Edit Contributors

{% for contributor in contributors %} - {{ contributor.email }} - {{ contributor.fullname }} - {{ contributor.email }} - {{ contributor.email }} + {{ contributor.user.email }} + {{ contributor.user.fullname }} + + + + Remove {% endfor %} diff --git a/admin/templates/preprints/contributors.html b/admin/templates/preprints/contributors.html index 1731d62b7ff..8590fe78739 100644 --- a/admin/templates/preprints/contributors.html +++ b/admin/templates/preprints/contributors.html @@ -53,6 +53,6 @@

Removing contributor: {{ user.username }}

{% endfor %} - {% include 'nodes/edit_contributors.html' with contributors=annotated_contributors %} + {% include 'nodes/edit_contributors.html' with contributors=annotated_contributors resource=preprint %} \ No newline at end of file From 192ad4b7b3bde6bf25b699036ac4c30d6938bd78 Mon Sep 17 00:00:00 2001 From: Ihor Sokhan Date: Fri, 29 Aug 2025 16:23:08 +0300 Subject: [PATCH 03/10] moved remove functionality to a new edit contributors pop-up --- admin/templates/nodes/contributors.html | 35 +-------- admin/templates/nodes/edit_contributors.html | 31 +++++++- admin/templates/preprints/contributors.html | 32 +------- .../preprints/edit_contributors.html | 75 +++++++++++++++++++ 4 files changed, 106 insertions(+), 67 deletions(-) create mode 100644 admin/templates/preprints/edit_contributors.html diff --git a/admin/templates/nodes/contributors.html b/admin/templates/nodes/contributors.html index 139f0e9885f..fbc4031c633 100644 --- a/admin/templates/nodes/contributors.html +++ b/admin/templates/nodes/contributors.html @@ -9,11 +9,7 @@ Email Name - Permissions - Actions - {% if perms.osf.change_node %} - - {% endif %} + Permission @@ -26,38 +22,11 @@ {{ user.fullname }} {% get_permissions user node %} - {% if perms.osf.change_node %} - - Remove - - - {% endif %} {% endfor %} - {% include 'nodes/edit_contributors.html' with contributors=annotated_contributors resource=node %} + {% include 'nodes/edit_contributors.html' with contributors=annotated_contributors %} \ No newline at end of file diff --git a/admin/templates/nodes/edit_contributors.html b/admin/templates/nodes/edit_contributors.html index b1be519078b..e18be77bbaa 100644 --- a/admin/templates/nodes/edit_contributors.html +++ b/admin/templates/nodes/edit_contributors.html @@ -14,9 +14,6 @@

Edit Contributors

Email Name Permission - - @@ -36,7 +33,33 @@

Edit Contributors

{% endfor %} - Remove + {% if perms.osf.change_node %} + + Remove + + + {% endif %} {% endfor %} diff --git a/admin/templates/preprints/contributors.html b/admin/templates/preprints/contributors.html index 8590fe78739..2e2a0a0c0e6 100644 --- a/admin/templates/preprints/contributors.html +++ b/admin/templates/preprints/contributors.html @@ -8,8 +8,7 @@ Email Name - Permissions - Actions + Permission @@ -22,37 +21,10 @@ {{ user.fullname }} {% get_permissions user preprint %} - {% if perms.osf.change_preprint %} - - Remove - - - {% endif %} {% endfor %} - {% include 'nodes/edit_contributors.html' with contributors=annotated_contributors resource=preprint %} + {% include 'preprints/edit_contributors.html' with contributors=annotated_contributors %} \ No newline at end of file diff --git a/admin/templates/preprints/edit_contributors.html b/admin/templates/preprints/edit_contributors.html new file mode 100644 index 00000000000..b93253bf6e5 --- /dev/null +++ b/admin/templates/preprints/edit_contributors.html @@ -0,0 +1,75 @@ + + Edit Contributors + + From 1e153f7c4be7dc0c9828b58c6ca421b89728a238 Mon Sep 17 00:00:00 2001 From: Ihor Sokhan Date: Mon, 1 Sep 2025 17:05:12 +0300 Subject: [PATCH 04/10] added contributors removal and permissions update --- admin/nodes/urls.py | 1 + admin/nodes/views.py | 41 ++++++++ admin/preprints/urls.py | 1 + admin/preprints/views.py | 8 +- admin/templates/nodes/edit_contributors.html | 96 +++++++++---------- .../preprints/edit_contributors.html | 96 +++++++++---------- 6 files changed, 146 insertions(+), 97 deletions(-) diff --git a/admin/nodes/urls.py b/admin/nodes/urls.py index 1d5f6e0bac9..ab96aa49fc2 100644 --- a/admin/nodes/urls.py +++ b/admin/nodes/urls.py @@ -46,4 +46,5 @@ re_path(r'^(?P[a-z0-9]+)/update_moderation_state/$', views.NodeUpdateModerationStateView.as_view(), name='node-update-mod-state'), re_path(r'^(?P[a-z0-9]+)/resync_datacite/$', views.NodeResyncDataCiteView.as_view(), name='resync-datacite'), re_path(r'^(?P[a-z0-9]+)/revert/$', views.NodeRevertToDraft.as_view(), name='revert-to-draft'), + re_path(r'^(?P[a-z0-9]+)/update_permissions/$', views.NodeUpdatePermissionsView.as_view(), name='update-permissions'), ] diff --git a/admin/nodes/views.py b/admin/nodes/views.py index 70da5be21f2..d93efeb23ae 100644 --- a/admin/nodes/views.py +++ b/admin/nodes/views.py @@ -198,6 +198,47 @@ def add_contributor_removed_log(self, node, user): ).save() +class NodeUpdatePermissionsView(NodeMixin, View): + permission_required = ('osf.view_node', 'osf.change_node') + raise_exception = True + redirect_view = NodeRemoveContributorView + + def post(self, request, *args, **kwargs): + data = dict(request.POST) + permissions = data['permissions'] + user_id_to_remove = data.get('remove-user') + resource = self.get_object() + if user_id_to_remove: + user_id = user_id_to_remove[0] + # html renders form into form incorrectly, + # so this view handles contributors deletion and permissions update + return self.redirect_view(request=request, kwargs={'guid': resource.guid, 'user_id': user_id}).post(request, user_id=user_id) + + has_admin = False + for contributor_permission in permissions: + guid, permission = contributor_permission.split('-') + if permission == ADMIN: + has_admin = True + break + + if not has_admin: + messages.error(self.request, 'Must be at least one admin on this node.') + return redirect(self.get_success_url()) + + for contributor_permission in permissions: + guid, permission = contributor_permission.split('-') + user = OSFUser.load(guid) + resource.update_contributor( + user, + permission, + resource.get_visible(user), + request, + save=True + ) + + return redirect(self.get_success_url()) + + class NodeDeleteView(NodeMixin, View): """ Allows authorized users to mark nodes as deleted. """ diff --git a/admin/preprints/urls.py b/admin/preprints/urls.py index 4ab9bd33939..4d9fdb0306d 100644 --- a/admin/preprints/urls.py +++ b/admin/preprints/urls.py @@ -29,4 +29,5 @@ re_path(r'^(?P\w+)/resync_crossref/$', views.PreprintResyncCrossRefView.as_view(), name='resync-crossref'), re_path(r'^(?P\w+)/make_published/$', views.PreprintMakePublishedView.as_view(), name='make-published'), re_path(r'^(?P\w+)/unwithdraw/$', views.PreprintUnwithdrawView.as_view(), name='unwithdraw'), + re_path(r'^(?P\w+)/update_permissions/$', views.PreprintUpdatePermissionsView.as_view(), name='update-permissions'), ] diff --git a/admin/preprints/views.py b/admin/preprints/views.py index 1fc872ac8d9..a981af8eedc 100644 --- a/admin/preprints/views.py +++ b/admin/preprints/views.py @@ -15,7 +15,7 @@ from admin.base.views import GuidView from admin.base.forms import GuidForm -from admin.nodes.views import NodeRemoveContributorView +from admin.nodes.views import NodeRemoveContributorView, NodeUpdatePermissionsView from admin.preprints.forms import ChangeProviderForm, MachineStateForm from api.share.utils import update_share @@ -276,6 +276,12 @@ def add_contributor_removed_log(self, preprint, user): ).save() +class PreprintUpdatePermissionsView(PreprintMixin, NodeUpdatePermissionsView): + permission_required = ('osf.view_node', 'osf.change_node') + raise_exception = True + redirect_view = PreprintRemoveContributorView + + class PreprintDeleteView(PreprintMixin, View): """ Allows authorized users to mark preprints as deleted. """ diff --git a/admin/templates/nodes/edit_contributors.html b/admin/templates/nodes/edit_contributors.html index e18be77bbaa..664885c946e 100644 --- a/admin/templates/nodes/edit_contributors.html +++ b/admin/templates/nodes/edit_contributors.html @@ -8,68 +8,68 @@

Edit Contributors

- - - - - - - - - - - {% for contributor in contributors %} + + {% csrf_token %} +
EmailNamePermission
+ - - - - {% if perms.osf.change_node %} - + + + + + + + {% for contributor in contributors %} + + + + + {% if perms.osf.change_node %} + - {% endif %} - - {% endfor %} - -
{{ contributor.user.email }}{{ contributor.user.fullname }} - - - Remove - EmailNamePermission
{{ contributor.user.email }}{{ contributor.user.fullname }} + + + Remove + - -
- + + {% endif %} + + {% endfor %} + + + + diff --git a/admin/templates/preprints/edit_contributors.html b/admin/templates/preprints/edit_contributors.html index b93253bf6e5..d3f19f86e61 100644 --- a/admin/templates/preprints/edit_contributors.html +++ b/admin/templates/preprints/edit_contributors.html @@ -8,68 +8,68 @@

Edit Contributors

- - - - - - - - - - - {% for contributor in contributors %} + + {% csrf_token %} +
EmailNamePermission
+ - - - - {% if perms.osf.change_preprint %} - + + + + + + + {% for contributor in contributors %} + + + + + {% if perms.osf.change_preprint %} + - {% endif %} - - {% endfor %} - -
{{ contributor.user.email }}{{ contributor.user.fullname }} - - - Remove - EmailNamePermission
{{ contributor.user.email }}{{ contributor.user.fullname }} + + + Remove + - -
- + + {% endif %} + + {% endfor %} + + + + From d3165bcdd9fabded6387101c3b683e26065fc46b Mon Sep 17 00:00:00 2001 From: Ihor Sokhan Date: Wed, 3 Sep 2025 11:12:51 +0300 Subject: [PATCH 05/10] added an ability to add a new contributor + html improvements --- admin/nodes/views.py | 35 ++++++-- admin/preprints/views.py | 3 +- admin/templates/nodes/contributors.html | 2 +- admin/templates/nodes/edit_contributors.html | 83 ++++++++++++++++--- admin/templates/preprints/contributors.html | 2 +- .../preprints/edit_contributors.html | 75 ----------------- 6 files changed, 106 insertions(+), 94 deletions(-) delete mode 100644 admin/templates/preprints/edit_contributors.html diff --git a/admin/nodes/views.py b/admin/nodes/views.py index d93efeb23ae..2cc2398447e 100644 --- a/admin/nodes/views.py +++ b/admin/nodes/views.py @@ -93,7 +93,7 @@ class NodeView(NodeMixin, GuidView): """ Allows authorized users to view node info. """ template_name = 'nodes/node.html' - permission_required = 'osf.view_node' + permission_required = ('osf.view_preprint', 'osf.change_preprint',) raise_exception = True def get_context_data(self, **kwargs): @@ -113,6 +113,7 @@ def get_context_data(self, **kwargs): 'annotated_contributors': node.contributor_set.prefetch_related('user__guids').annotate(guid=F('user__guids___id')), 'children': children, 'permissions': API_CONTRIBUTOR_PERMISSIONS, + 'has_update_permission': node.is_admin_contributor(self.request.user) }) return context @@ -205,16 +206,38 @@ class NodeUpdatePermissionsView(NodeMixin, View): def post(self, request, *args, **kwargs): data = dict(request.POST) - permissions = data['permissions'] - user_id_to_remove = data.get('remove-user') + contributor_id_to_remove = data.get('remove-user') resource = self.get_object() - if user_id_to_remove: - user_id = user_id_to_remove[0] + if contributor_id_to_remove: + contributor_id = contributor_id_to_remove[0] # html renders form into form incorrectly, # so this view handles contributors deletion and permissions update - return self.redirect_view(request=request, kwargs={'guid': resource.guid, 'user_id': user_id}).post(request, user_id=user_id) + return self.redirect_view( + request=request, + kwargs={'guid': resource.guid, 'user_id': contributor_id} + ).post(request, user_id=contributor_id) + + contributor_email_to_add = data.get('new_email') + if contributor_email_to_add: + email = contributor_email_to_add[0] + contributor_user = OSFUser.objects.filter(emails__address=email.lower()).first() + if not contributor_user: + messages.error(self.request, f'Email {email} is not registered in OSF.') + return redirect(self.get_success_url()) + if resource.is_contributor(contributor_user): + messages.error(self.request, f'User with email {email} is already a contributor') + return redirect(self.get_success_url()) + + resource.add_contributor_registered_or_not( + auth=request, + user_id=contributor_user._id, + permissions=data['new-permissions'][0], + save=True + ) + return redirect(self.get_success_url()) has_admin = False + permissions = data['permissions'] for contributor_permission in permissions: guid, permission = contributor_permission.split('-') if permission == ADMIN: diff --git a/admin/preprints/views.py b/admin/preprints/views.py index a981af8eedc..e4262e751c7 100644 --- a/admin/preprints/views.py +++ b/admin/preprints/views.py @@ -77,11 +77,12 @@ def get_context_data(self, **kwargs): return super().get_context_data(**{ 'preprint': preprint, # to edit contributors we should have guid as django prohibits _id usage as it starts with an underscore - 'annotated_contributors': preprint.preprintcontributor_set.prefetch_related('user__guids').annotate(guid=F('user__guids___id')), + 'annotated_contributors': preprint.contributor_set.prefetch_related('user__guids').annotate(guid=F('user__guids___id')), 'SPAM_STATUS': SpamStatus, 'change_provider_form': ChangeProviderForm(instance=preprint), 'change_machine_state_form': MachineStateForm(instance=preprint), 'permissions': API_CONTRIBUTOR_PERMISSIONS, + 'has_update_permission': preprint.is_admin_contributor(self.request.user) }, **kwargs) diff --git a/admin/templates/nodes/contributors.html b/admin/templates/nodes/contributors.html index fbc4031c633..48a30fe98a2 100644 --- a/admin/templates/nodes/contributors.html +++ b/admin/templates/nodes/contributors.html @@ -26,7 +26,7 @@ {% endfor %} - {% include 'nodes/edit_contributors.html' with contributors=annotated_contributors %} + {% include 'nodes/edit_contributors.html' with contributors=annotated_contributors resource=node %} \ No newline at end of file diff --git a/admin/templates/nodes/edit_contributors.html b/admin/templates/nodes/edit_contributors.html index 664885c946e..61bde68935e 100644 --- a/admin/templates/nodes/edit_contributors.html +++ b/admin/templates/nodes/edit_contributors.html @@ -5,12 +5,16 @@ + + \ No newline at end of file diff --git a/admin/templates/preprints/contributors.html b/admin/templates/preprints/contributors.html index 2e2a0a0c0e6..a3d8697a0c6 100644 --- a/admin/templates/preprints/contributors.html +++ b/admin/templates/preprints/contributors.html @@ -25,6 +25,6 @@ {% endfor %} - {% include 'preprints/edit_contributors.html' with contributors=annotated_contributors %} + {% include 'nodes/edit_contributors.html' with contributors=annotated_contributors resource=preprint %} \ No newline at end of file diff --git a/admin/templates/preprints/edit_contributors.html b/admin/templates/preprints/edit_contributors.html deleted file mode 100644 index d3f19f86e61..00000000000 --- a/admin/templates/preprints/edit_contributors.html +++ /dev/null @@ -1,75 +0,0 @@ - - Edit Contributors - - From 4e504f8013509c487389cfe8351c421cd67a5c6f Mon Sep 17 00:00:00 2001 From: Ihor Sokhan Date: Wed, 3 Sep 2025 13:17:15 +0300 Subject: [PATCH 06/10] removed redundant code --- admin/templates/nodes/edit_contributors.html | 2 -- 1 file changed, 2 deletions(-) diff --git a/admin/templates/nodes/edit_contributors.html b/admin/templates/nodes/edit_contributors.html index 61bde68935e..2d2cc5d8e99 100644 --- a/admin/templates/nodes/edit_contributors.html +++ b/admin/templates/nodes/edit_contributors.html @@ -109,13 +109,11 @@

Removing contributor: {{ contributor.user.username }}

cell4.innerHTML = '' cell4.style = 'text-align: center;' - // Append cells to the row newRow.appendChild(cell1); newRow.appendChild(cell2); newRow.appendChild(cell3); newRow.appendChild(cell4) - // Append the row to the table body tableBody.appendChild(newRow); const addNewContributor = document.getElementById("add-contributor") From f6d96eb2a43beec59bb58a246c7b63ddc2e05c6a Mon Sep 17 00:00:00 2001 From: Ihor Sokhan Date: Wed, 3 Sep 2025 13:43:47 +0300 Subject: [PATCH 07/10] fixed permissions --- admin/nodes/views.py | 2 +- admin/preprints/views.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/admin/nodes/views.py b/admin/nodes/views.py index 2cc2398447e..70776230ee7 100644 --- a/admin/nodes/views.py +++ b/admin/nodes/views.py @@ -93,7 +93,7 @@ class NodeView(NodeMixin, GuidView): """ Allows authorized users to view node info. """ template_name = 'nodes/node.html' - permission_required = ('osf.view_preprint', 'osf.change_preprint',) + permission_required = 'osf.view_node' raise_exception = True def get_context_data(self, **kwargs): diff --git a/admin/preprints/views.py b/admin/preprints/views.py index e4262e751c7..3eedceaf9e6 100644 --- a/admin/preprints/views.py +++ b/admin/preprints/views.py @@ -278,7 +278,7 @@ def add_contributor_removed_log(self, preprint, user): class PreprintUpdatePermissionsView(PreprintMixin, NodeUpdatePermissionsView): - permission_required = ('osf.view_node', 'osf.change_node') + permission_required = ('osf.view_preprint', 'osf.change_preprint') raise_exception = True redirect_view = PreprintRemoveContributorView From 0c1f8759c380de7259e216ce762eae1302e74831 Mon Sep 17 00:00:00 2001 From: Ihor Sokhan Date: Wed, 3 Sep 2025 14:34:11 +0300 Subject: [PATCH 08/10] fixed test --- admin_tests/nodes/test_views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/admin_tests/nodes/test_views.py b/admin_tests/nodes/test_views.py index 9607a1a0c99..71ee77c4c03 100644 --- a/admin_tests/nodes/test_views.py +++ b/admin_tests/nodes/test_views.py @@ -103,6 +103,7 @@ def test_name_data(self): node = ProjectFactory() guid = node._id request = RequestFactory().get('/fake_path') + request.user = AuthUserFactory() view = NodeView() view = setup_view(view, request, guid=guid) temp_object = view.get_object() From 1aae4774b929be69302400ee65af786aebe4fdc4 Mon Sep 17 00:00:00 2001 From: Ihor Sokhan Date: Thu, 4 Sep 2025 11:53:26 +0300 Subject: [PATCH 09/10] added an ability to add multiple contributors + handle some edge cases --- admin/nodes/views.py | 40 +++++++++++--------- admin/templates/nodes/edit_contributors.html | 39 +++++++++---------- 2 files changed, 43 insertions(+), 36 deletions(-) diff --git a/admin/nodes/views.py b/admin/nodes/views.py index 70776230ee7..b9f050bee85 100644 --- a/admin/nodes/views.py +++ b/admin/nodes/views.py @@ -208,6 +208,7 @@ def post(self, request, *args, **kwargs): data = dict(request.POST) contributor_id_to_remove = data.get('remove-user') resource = self.get_object() + if contributor_id_to_remove: contributor_id = contributor_id_to_remove[0] # html renders form into form incorrectly, @@ -217,38 +218,43 @@ def post(self, request, *args, **kwargs): kwargs={'guid': resource.guid, 'user_id': contributor_id} ).post(request, user_id=contributor_id) - contributor_email_to_add = data.get('new_email') - if contributor_email_to_add: - email = contributor_email_to_add[0] + new_emails_to_add = data.get('new-emails', []) + new_permissions_to_add = data.get('new-permissions', []) + + new_permission_indexes_to_remove = [] + for email, permission in zip(new_emails_to_add, new_permissions_to_add): contributor_user = OSFUser.objects.filter(emails__address=email.lower()).first() if not contributor_user: + new_permission_indexes_to_remove.append(new_emails_to_add.index(email)) messages.error(self.request, f'Email {email} is not registered in OSF.') - return redirect(self.get_success_url()) - if resource.is_contributor(contributor_user): - messages.error(self.request, f'User with email {email} is already a contributor') - return redirect(self.get_success_url()) + continue + elif resource.is_contributor(contributor_user): + new_permission_indexes_to_remove.append(new_emails_to_add.index(email)) + messages.error(self.request, f'User with email {email} is already a contributor.') + continue resource.add_contributor_registered_or_not( auth=request, user_id=contributor_user._id, - permissions=data['new-permissions'][0], + permissions=permission, save=True ) - return redirect(self.get_success_url()) + messages.success(self.request, f'User with email {email} was successfully added.') - has_admin = False - permissions = data['permissions'] - for contributor_permission in permissions: - guid, permission = contributor_permission.split('-') - if permission == ADMIN: - has_admin = True - break + # should remove permissions of invalid emails because + # admin can make all existing contributors non admins + # and enter an invalid email with the only admin permission + for permission_index in new_permission_indexes_to_remove: + new_permissions_to_add.pop(permission_index) + updated_permissions = data.get('updated-permissions', []) + all_permissions = updated_permissions + new_permissions_to_add + has_admin = bool(list(filter(lambda permission: ADMIN in permission, all_permissions))) if not has_admin: messages.error(self.request, 'Must be at least one admin on this node.') return redirect(self.get_success_url()) - for contributor_permission in permissions: + for contributor_permission in updated_permissions: guid, permission = contributor_permission.split('-') user = OSFUser.load(guid) resource.update_contributor( diff --git a/admin/templates/nodes/edit_contributors.html b/admin/templates/nodes/edit_contributors.html index 2d2cc5d8e99..f976e65aed9 100644 --- a/admin/templates/nodes/edit_contributors.html +++ b/admin/templates/nodes/edit_contributors.html @@ -29,7 +29,7 @@

Edit Contributors

{{ contributor.user.email }} {{ contributor.user.fullname }} - {% for permission in permissions %} {% if contributor.permission == permission %} @@ -53,8 +53,8 @@

Removing contributor: {{ contributor.user.username }}

User will be removed. Currently only an admin on this node type will be able to add them back. @@ -69,13 +69,13 @@

Removing contributor: {{ contributor.user.username }}

@@ -84,16 +84,19 @@

Removing contributor: {{ contributor.user.username }}

\ No newline at end of file From 2f8ae0927c48d3a02774990f1c67dffe17b01309 Mon Sep 17 00:00:00 2001 From: Ihor Sokhan Date: Fri, 5 Sep 2025 17:12:05 +0300 Subject: [PATCH 10/10] removed redundant conversion --- admin/nodes/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/nodes/views.py b/admin/nodes/views.py index b9f050bee85..9dfc147e47d 100644 --- a/admin/nodes/views.py +++ b/admin/nodes/views.py @@ -249,7 +249,7 @@ def post(self, request, *args, **kwargs): updated_permissions = data.get('updated-permissions', []) all_permissions = updated_permissions + new_permissions_to_add - has_admin = bool(list(filter(lambda permission: ADMIN in permission, all_permissions))) + has_admin = list(filter(lambda permission: ADMIN in permission, all_permissions)) if not has_admin: messages.error(self.request, 'Must be at least one admin on this node.') return redirect(self.get_success_url())