Skip to content
Snippets Groups Projects
Commit 599d6637 authored by Kyle McCormick's avatar Kyle McCormick Committed by Kyle McCormick
Browse files

Add option to backfill org data as inactive

Add an `--inactive` option to the
`bulk_add_orgs_and_org_courses` management
command, which causes the backfill to set
`active=False` on all rows created in the
Organization and OrganizationCourse tables.

This will lower the potential data
integrity risk to production systems
such as courses.edx.org.

Upgrade edx-organizations to 6.6.0

TNL-7774
parent 492ce7f2
No related branches found
No related tags found
No related merge requests found
...@@ -107,6 +107,11 @@ class Command(BaseCommand): ...@@ -107,6 +107,11 @@ class Command(BaseCommand):
action='store_true', action='store_true',
help="Show backfill, but do not apply changes to database." help="Show backfill, but do not apply changes to database."
) )
parser.add_argument(
'--inactive',
action='store_true',
help="Backfill data as inactive and do not re-activate any existing data."
)
def handle(self, *args, **options): def handle(self, *args, **options):
""" """
...@@ -135,7 +140,12 @@ class Command(BaseCommand): ...@@ -135,7 +140,12 @@ class Command(BaseCommand):
if not confirm_changes(options, orgs, org_courseid_pairs): if not confirm_changes(options, orgs, org_courseid_pairs):
print("No changes applied.") print("No changes applied.")
return return
bulk_add_data(orgs, org_courseid_pairs, dry_run=False) bulk_add_data(
orgs,
org_courseid_pairs,
dry_run=False,
activate=(not options.get('inactive')),
)
def confirm_changes(options, orgs, org_courseid_pairs): def confirm_changes(options, orgs, org_courseid_pairs):
...@@ -158,7 +168,12 @@ def confirm_changes(options, orgs, org_courseid_pairs): ...@@ -158,7 +168,12 @@ def confirm_changes(options, orgs, org_courseid_pairs):
raise CommandError("Only one of 'apply' and 'dry' may be specified") raise CommandError("Only one of 'apply' and 'dry' may be specified")
if options.get('apply'): if options.get('apply'):
return True return True
bulk_add_data(orgs, org_courseid_pairs, dry_run=True) bulk_add_data(
orgs,
org_courseid_pairs,
dry_run=True,
activate=(not options.get('inactive')),
)
if options.get('dry'): if options.get('dry'):
return False return False
answer = "" answer = ""
...@@ -167,7 +182,7 @@ def confirm_changes(options, orgs, org_courseid_pairs): ...@@ -167,7 +182,7 @@ def confirm_changes(options, orgs, org_courseid_pairs):
return answer.lower().startswith('y') return answer.lower().startswith('y')
def bulk_add_data(orgs, org_courseid_pairs, dry_run): def bulk_add_data(orgs, org_courseid_pairs, dry_run, activate):
""" """
Bulk-add the organizations and organization-course linkages. Bulk-add the organizations and organization-course linkages.
...@@ -182,6 +197,9 @@ def bulk_add_data(orgs, org_courseid_pairs, dry_run): ...@@ -182,6 +197,9 @@ def bulk_add_data(orgs, org_courseid_pairs, dry_run):
org_courseid_pairs (list[tuple[dict, str]]): org_courseid_pairs (list[tuple[dict, str]]):
list of (org data dictionary, course key string) links to bulk-add. list of (org data dictionary, course key string) links to bulk-add.
dry_run: Whether or not this run should be "dry" (ie, don't apply changes). dry_run: Whether or not this run should be "dry" (ie, don't apply changes).
activate: Whether newly-added organizations and organization-course linkages
should be activated, and whether existing-but-inactive
organizations/linkages should be reactivated.
""" """
adding_phrase = "Dry-run of bulk-adding" if dry_run else "Bulk-adding" adding_phrase = "Dry-run of bulk-adding" if dry_run else "Bulk-adding"
created_phrase = "Will create" if dry_run else "Created" created_phrase = "Will create" if dry_run else "Created"
...@@ -190,7 +208,7 @@ def bulk_add_data(orgs, org_courseid_pairs, dry_run): ...@@ -190,7 +208,7 @@ def bulk_add_data(orgs, org_courseid_pairs, dry_run):
print("------------------------------------------------------") print("------------------------------------------------------")
print(f"{adding_phrase} organizations...") print(f"{adding_phrase} organizations...")
orgs_created, orgs_reactivated = organizations_api.bulk_add_organizations( orgs_created, orgs_reactivated = organizations_api.bulk_add_organizations(
orgs, dry_run=dry_run orgs, dry_run=dry_run, activate=activate
) )
print(f"{created_phrase} {len(orgs_created)} organizations:") print(f"{created_phrase} {len(orgs_created)} organizations:")
for org_short_name in sorted(orgs_created): for org_short_name in sorted(orgs_created):
...@@ -202,7 +220,7 @@ def bulk_add_data(orgs, org_courseid_pairs, dry_run): ...@@ -202,7 +220,7 @@ def bulk_add_data(orgs, org_courseid_pairs, dry_run):
print("------------------------------------------------------") print("------------------------------------------------------")
print(f"{adding_phrase} organization-course linkages...") print(f"{adding_phrase} organization-course linkages...")
linkages_created, linkages_reactivated = organizations_api.bulk_add_organization_courses( linkages_created, linkages_reactivated = organizations_api.bulk_add_organization_courses(
org_courseid_pairs, dry_run=dry_run org_courseid_pairs, dry_run=dry_run, activate=activate
) )
print(f"{created_phrase} {len(linkages_created)} organization-course linkages:") print(f"{created_phrase} {len(linkages_created)} organization-course linkages:")
for org_short_name, course_id in sorted(linkages_created): for org_short_name, course_id in sorted(linkages_created):
......
...@@ -117,26 +117,43 @@ class BackfillOrgsAndOrgCoursesTest(SharedModuleStoreTestCase): ...@@ -117,26 +117,43 @@ class BackfillOrgsAndOrgCoursesTest(SharedModuleStoreTestCase):
"command_line_args": [], "command_line_args": [],
"user_inputs": ["n"], "user_inputs": ["n"],
"should_apply_changes": False, "should_apply_changes": False,
"should_data_be_activated": True,
}, },
{ {
"command_line_args": [], "command_line_args": [],
"user_inputs": ["x", "N"], "user_inputs": ["x", "N"],
"should_apply_changes": False, "should_apply_changes": False,
"should_data_be_activated": True,
}, },
{ {
"command_line_args": [], "command_line_args": [],
"user_inputs": ["", "", "YeS"], "user_inputs": ["", "", "YeS"],
"should_apply_changes": True, "should_apply_changes": True,
"should_data_be_activated": True,
},
{
"command_line_args": ["--inactive"],
"user_inputs": ["y"],
"should_apply_changes": True,
"should_data_be_activated": False,
}, },
{ {
"command_line_args": ["--dry"], "command_line_args": ["--dry"],
"user_inputs": [], "user_inputs": [],
"should_apply_changes": False, "should_apply_changes": False,
"should_data_be_activated": True,
},
{
"command_line_args": ["--dry", "--inactive"],
"user_inputs": [],
"should_apply_changes": False,
"should_data_be_activated": False,
}, },
{ {
"command_line_args": ["--apply"], "command_line_args": ["--apply"],
"user_inputs": [], "user_inputs": [],
"should_apply_changes": True, "should_apply_changes": True,
"should_data_be_activated": True,
}, },
) )
@ddt.unpack @ddt.unpack
...@@ -161,6 +178,7 @@ class BackfillOrgsAndOrgCoursesTest(SharedModuleStoreTestCase): ...@@ -161,6 +178,7 @@ class BackfillOrgsAndOrgCoursesTest(SharedModuleStoreTestCase):
command_line_args, command_line_args,
user_inputs, user_inputs,
should_apply_changes, should_apply_changes,
should_data_be_activated,
): ):
""" """
Test that the command-line arguments and user input processing works as Test that the command-line arguments and user input processing works as
...@@ -184,31 +202,38 @@ class BackfillOrgsAndOrgCoursesTest(SharedModuleStoreTestCase): ...@@ -184,31 +202,38 @@ class BackfillOrgsAndOrgCoursesTest(SharedModuleStoreTestCase):
# then we expect one DRY bulk-add run *and* one REAL bulk-add run. # then we expect one DRY bulk-add run *and* one REAL bulk-add run.
assert mock_add_orgs.call_count == 2 assert mock_add_orgs.call_count == 2
assert mock_add_org_courses.call_count == 2 assert mock_add_org_courses.call_count == 2
assert mock_add_orgs.call_args_list[0].kwargs == {"dry_run": True} assert mock_add_orgs.call_args_list[0].kwargs["dry_run"] is True
assert mock_add_org_courses.call_args_list[0].kwargs == {"dry_run": True} assert mock_add_org_courses.call_args_list[0].kwargs["dry_run"] is True
assert mock_add_orgs.call_args_list[1].kwargs == {"dry_run": False} assert mock_add_orgs.call_args_list[1].kwargs["dry_run"] is False
assert mock_add_org_courses.call_args_list[1].kwargs == {"dry_run": False} assert mock_add_org_courses.call_args_list[1].kwargs["dry_run"] is False
elif should_apply_changes: elif should_apply_changes:
# If DID apply changes but the user WASN'T prompted, # If DID apply changes but the user WASN'T prompted,
# then we expect just one REAL bulk-add run. # then we expect just one REAL bulk-add run.
assert mock_add_orgs.call_count == 1 assert mock_add_orgs.call_count == 1
assert mock_add_org_courses.call_count == 1 assert mock_add_org_courses.call_count == 1
assert mock_add_orgs.call_args.kwargs == {"dry_run": False} assert mock_add_orgs.call_args.kwargs["dry_run"] is False
assert mock_add_org_courses.call_args.kwargs == {"dry_run": False} assert mock_add_org_courses.call_args.kwargs["dry_run"] is False
elif user_inputs: elif user_inputs:
# If we DIDN'T apply changes but the user WAS prompted # If we DIDN'T apply changes but the user WAS prompted
# then we expect just one DRY bulk-add run. # then we expect just one DRY bulk-add run.
assert mock_add_orgs.call_count == 1 assert mock_add_orgs.call_count == 1
assert mock_add_org_courses.call_count == 1 assert mock_add_org_courses.call_count == 1
assert mock_add_orgs.call_args.kwargs == {"dry_run": True} assert mock_add_orgs.call_args.kwargs["dry_run"] is True
assert mock_add_org_courses.call_args.kwargs == {"dry_run": True} assert mock_add_org_courses.call_args.kwargs["dry_run"] is True
else: else:
# Similarly, if we DIDN'T apply changes and the user WASN'T prompted # Similarly, if we DIDN'T apply changes and the user WASN'T prompted
# then we expect just one DRY bulk-add run. # then we expect just one DRY bulk-add run.
assert mock_add_orgs.call_count == 1 assert mock_add_orgs.call_count == 1
assert mock_add_org_courses.call_count == 1 assert mock_add_org_courses.call_count == 1
assert mock_add_orgs.call_args.kwargs == {"dry_run": True} assert mock_add_orgs.call_args.kwargs["dry_run"] is True
assert mock_add_org_courses.call_args.kwargs == {"dry_run": True} assert mock_add_org_courses.call_args.kwargs["dry_run"] is True
# Assert that the value of of the "active" kwarg is correct for all
# calls both bulk-add functions, whether or not they were dry runs.
for call in mock_add_orgs:
assert call.kwargs["activate"] == should_data_be_activated
for call in mock_add_org_courses:
assert call.kwargs["activate"] == should_data_be_activated
def test_conflicting_arguments(self): def test_conflicting_arguments(self):
""" """
......
...@@ -103,7 +103,7 @@ edx-event-routing-backends==2.0.0 # via -r requirements/edx/base.in ...@@ -103,7 +103,7 @@ edx-event-routing-backends==2.0.0 # via -r requirements/edx/base.in
edx-i18n-tools==0.5.3 # via ora2 edx-i18n-tools==0.5.3 # via ora2
edx-milestones==0.3.0 # via -r requirements/edx/base.in edx-milestones==0.3.0 # via -r requirements/edx/base.in
edx-opaque-keys[django]==2.1.1 # via -r requirements/edx/paver.txt, edx-bulk-grades, edx-ccx-keys, edx-completion, edx-drf-extensions, edx-enterprise, edx-milestones, edx-organizations, edx-proctoring, edx-user-state-client, edx-when, lti-consumer-xblock, xmodule edx-opaque-keys[django]==2.1.1 # via -r requirements/edx/paver.txt, edx-bulk-grades, edx-ccx-keys, edx-completion, edx-drf-extensions, edx-enterprise, edx-milestones, edx-organizations, edx-proctoring, edx-user-state-client, edx-when, lti-consumer-xblock, xmodule
edx-organizations==6.5.0 # via -r requirements/edx/base.in edx-organizations==6.6.0 # via -r requirements/edx/base.in
edx-proctoring-proctortrack==1.0.5 # via -r requirements/edx/base.in edx-proctoring-proctortrack==1.0.5 # via -r requirements/edx/base.in
edx-proctoring==2.5.5 # via -r requirements/edx/base.in, edx-proctoring-proctortrack edx-proctoring==2.5.5 # via -r requirements/edx/base.in, edx-proctoring-proctortrack
edx-rbac==1.3.3 # via edx-enterprise edx-rbac==1.3.3 # via edx-enterprise
......
...@@ -115,7 +115,7 @@ edx-i18n-tools==0.5.3 # via -r requirements/edx/testing.txt, ora2 ...@@ -115,7 +115,7 @@ edx-i18n-tools==0.5.3 # via -r requirements/edx/testing.txt, ora2
edx-lint==1.6 # via -r requirements/edx/testing.txt edx-lint==1.6 # via -r requirements/edx/testing.txt
edx-milestones==0.3.0 # via -r requirements/edx/testing.txt edx-milestones==0.3.0 # via -r requirements/edx/testing.txt
edx-opaque-keys[django]==2.1.1 # via -r requirements/edx/testing.txt, edx-bulk-grades, edx-ccx-keys, edx-completion, edx-drf-extensions, edx-enterprise, edx-milestones, edx-organizations, edx-proctoring, edx-user-state-client, edx-when, lti-consumer-xblock, xmodule edx-opaque-keys[django]==2.1.1 # via -r requirements/edx/testing.txt, edx-bulk-grades, edx-ccx-keys, edx-completion, edx-drf-extensions, edx-enterprise, edx-milestones, edx-organizations, edx-proctoring, edx-user-state-client, edx-when, lti-consumer-xblock, xmodule
edx-organizations==6.5.0 # via -r requirements/edx/testing.txt edx-organizations==6.6.0 # via -r requirements/edx/testing.txt
edx-proctoring-proctortrack==1.0.5 # via -r requirements/edx/testing.txt edx-proctoring-proctortrack==1.0.5 # via -r requirements/edx/testing.txt
edx-proctoring==2.5.5 # via -r requirements/edx/testing.txt, edx-proctoring-proctortrack edx-proctoring==2.5.5 # via -r requirements/edx/testing.txt, edx-proctoring-proctortrack
edx-rbac==1.3.3 # via -r requirements/edx/testing.txt, edx-enterprise edx-rbac==1.3.3 # via -r requirements/edx/testing.txt, edx-enterprise
......
...@@ -112,7 +112,7 @@ edx-i18n-tools==0.5.3 # via -r requirements/edx/base.txt, -r requirements/ed ...@@ -112,7 +112,7 @@ edx-i18n-tools==0.5.3 # via -r requirements/edx/base.txt, -r requirements/ed
edx-lint==1.6 # via -r requirements/edx/testing.in edx-lint==1.6 # via -r requirements/edx/testing.in
edx-milestones==0.3.0 # via -r requirements/edx/base.txt edx-milestones==0.3.0 # via -r requirements/edx/base.txt
edx-opaque-keys[django]==2.1.1 # via -r requirements/edx/base.txt, edx-bulk-grades, edx-ccx-keys, edx-completion, edx-drf-extensions, edx-enterprise, edx-milestones, edx-organizations, edx-proctoring, edx-user-state-client, edx-when, lti-consumer-xblock, xmodule edx-opaque-keys[django]==2.1.1 # via -r requirements/edx/base.txt, edx-bulk-grades, edx-ccx-keys, edx-completion, edx-drf-extensions, edx-enterprise, edx-milestones, edx-organizations, edx-proctoring, edx-user-state-client, edx-when, lti-consumer-xblock, xmodule
edx-organizations==6.5.0 # via -r requirements/edx/base.txt edx-organizations==6.6.0 # via -r requirements/edx/base.txt
edx-proctoring-proctortrack==1.0.5 # via -r requirements/edx/base.txt edx-proctoring-proctortrack==1.0.5 # via -r requirements/edx/base.txt
edx-proctoring==2.5.5 # via -r requirements/edx/base.txt, edx-proctoring-proctortrack edx-proctoring==2.5.5 # via -r requirements/edx/base.txt, edx-proctoring-proctortrack
edx-rbac==1.3.3 # via -r requirements/edx/base.txt, edx-enterprise edx-rbac==1.3.3 # via -r requirements/edx/base.txt, edx-enterprise
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment