Skip to content

Commit 03d7dd1

Browse files
committed
Merge branch 'feature/pbs-25-08' of https://github.com/CenterForOpenScience/osf.io into impact-notifications
* 'feature/pbs-25-08' of https://github.com/CenterForOpenScience/osf.io: (28 commits) [ENG-7716] Allow for reinstatement of previous preprint versions (with date uploaded) via the admin app (#11118) [ENG-7263] Fix/eng 7263 part 3 (#11119) [ENG-7263] Part 2 (#11110) fix feature for non-contributor admin (#11111) [ENG-7716] Allow for reinstatement of previous preprint versions (with date uploaded) via the admin app (#11097) delete sharev2 push [ENG-7387] (#11032) [ENG-7503] Fix/eng 7503 (#11092) [ENG-7263] Fix/eng 7263 (#11090) [ENG-7798] Parse versioned guid (#11104) [ENG-7270] Enable Product Team to Force Archive Registrations in the Admin App (#11105) gdpr deletion shouldn't take into account deleted nodes (#11098) Bind task for proper retrying improved naming use newly built doi for previous versions mint missing doi when build metadata use minted doi for building metadata updated error text simplified query flat guids added version filtering ... # Conflicts: # api/crossref/views.py # tests/test_events.py # tests/test_misc_views.py
2 parents 0d4d9c5 + 3e83dec commit 03d7dd1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+1533
-1086
lines changed

.docker-compose.gv.env

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
ALLOWED_HOSTS="localhost,192.168.168.167"
1+
ALLOWED_HOSTS="localhost,192.168.168.167,127.0.0.1"
22
CORS_ALLOWED_ORIGINS="http://localhost:5000,http://192.168.168.167:5000"
33
OSFDB_HOST=192.168.168.167
44
POSTGRES_HOST="$OSFDB_HOST"
@@ -7,5 +7,10 @@ OSF_API_BASE_URL="http://192.168.168.167:8000"
77
POSTGRES_USER="postgres"
88
POSTGRES_DB="gravyvalet"
99
SECRET_KEY="secret"
10-
PYTHONUNBUFFERED=0 # This when set to 0 will allow print statements to be visible in the Docker logs
10+
PYTHONUNBUFFERED=1 # This when set to 0 will allow print statements to be visible in the Docker logs
11+
OSF_AUTH_COOKIE_NAME=osf
12+
OSF_SENSITIVE_DATA_SECRET="TrainglesAre5Squares"
13+
OSF_SENSITIVE_DATA_SALT="yusaltydough"
14+
DEBUG=1
15+
1116

addons/base/views.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -431,11 +431,7 @@ def _enqueue_metrics(file_version, file_node, action, auth, from_mfr=False):
431431
def _construct_payload(auth, resource, credentials, waterbutler_settings):
432432

433433
if isinstance(resource, Registration):
434-
callback_url = resource.api_url_for(
435-
'registration_callbacks',
436-
_absolute=True,
437-
_internal=True
438-
)
434+
callback_url = resource.callbacks_url
439435
else:
440436
callback_url = resource.api_url_for(
441437
'create_waterbutler_log',

admin/nodes/urls.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@
2727
re_path(r'^(?P<guid>[a-z0-9]+)/reindex_share_node/$', views.NodeReindexShare.as_view(), name='reindex-share-node'),
2828
re_path(r'^(?P<guid>[a-z0-9]+)/reindex_elastic_node/$', views.NodeReindexElastic.as_view(),
2929
name='reindex-elastic-node'),
30-
re_path(r'^(?P<guid>[a-z0-9]+)/restart_stuck_registrations/$', views.RestartStuckRegistrationsView.as_view(),
31-
name='restart-stuck-registrations'),
3230
re_path(r'^(?P<guid>[a-z0-9]+)/remove_stuck_registrations/$', views.RemoveStuckRegistrationsView.as_view(),
3331
name='remove-stuck-registrations'),
32+
re_path(r'^(?P<guid>[a-z0-9]+)/check_archive_status/$', views.CheckArchiveStatusRegistrationsView.as_view(),
33+
name='check-archive-status'),
34+
re_path(r'^(?P<guid>[a-z0-9]+)/force_archive_registration/$', views.ForceArchiveRegistrationsView.as_view(),
35+
name='force-archive-registration'),
3436
re_path(r'^(?P<guid>[a-z0-9]+)/remove_user/(?P<user_id>[a-z0-9]+)/$', views.NodeRemoveContributorView.as_view(),
3537
name='remove-user'),
3638
re_path(r'^(?P<guid>[a-z0-9]+)/modify_storage_usage/$', views.NodeModifyStorageUsage.as_view(),

admin/nodes/views.py

Lines changed: 76 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import pytz
2+
from enum import Enum
23
from datetime import datetime
34
from framework import status
45

@@ -26,7 +27,7 @@
2627
from api.share.utils import update_share
2728
from api.caching.tasks import update_storage_usage_cache
2829

29-
from osf.exceptions import NodeStateError
30+
from osf.exceptions import NodeStateError, RegistrationStuckError
3031
from osf.models import (
3132
OSFUser,
3233
NodeLog,
@@ -672,44 +673,97 @@ def post(self, request, *args, **kwargs):
672673
return redirect(self.get_success_url())
673674

674675

675-
class RestartStuckRegistrationsView(NodeMixin, TemplateView):
676-
""" Allows an authorized user to restart a registrations archive process.
676+
class RemoveStuckRegistrationsView(NodeMixin, View):
677+
""" Allows an authorized user to remove a registrations if it's stuck in the archiving process.
677678
"""
678-
template_name = 'nodes/restart_registrations_modal.html'
679679
permission_required = ('osf.view_node', 'osf.change_node')
680680

681681
def post(self, request, *args, **kwargs):
682-
# Prevents circular imports that cause admin app to hang at startup
683-
from osf.management.commands.force_archive import archive, verify
684682
stuck_reg = self.get_object()
685-
if verify(stuck_reg):
686-
try:
687-
archive(stuck_reg)
688-
messages.success(request, 'Registration archive processes has restarted')
689-
except Exception as exc:
690-
messages.error(request, f'This registration cannot be unstuck due to {exc.__class__.__name__} '
691-
f'if the problem persists get a developer to fix it.')
683+
if Registration.find_failed_registrations().filter(id=stuck_reg.id).exists():
684+
stuck_reg.delete_registration_tree(save=True)
685+
messages.success(request, 'The registration has been deleted')
692686
else:
693687
messages.error(request, 'This registration may not technically be stuck,'
694688
' if the problem persists get a developer to fix it.')
695689

696690
return redirect(self.get_success_url())
697691

698692

699-
class RemoveStuckRegistrationsView(NodeMixin, TemplateView):
700-
""" Allows an authorized user to remove a registrations if it's stuck in the archiving process.
693+
class CheckArchiveStatusRegistrationsView(NodeMixin, View):
694+
"""Allows an authorized user to check a registration archive status.
695+
"""
696+
permission_required = ('osf.view_node', 'osf.change_node')
697+
698+
def get(self, request, *args, **kwargs):
699+
# Prevents circular imports that cause admin app to hang at startup
700+
from osf.management.commands.force_archive import check
701+
702+
registration = self.get_object()
703+
704+
if registration.archived:
705+
messages.success(request, f"Registration {registration._id} is archived.")
706+
return redirect(self.get_success_url())
707+
708+
try:
709+
archive_status = check(registration)
710+
messages.success(request, archive_status)
711+
except RegistrationStuckError as exc:
712+
messages.error(request, str(exc))
713+
714+
return redirect(self.get_success_url())
715+
716+
717+
class CollisionMode(Enum):
718+
NONE: str = 'none'
719+
SKIP: str = 'skip'
720+
DELETE: str = 'delete'
721+
722+
723+
class ForceArchiveRegistrationsView(NodeMixin, View):
724+
"""Allows an authorized user to force archive registration.
701725
"""
702-
template_name = 'nodes/remove_registrations_modal.html'
703726
permission_required = ('osf.view_node', 'osf.change_node')
704727

705728
def post(self, request, *args, **kwargs):
706-
stuck_reg = self.get_object()
707-
if Registration.find_failed_registrations().filter(id=stuck_reg.id).exists():
708-
stuck_reg.delete_registration_tree(save=True)
709-
messages.success(request, 'The registration has been deleted')
729+
# Prevents circular imports that cause admin app to hang at startup
730+
from osf.management.commands.force_archive import verify, archive, DEFAULT_PERMISSIBLE_ADDONS
731+
732+
registration = self.get_object()
733+
force_archive_params = request.POST
734+
735+
collision_mode = force_archive_params.get('collision_mode', CollisionMode.NONE.value)
736+
delete_collision = CollisionMode.DELETE.value == collision_mode
737+
skip_collision = CollisionMode.SKIP.value == collision_mode
738+
739+
allow_unconfigured = force_archive_params.get('allow_unconfigured', False)
740+
741+
addons = set(force_archive_params.getlist('addons', []))
742+
addons.update(DEFAULT_PERMISSIBLE_ADDONS)
743+
744+
try:
745+
verify(registration, permissible_addons=addons, raise_error=True)
746+
except ValidationError as exc:
747+
messages.error(request, str(exc))
748+
return redirect(self.get_success_url())
749+
750+
dry_mode = force_archive_params.get('dry_mode', False)
751+
752+
if dry_mode:
753+
messages.success(request, f"Registration {registration._id} can be archived.")
710754
else:
711-
messages.error(request, 'This registration may not technically be stuck,'
712-
' if the problem persists get a developer to fix it.')
755+
try:
756+
archive(
757+
registration,
758+
permissible_addons=addons,
759+
allow_unconfigured=allow_unconfigured,
760+
skip_collision=skip_collision,
761+
delete_collision=delete_collision,
762+
)
763+
messages.success(request, 'Registration archive process has finished.')
764+
except Exception as exc:
765+
messages.error(request, f'This registration cannot be archived due to {exc.__class__.__name__}: {str(exc)}. '
766+
f'If the problem persists get a developer to fix it.')
713767

714768
return redirect(self.get_success_url())
715769

admin/preprints/urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313
re_path(r'^(?P<guid>\w+)/change_provider/$', views.PreprintProviderChangeView.as_view(), name='preprint-provider'),
1414
re_path(r'^(?P<guid>\w+)/machine_state/$', views.PreprintMachineStateView.as_view(), name='preprint-machine-state'),
1515
re_path(r'^(?P<guid>\w+)/reindex_share_preprint/$', views.PreprintReindexShare.as_view(), name='reindex-share-preprint'),
16+
re_path(r'^(?P<guid>\w+)/reversion_preprint/$', views.PreprintReVersion.as_view(), name='re-version-preprint'),
1617
re_path(r'^(?P<guid>\w+)/remove_user/(?P<user_id>[a-z0-9]+)/$', views.PreprintRemoveContributorView.as_view(), name='remove-user'),
1718
re_path(r'^(?P<guid>\w+)/make_private/$', views.PreprintMakePrivate.as_view(), name='make-private'),
19+
re_path(r'^(?P<guid>\w+)/fix_editing/$', views.PreprintFixEditing.as_view(), name='fix-editing'),
1820
re_path(r'^(?P<guid>\w+)/make_public/$', views.PreprintMakePublic.as_view(), name='make-public'),
1921
re_path(r'^(?P<guid>\w+)/remove/$', views.PreprintDeleteView.as_view(), name='remove'),
2022
re_path(r'^(?P<guid>\w+)/restore/$', views.PreprintDeleteView.as_view(), name='restore'),

admin/preprints/views.py

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
from django.db import transaction
12
from django.db.models import F
23
from django.core.exceptions import PermissionDenied
3-
from django.urls import NoReverseMatch
4+
from django.http import HttpResponse, JsonResponse
45
from django.contrib import messages
56
from django.contrib.auth.mixins import PermissionRequiredMixin
67
from django.shortcuts import redirect
@@ -10,7 +11,7 @@
1011
FormView,
1112
)
1213
from django.utils import timezone
13-
from django.urls import reverse_lazy
14+
from django.urls import NoReverseMatch, reverse_lazy
1415

1516
from admin.base.views import GuidView
1617
from admin.base.forms import GuidForm
@@ -19,9 +20,13 @@
1920

2021
from api.share.utils import update_share
2122
from api.providers.workflows import Workflows
23+
from api.preprints.serializers import PreprintSerializer
2224

2325
from osf.exceptions import PreprintStateError
26+
from rest_framework.exceptions import PermissionDenied as DrfPermissionDenied
27+
from framework.exceptions import PermissionsError
2428

29+
from osf.management.commands.fix_preprints_has_data_links_and_why_no_data import process_wrong_why_not_data_preprints
2530
from osf.models import (
2631
SpamStatus,
2732
Preprint,
@@ -44,6 +49,7 @@
4449
)
4550
from osf.utils.workflows import DefaultStates
4651
from website import search
52+
from website.files.utils import copy_files
4753
from website.preprints.tasks import on_preprint_updated
4854

4955

@@ -55,8 +61,8 @@ def get_object(self):
5561
preprint.guid = preprint._id
5662
return preprint
5763

58-
def get_success_url(self):
59-
return reverse_lazy('preprints:preprint', kwargs={'guid': self.kwargs['guid']})
64+
def get_success_url(self, guid=None):
65+
return reverse_lazy('preprints:preprint', kwargs={'guid': guid or self.kwargs['guid']})
6066

6167

6268
class PreprintView(PreprintMixin, GuidView):
@@ -182,6 +188,55 @@ def post(self, request, *args, **kwargs):
182188
return redirect(self.get_success_url())
183189

184190

191+
class PreprintReVersion(PreprintMixin, View):
192+
"""Allows an authorized user to create new version 1 of a preprint based on earlier
193+
primary file version(s). All operations are executed within an atomic transaction.
194+
If any step fails, the entire transaction will be rolled back and no version will be changed.
195+
"""
196+
permission_required = 'osf.change_node'
197+
198+
def post(self, request, *args, **kwargs):
199+
preprint = self.get_object()
200+
201+
file_versions = request.POST.getlist('file_versions')
202+
if not file_versions:
203+
return HttpResponse('At least one file version should be attached.', status=400)
204+
205+
try:
206+
with transaction.atomic():
207+
versions = preprint.get_preprint_versions()
208+
for version in versions:
209+
version.upgrade_version()
210+
211+
new_preprint, data_to_update = Preprint.create_version(
212+
create_from_guid=preprint._id,
213+
assign_version_number=1,
214+
auth=request,
215+
ignore_permission=True,
216+
ignore_existing_versions=True,
217+
)
218+
data_to_update = data_to_update or dict()
219+
220+
primary_file = copy_files(preprint.primary_file, target_node=new_preprint, identifier__in=file_versions)
221+
if primary_file is None:
222+
raise ValueError(f"Primary file {preprint.primary_file.id} doesn't have following versions: {file_versions}") # rollback changes
223+
data_to_update['primary_file'] = primary_file
224+
225+
# FIXME: currently it's not possible to ignore permission when update subjects
226+
# via serializer, remove this logic if deprecated
227+
subjects = data_to_update.pop('subjects', None)
228+
if subjects:
229+
new_preprint.set_subjects_from_relationships(subjects, auth=request, ignore_permission=True)
230+
231+
PreprintSerializer(new_preprint, context={'request': request, 'ignore_permission': True}).update(new_preprint, data_to_update)
232+
except ValueError as exc:
233+
return HttpResponse(str(exc), status=400)
234+
except (PermissionsError, DrfPermissionDenied) as exc:
235+
return HttpResponse(f'Not enough permissions to perform this action : {str(exc)}', status=400)
236+
237+
return JsonResponse({'redirect': self.get_success_url(new_preprint._id)})
238+
239+
185240
class PreprintReindexElastic(PreprintMixin, View):
186241
""" Allows an authorized user to reindex a node in ElasticSearch.
187242
"""
@@ -525,6 +580,21 @@ def post(self, request, *args, **kwargs):
525580

526581
return redirect(self.get_success_url())
527582

583+
class PreprintFixEditing(PreprintMixin, View):
584+
""" Allows an authorized user to manually fix why not data field.
585+
"""
586+
permission_required = 'osf.change_node'
587+
588+
def post(self, request, *args, **kwargs):
589+
preprint = self.get_object()
590+
process_wrong_why_not_data_preprints(
591+
version_guid=preprint._id,
592+
dry_run=False,
593+
executing_through_command=False,
594+
)
595+
596+
return redirect(self.get_success_url())
597+
528598

529599
class PreprintMakePublic(PreprintMixin, View):
530600
""" Allows an authorized user to manually make a private preprint public.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
$(document).ready(function() {
2+
3+
$("#confirmReversion").on("submit", function (event) {
4+
event.preventDefault();
5+
6+
$.ajax({
7+
url: window.templateVars.reVersionPreprint,
8+
type: "post",
9+
data: $("#re-version-preprint-form").serialize(),
10+
}).success(function (response) {
11+
if (response.redirect) {
12+
window.location.href = response.redirect;
13+
}
14+
}).fail(function (jqXHR, textStatus, error) {
15+
$("#version-validation").text(jqXHR.responseText);
16+
});
17+
});
18+
});

admin/templates/nodes/node.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
<a href="{% url 'nodes:search' %}" class="btn btn-primary"> <i class="fa fa-search"></i></a>
1818
<a href="{% url 'nodes:node-logs' guid=node.guid %}" class="btn btn-primary">View Logs</a>
1919
{% include "nodes/remove_node.html" with node=node %}
20-
{% include "nodes/restart_stuck_registration.html" with node=node %}
20+
{% include "nodes/registration_force_archive.html" with node=node %}
2121
{% include "nodes/make_private.html" with node=node %}
2222
{% include "nodes/make_public.html" with node=node %}
2323
{% include "nodes/mark_spam.html" with node=node %}

0 commit comments

Comments
 (0)