Skip to content

Commit 86c26bf

Browse files
committed
Add management command to manual archive
1 parent 7ebd91a commit 86c26bf

File tree

7 files changed

+405
-9
lines changed

7 files changed

+405
-9
lines changed

admin/nodes/views.py

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -866,7 +866,8 @@ class ForceArchiveRegistrationsView(NodeMixin, View):
866866

867867
def post(self, request, *args, **kwargs):
868868
# Prevents circular imports that cause admin app to hang at startup
869-
from osf.management.commands.force_archive import verify, DEFAULT_PERMISSIBLE_ADDONS
869+
from osf.management.commands.force_archive import verify, archive, DEFAULT_PERMISSIBLE_ADDONS
870+
from osf.models.admin_log_entry import update_admin_log, MANUAL_ARCHIVE_RESTART
870871

871872
registration = self.get_object()
872873
force_archive_params = request.POST
@@ -891,14 +892,28 @@ def post(self, request, *args, **kwargs):
891892
if dry_mode:
892893
messages.success(request, f"Registration {registration._id} can be archived.")
893894
else:
894-
force_archive_task = force_archive.delay(
895-
str(registration._id),
896-
permissible_addons=list(addons),
897-
allow_unconfigured=allow_unconfigured,
898-
skip_collisions=skip_collision,
899-
delete_collisions=delete_collision,
900-
)
901-
messages.success(request, f'Registration archive process has started. Task id: {force_archive_task.id}.')
895+
try:
896+
update_admin_log(
897+
user_id=request.user.id,
898+
object_id=registration.pk,
899+
object_repr=str(registration),
900+
message=f'Manual archive restart initiated for registration {registration._id}',
901+
action_flag=MANUAL_ARCHIVE_RESTART
902+
)
903+
force_archive_task = force_archive.delay(
904+
str(registration._id),
905+
permissible_addons=list(addons),
906+
allow_unconfigured=allow_unconfigured,
907+
skip_collisions=skip_collision,
908+
delete_collisions=delete_collision,
909+
)
910+
messages.success(
911+
request,
912+
f'Registration archive process has started. Task id: {force_archive_task.id}.'
913+
)
914+
except Exception as exc:
915+
messages.error(request, f'This registration cannot be archived due to {exc.__class__.__name__}: {str(exc)}. '
916+
f'If the problem persists get a developer to fix it.')
902917

903918
return redirect(self.get_success_url())
904919

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import logging
2+
from datetime import timedelta
3+
from django.core.management.base import BaseCommand
4+
from django.utils import timezone
5+
from osf.models import Registration
6+
from osf.models.admin_log_entry import AdminLogEntry, MANUAL_ARCHIVE_RESTART
7+
from website import settings
8+
from scripts.approve_registrations import approve_past_pendings
9+
10+
logger = logging.getLogger(__name__)
11+
12+
13+
class Command(BaseCommand):
14+
help = 'Process registrations that were manually restarted and may need approval'
15+
16+
def add_arguments(self, parser):
17+
parser.add_argument(
18+
'--dry-run',
19+
action='store_true',
20+
help='Show what would be done without actually doing it',
21+
)
22+
parser.add_argument(
23+
'--hours-back',
24+
type=int,
25+
default=72,
26+
help='How many hours back to look for manual restarts (default: 72)',
27+
)
28+
parser.add_argument(
29+
'--registration-id',
30+
type=str,
31+
help='Process a specific registration ID only',
32+
)
33+
34+
def handle(self, *args, **options):
35+
dry_run = options['dry_run']
36+
hours_back = options['hours_back']
37+
specific_registration = options.get('registration_id')
38+
39+
if dry_run:
40+
self.stdout.write(self.style.WARNING('Running in DRY RUN mode - no changes will be made'))
41+
42+
since = timezone.now() - timedelta(hours=hours_back)
43+
44+
query = AdminLogEntry.objects.filter(
45+
action_flag=MANUAL_ARCHIVE_RESTART,
46+
action_time__gte=since
47+
)
48+
49+
if specific_registration:
50+
try:
51+
reg = Registration.objects.get(_id=specific_registration)
52+
query = query.filter(object_id=reg.pk)
53+
self.stdout.write(f"Processing specific registration: {specific_registration}")
54+
except Registration.DoesNotExist:
55+
self.stdout.write(self.style.ERROR(f"Registration {specific_registration} not found"))
56+
return
57+
58+
manual_restart_logs = query.values_list('object_id', flat=True).distinct()
59+
60+
registrations_to_check = Registration.objects.filter(
61+
pk__in=manual_restart_logs,
62+
)
63+
64+
self.stdout.write(f"Found {registrations_to_check.count()} manually restarted registrations to check")
65+
66+
approvals_ready = []
67+
skipped_registrations = []
68+
69+
for registration in registrations_to_check:
70+
status = self.should_auto_approve(registration)
71+
72+
if status == 'ready':
73+
approval = registration.registration_approval
74+
if approval:
75+
approvals_ready.append(approval)
76+
self.stdout.write(
77+
self.style.SUCCESS(f"✓ Queuing registration {registration._id} for approval")
78+
)
79+
else:
80+
skipped_registrations.append((registration._id, status))
81+
self.stdout.write(
82+
self.style.WARNING(f"⚠ Skipping registration {registration._id}: {status}")
83+
)
84+
85+
if approvals_ready:
86+
if dry_run:
87+
self.stdout.write(
88+
self.style.WARNING(f"DRY RUN: Would approve {len(approvals_ready)} registrations")
89+
)
90+
else:
91+
try:
92+
approve_past_pendings(approvals_ready, dry_run=False)
93+
self.stdout.write(
94+
self.style.SUCCESS(f"✓ Successfully approved {len(approvals_ready)} manually restarted registrations")
95+
)
96+
except Exception as e:
97+
self.stdout.write(
98+
self.style.ERROR(f"✗ Error approving registrations: {e}")
99+
)
100+
else:
101+
self.stdout.write('No registrations ready for approval')
102+
103+
self.stdout.write(f"Total checked: {registrations_to_check.count()}")
104+
self.stdout.write(f"Ready for approval: {len(approvals_ready)}")
105+
self.stdout.write(f"Skipped: {len(skipped_registrations)}")
106+
107+
if skipped_registrations:
108+
self.stdout.write('\nSkipped registrations:')
109+
for reg_id, reason in skipped_registrations:
110+
self.stdout.write(f" - {reg_id}: {reason}")
111+
112+
def should_auto_approve(self, registration):
113+
if registration.is_public:
114+
return 'already public'
115+
116+
if registration.is_registration_approved:
117+
return 'already approved'
118+
119+
if registration.archiving:
120+
return 'still archiving'
121+
122+
archive_job = registration.archive_job
123+
if archive_job and hasattr(archive_job, 'status'):
124+
if archive_job.status not in ['SUCCESS', None]:
125+
return f'archive status: {archive_job.status}'
126+
127+
approval = registration.registration_approval
128+
if not approval:
129+
return 'no approval object'
130+
131+
if approval.is_approved:
132+
return 'approval already approved'
133+
134+
if approval.is_rejected:
135+
return 'approval was rejected'
136+
137+
time_since_initiation = timezone.now() - approval.initiation_date
138+
if time_since_initiation < settings.REGISTRATION_APPROVAL_TIME:
139+
remaining = settings.REGISTRATION_APPROVAL_TIME - time_since_initiation
140+
return f'not ready yet ({remaining} remaining)'
141+
142+
if registration.is_stuck_registration:
143+
return 'registration still stuck'
144+
145+
return 'ready'

osf/models/admin_log_entry.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
DOI_CREATION_FAILED = 80
3838
DOI_UPDATE_FAILED = 81
3939

40+
MANUAL_ARCHIVE_RESTART = 90
41+
4042
def update_admin_log(user_id, object_id, object_repr, message, action_flag=UNKNOWN):
4143
AdminLogEntry.objects.log_action(
4244
user_id=user_id,
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import logging
2+
from framework.celery_tasks import app as celery_app
3+
from django.core.management import call_command
4+
from osf.models import Registration
5+
6+
logger = logging.getLogger(__name__)
7+
8+
9+
@celery_app.task(name='scripts.check_manual_restart_approval')
10+
def check_manual_restart_approval(registration_id):
11+
try:
12+
try:
13+
registration = Registration.objects.get(_id=registration_id)
14+
except Registration.DoesNotExist:
15+
logger.error(f"Registration {registration_id} not found")
16+
return f"Registration {registration_id} not found"
17+
18+
if registration.is_public or registration.is_registration_approved:
19+
return f"Registration {registration_id} already approved/public"
20+
21+
if registration.archiving:
22+
logger.info(f"Registration {registration_id} still archiving, retrying in 10 minutes")
23+
check_manual_restart_approval.apply_async(
24+
args=[registration_id],
25+
countdown=600
26+
)
27+
return f"Registration {registration_id} still archiving, scheduled retry"
28+
29+
logger.info(f"Processing manual restart approval for registration {registration_id}")
30+
31+
call_command(
32+
'process_manual_restart_approvals',
33+
registration_id=registration_id,
34+
dry_run=False,
35+
hours_back=24,
36+
verbosity=1
37+
)
38+
39+
return f"Processed manual restart approval check for registration {registration_id}"
40+
41+
except Exception as e:
42+
logger.error(f"Error processing manual restart approval for {registration_id}: {e}")
43+
raise
44+
45+
46+
@celery_app.task(name='scripts.check_manual_restart_approvals_batch')
47+
def check_manual_restart_approvals_batch(hours_back=24):
48+
try:
49+
logger.info(f"Running batch check for manual restart approvals (last {hours_back} hours)")
50+
51+
call_command(
52+
'process_manual_restart_approvals',
53+
dry_run=False,
54+
hours_back=hours_back,
55+
verbosity=1
56+
)
57+
58+
return f"Completed batch manual restart approval check for last {hours_back} hours"
59+
60+
except Exception as e:
61+
logger.error(f"Error in batch manual restart approval check: {e}")
62+
raise
63+
64+
65+
@celery_app.task(name='scripts.delayed_manual_restart_approval')
66+
def delayed_manual_restart_approval(registration_id, delay_minutes=30):
67+
logger.info(f"Scheduling delayed manual restart approval check for {registration_id} in {delay_minutes} minutes")
68+
69+
check_manual_restart_approval.apply_async(
70+
args=[registration_id],
71+
countdown=delay_minutes * 60
72+
)
73+
74+
return f"Scheduled manual restart approval check for {registration_id} in {delay_minutes} minutes"

0 commit comments

Comments
 (0)