From 6294baf4568b9beaf14fd3907da1ea02f79ebc54 Mon Sep 17 00:00:00 2001 From: bmedx Date: Wed, 6 Dec 2017 12:48:05 -0500 Subject: [PATCH] Add command to bulk move user language prefs from one code to another --- .../commands/migrate_user_profile_langs.py | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 openedx/core/djangoapps/user_api/management/commands/migrate_user_profile_langs.py diff --git a/openedx/core/djangoapps/user_api/management/commands/migrate_user_profile_langs.py b/openedx/core/djangoapps/user_api/management/commands/migrate_user_profile_langs.py new file mode 100644 index 0000000000..c8a064d351 --- /dev/null +++ b/openedx/core/djangoapps/user_api/management/commands/migrate_user_profile_langs.py @@ -0,0 +1,101 @@ +""" +Migrates user preferences from one language code to another in batches. Dark lang preferences are not affected. +""" +from __future__ import print_function + +import logging +from time import sleep + +from django.conf import settings +from django.core.management.base import BaseCommand, CommandError +from django.db import transaction +from django.db.models import Q, Max + +from openedx.core.djangoapps.dark_lang.models import DarkLangConfig +from openedx.core.djangoapps.user_api.models import UserPreference + + +DEFAULT_CHUNK_SIZE = 10000 +DEFAULT_SLEEP_TIME_SECS = 10 + +LOGGER = logging.getLogger(__name__) + + +class Command(BaseCommand): + """ + Implementation of the migrate command + """ + help = 'Migrate all user language preferences (excluding dark languages) from one language code to another.' + + def add_arguments(self, parser): + parser.add_argument('old_lang_code', + help='Original language code, ex. "zh-cn"') + parser.add_argument('new_lang_code', + help='New language code, ex. "zh-hans"') + parser.add_argument('--start_id', + type=int, + default=1, + help='ID to begin from, in case a run needs to be restarted from the middle.') + parser.add_argument('--chunk_size', + type=int, + default=DEFAULT_CHUNK_SIZE, + help='Number of users whose preferences will be updated per batch.') + parser.add_argument('--sleep_time_secs', + type=int, + default=DEFAULT_SLEEP_TIME_SECS, + help='Number of seconds to sleep between batches.') + + def handle(self, *args, **options): + """ + Execute the command. + """ + old_lang_code = options['old_lang_code'] + new_lang_code = options['new_lang_code'] + chunk_size = options['chunk_size'] + sleep_time_secs = options['sleep_time_secs'] + start = options['start_id'] + end = start + chunk_size + + # Make sure we're changing to a code that actually exists. Presumably it's safe to move away from a code that + # doesn't. + langs = [lang_code[0] for lang_code in settings.LANGUAGES] + langs += DarkLangConfig.current().released_languages_list + + if new_lang_code not in langs: + raise CommandError('{} is not a configured language code in settings.LANGUAGES ' + 'or the current DarkLangConfig.'.format(new_lang_code)) + + max_id = UserPreference.objects.all().aggregate(Max('id'))['id__max'] + + print('Updating user language preferences from {} to {}. ' + 'Start id is {}, current max id is {}. ' + 'Chunk size is of {}'.format(old_lang_code, new_lang_code, start, max_id, chunk_size)) + + updated_count = 0 + + while True: + # On the last time through catch any new rows added since this run began + if end >= max_id: + print('Last round, includes all new rows added since this run started.') + id_query = Q(id__gte=start) + else: + id_query = Q(id__gte=start) & Q(id__lt=end) + + curr = UserPreference.objects.filter( + id_query, + key='pref-lang', + value=old_lang_code + ).update(value=new_lang_code) + + updated_count += curr + + print('Updated rows {} to {}, {} rows affected'.format(start, end - 1, curr)) + + if end >= max_id: + break + + start = end + end += chunk_size + sleep(sleep_time_secs) + + print('Finished! Updated {} total preferences from {} to {}'.format(updated_count, old_lang_code, new_lang_code))