From efdbe1f3b1d425c4ca7bf11114e192630dc37661 Mon Sep 17 00:00:00 2001
From: George Babey <gbabey@edx.org>
Date: Thu, 30 Jan 2020 13:32:25 -0500
Subject: [PATCH] Adds management command for bulk updating email addresses

This PR adds a management command that will update the email address
of the given accounts provided through a CSV file.

This is necessary internally at edX to update a number of internal accounts
which are using sibling email addresses and would be locked out after we
enforce SSO.
---
 .../management/commands/bulk_update_email.py  | 76 +++++++++++++++++++
 1 file changed, 76 insertions(+)
 create mode 100644 common/djangoapps/student/management/commands/bulk_update_email.py

diff --git a/common/djangoapps/student/management/commands/bulk_update_email.py b/common/djangoapps/student/management/commands/bulk_update_email.py
new file mode 100644
index 00000000000..1f53ea0efd5
--- /dev/null
+++ b/common/djangoapps/student/management/commands/bulk_update_email.py
@@ -0,0 +1,76 @@
+"""
+Management command to bulk update many user's email addresses
+"""
+
+
+import csv
+import logging
+from os import path
+
+from django.core.management.base import BaseCommand, CommandError
+
+from django.contrib.auth import get_user_model
+
+logger = logging.getLogger('student.management.commands.bulk_update_email')
+
+
+class Command(BaseCommand):
+    """
+        Management command to bulk update many user's email addresses
+    """
+
+    help = """
+        Change the email address of each user specified in the csv file
+
+        csv file is expected to have one row per user with the format:
+        current_email_address, new_email_address
+
+        Example:
+            $ ... bulk_update_email csv_file_path
+        """
+
+    def add_arguments(self, parser):
+        """ Add argument to the command parser. """
+        parser.add_argument(
+            '--csv_file_path',
+            required=True,
+            help='Csv file path'
+        )
+
+    def handle(self, *args, **options):
+        """ Main handler for the command."""
+        file_path = options['csv_file_path']
+
+        if not path.isfile(file_path):
+            raise CommandError('File not found.')
+
+        with open(file_path, 'r') as csv_file:
+            csv_reader = csv.reader(csv_file)
+
+            email_mappings = [
+                (current_email, new_email)
+                for (current_email, new_email)
+                in csv_reader
+            ]
+
+        successful_updates = []
+        failed_updates = []
+
+        for (current_email, new_email) in email_mappings:
+            try:
+                user = get_user_model().objects.get(email=current_email)
+                user.email = new_email
+                user.save()
+                successful_updates.append(new_email)
+            except Exception:  # pylint: disable=broad-except
+                logger.exception('Unable to update account %s', current_email)
+                failed_updates.append(current_email)
+
+        logger.info(
+            'Successfully updated %s accounts. Failed to update %s accounts',
+            len(successful_updates),
+            len(failed_updates)
+        )
+
+        if (failed_updates):
+            exit(-1)
-- 
GitLab