Skip to content

Commit 9238258

Browse files
committed
fix issues with migrate schema response task deletion
1 parent b0d8bdc commit 9238258

File tree

5 files changed

+176
-158
lines changed

5 files changed

+176
-158
lines changed

api_tests/users/views/test_user_settings.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,10 +267,10 @@ def test_post_invalid_password(self, app, url, user_one, csrf_token):
267267
res = app.post_json_api(url, payload, expect_errors=True, headers={'X-THROTTLE-TOKEN': 'test-token', 'X-CSRFToken': csrf_token})
268268
assert res.status_code == 400
269269

270-
def test_throrrle(self, app, url, user_one):
270+
def test_throttle(self, app, url, user_one):
271271
encoded_email = urllib.parse.quote(user_one.email)
272272
url = f'{url}?email={encoded_email}'
273-
res = app.get(url)
273+
app.get(url)
274274
user_one.reload()
275275
payload = {
276276
'data': {

osf/management/commands/migrate_notifications.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from osf.models.notifications import NotificationSubscriptionLegacy
55
from django.core.management.base import BaseCommand
66
from django.db import transaction
7-
from osf.management.commands.local_setup.populate_notification_types import populate_notification_types
7+
from osf.management.commands.populate_notification_types import populate_notification_types
88

99
logger = logging.getLogger(__name__)
1010

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import datetime
2+
import logging
3+
4+
from django.core.management.base import BaseCommand
5+
from django.apps import apps
6+
from tqdm import tqdm
7+
8+
from framework.celery_tasks import app as celery_app
9+
from framework import sentry
10+
11+
from osf.exceptions import SchemaBlockConversionError
12+
from osf.utils.registrations import flatten_registration_metadata
13+
14+
logger = logging.getLogger(__name__)
15+
16+
# because Registrations and DraftRegistrations are different
17+
def get_nested_responses(registration_or_draft, schema_id):
18+
nested_responses = getattr(
19+
registration_or_draft,
20+
'registration_metadata',
21+
None,
22+
)
23+
if nested_responses is None:
24+
registered_meta = registration_or_draft.registered_meta or {}
25+
nested_responses = registered_meta.get(schema_id, None)
26+
return nested_responses
27+
28+
# because Registrations and DraftRegistrations are different
29+
def get_registration_schema(registration_or_draft):
30+
schema = getattr(registration_or_draft, 'registration_schema', None)
31+
if schema is None:
32+
schema = registration_or_draft.registered_schema.first()
33+
return schema
34+
35+
def migrate_registrations(dry_run, rows='all', AbstractNodeModel=None):
36+
"""
37+
Loops through registrations whose registration_responses have not been migrated,
38+
and pulls this information from the "registered_meta" and flattens it, with
39+
keys being the "registration_response_key"s and values being the most deeply
40+
nested user response in registered_meta
41+
"""
42+
if AbstractNodeModel is None:
43+
AbstractNodeModel = apps.get_model('osf', 'abstractnode')
44+
45+
registrations = AbstractNodeModel.objects.filter(
46+
type='osf.registration',
47+
).exclude(
48+
registration_responses_migrated=True,
49+
)
50+
return migrate_responses(AbstractNodeModel, registrations, 'registrations', dry_run, rows)
51+
52+
def migrate_draft_registrations(dry_run, rows='all', DraftRegistrationModel=None):
53+
"""
54+
Populates a subset of draft_registration.registration_responses, and corresponding
55+
draft_registration.registration_responses_migrated.
56+
:params dry_run
57+
:params rows
58+
"""
59+
if DraftRegistrationModel is None:
60+
DraftRegistrationModel = apps.get_model('osf', 'draftregistration')
61+
62+
draft_registrations = DraftRegistrationModel.objects.exclude(
63+
registration_responses_migrated=True
64+
)
65+
return migrate_responses(DraftRegistrationModel, draft_registrations, 'draft registrations', dry_run, rows)
66+
67+
68+
def migrate_responses(model, resources, resource_name, dry_run=False, rows='all'):
69+
"""
70+
DRY method to be used to migrate both DraftRegistration.registration_responses
71+
and Registration.registration_responses.
72+
"""
73+
progress_bar = None
74+
if rows == 'all':
75+
logger.info(f'Migrating all {resource_name}.')
76+
else:
77+
resources = resources[:rows]
78+
logger.info(f'Migrating up to {rows} {resource_name}.')
79+
progress_bar = tqdm(total=rows)
80+
81+
successes_to_save = []
82+
errors_to_save = []
83+
for resource in resources:
84+
try:
85+
schema = get_registration_schema(resource)
86+
resource.registration_responses = flatten_registration_metadata(
87+
schema,
88+
get_nested_responses(resource, schema._id),
89+
)
90+
resource.registration_responses_migrated = True
91+
successes_to_save.append(resource)
92+
except SchemaBlockConversionError as e:
93+
resource.registration_responses_migrated = False
94+
errors_to_save.append(resource)
95+
logger.error(f'Unexpected/invalid nested data in resource: {resource} with error {e}')
96+
if progress_bar:
97+
progress_bar.update()
98+
99+
if progress_bar:
100+
progress_bar.close()
101+
102+
success_count = len(successes_to_save)
103+
error_count = len(errors_to_save)
104+
total_count = success_count + error_count
105+
106+
if total_count == 0:
107+
logger.info(f'No {resource_name} left to migrate.')
108+
return total_count
109+
110+
logger.info(f'Successfully migrated {success_count} out of {total_count} {resource_name}.')
111+
if error_count:
112+
logger.warning(f'Encountered errors on {error_count} out of {total_count} {resource_name}.')
113+
if not success_count:
114+
sentry.log_message(f'`migrate_registration_responses` has only errors left ({error_count} errors)')
115+
116+
if dry_run:
117+
logger.info('DRY RUN; discarding changes.')
118+
else:
119+
logger.info('Saving changes...')
120+
model.objects.bulk_update(successes_to_save, fields=['registration_responses', 'registration_responses_migrated'])
121+
model.objects.bulk_update(errors_to_save, fields=['registration_responses_migrated'])
122+
123+
return total_count
124+
125+
126+
@celery_app.task(name='management.commands.migrate_registration_responses')
127+
def migrate_registration_responses(dry_run=False, rows=5000):
128+
script_start_time = datetime.datetime.now()
129+
logger.info(f'Script started time: {script_start_time}')
130+
131+
draft_count = migrate_draft_registrations(dry_run, rows)
132+
registration_count = migrate_registrations(dry_run, rows)
133+
134+
if draft_count == 0 and registration_count == 0:
135+
logger.info('Migration complete! No more drafts or registrations need migrating.')
136+
sentry.log_message('`migrate_registration_responses` command found nothing to migrate!')
137+
138+
script_finish_time = datetime.datetime.now()
139+
logger.info(f'Script finished time: {script_finish_time}')
140+
logger.info(f'Run time {script_finish_time - script_start_time}')
141+
142+
143+
class Command(BaseCommand):
144+
help = """ Incrementally migrates DraftRegistration.registration_metadata
145+
-> DraftRegistration.registration_responses, and Registration.registered_meta
146+
-> Registration.registered_responses. registration_responses is a flattened version
147+
of registration_metadata/registered_meta.
148+
149+
This will need to be run multiple times to migrate all records on prod.
150+
"""
151+
152+
def add_arguments(self, parser):
153+
parser.add_argument(
154+
'--dry_run',
155+
type=bool,
156+
default=False,
157+
help='Run queries but do not write files',
158+
)
159+
parser.add_argument(
160+
'--rows',
161+
type=int,
162+
default=5000,
163+
help='How many rows to process during this run',
164+
)
165+
166+
# Management command handler
167+
def handle(self, *args, **options):
168+
dry_run = options['dry_run']
169+
rows = options['rows']
170+
if dry_run:
171+
logger.info('DRY RUN')
172+
173+
migrate_registration_responses(dry_run, rows)

osf_tests/management_commands/test_migrate_preprint_affiliations.py

Lines changed: 0 additions & 151 deletions
This file was deleted.

website/settings/defaults.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,6 @@ class CeleryConfig:
441441
'osf.management.commands.migrate_pagecounter_data',
442442
'osf.management.commands.migrate_deleted_date',
443443
'osf.management.commands.addon_deleted_date',
444-
'osf.management.commands.migrate_registration_responses',
445444
'osf.management.commands.archive_registrations_on_IA'
446445
'osf.management.commands.sync_doi_metadata',
447446
'osf.management.commands.sync_collection_provider_indices',
@@ -693,9 +692,6 @@ class CeleryConfig:
693692
# 'task': 'management.commands.migrate_pagecounter_data',
694693
# 'schedule': crontab(minute=0, hour=7), # Daily 2:00 a.m.
695694
# },
696-
# 'migrate_registration_responses': {
697-
# 'task': 'management.commands.migrate_registration_responses',
698-
# 'schedule': crontab(minute=32, hour=7), # Daily 2:32 a.m.
699695
# 'migrate_deleted_date': {
700696
# 'task': 'management.commands.migrate_deleted_date',
701697
# 'schedule': crontab(minute=0, hour=3),

0 commit comments

Comments
 (0)