Skip to content

feat: add unenroll_learners management command for bulk unenrollment#3603

Open
Anas12091101 wants to merge 4 commits into
mainfrom
anas/add-bulk-unenroll
Open

feat: add unenroll_learners management command for bulk unenrollment#3603
Anas12091101 wants to merge 4 commits into
mainfrom
anas/add-bulk-unenroll

Conversation

@Anas12091101
Copy link
Copy Markdown
Contributor

@Anas12091101 Anas12091101 commented May 22, 2026

What are the relevant tickets?

https://github.com/mitodl/hq/issues/11347

Description

Add a new unenroll_learners management command that supports bulk unenrollment of learners from course runs in both edX and MITx Online.

Input modes:

  • --csv — CSV file with user and courseware_id columns
  • --users with --run — Comma-separated user emails/usernames for a specific course run
  • --run alone — Unenroll ALL active learners from a course run

Safety & options:

  • Defaults to dry-run mode (preview only); requires --commit to execute
  • --no-email suppresses unenrollment notification emails
  • -k keeps local enrollment records even if edX unenrollment fails

The core unenrollment logic is extracted into shared utility functions (unenroll_learner_from_run and bulk_unenroll_learners) in courses/management/utils.py, which handle user/run resolution, enrollment deactivation via deactivate_run_enrollment, and structured logging. A send_notification parameter was added to deactivate_run_enrollment in courses/api.py to support email suppression.

The previous single-user unenroll_enrollment command has been removed since unenroll_learners covers both single and bulk use cases.

How to test

1. Dry-run (default — no changes made):

./manage.py unenroll_learners --users=user@example.com --run=course-v1:MITx+6.00.1x+2024

Verify output shows (DRY RUN), lists "Would unenroll" entries, and prints "Re-run with --commit to apply changes."

2. Unenroll all active learners from a course run (preview):

./manage.py unenroll_learners --run=course-v1:MITx+6.00.1x+2024

Verify it lists ALL active enrollments for that run in dry-run mode.

3. Unenroll specific users with --commit:

./manage.py unenroll_learners --users=user1@example.com,user2@example.com --run=course-v1:MITx+6.00.1x+2024 --commit

Verify users are unenrolled from both edX and MITx Online, and summary shows succeeded count.

4. Unenroll via CSV with --commit:

./manage.py unenroll_learners --csv=unenrollments.csv --commit

CSV format: user,courseware_id columns. Verify all listed users are unenrolled.

5. Unenroll all active learners from a run with --commit:

./manage.py unenroll_learners --run=course-v1:MITx+6.00.1x+2024 --commit

Verify ALL active enrollments for that run are unenrolled.

6. Suppress unenrollment emails (--no-email):

./manage.py unenroll_learners --users=user@example.com --run=course-v1:MITx+6.00.1x+2024 --commit --no-email

Verify unenrollment succeeds and no notification email is sent to the learner.

7. Keep failed enrollments (-k):

./manage.py unenroll_learners --users=user@example.com --run=course-v1:MITx+6.00.1x+2024 --commit -k

Verify local enrollment record is preserved even if edX API call fails.

8. Conflicting arguments are rejected:

./manage.py unenroll_learners --csv=file.csv --users=user@example.com
./manage.py unenroll_learners --csv=file.csv --run=course-v1:MITx+6.00.1x+2024
./manage.py unenroll_learners --users=user@example.com
./manage.py unenroll_learners

Verify each raises a clear error message.

9. Run automated tests:

docker compose run --rm -e WEBPACK_LOADER_ENABLED=False web pytest unenroll_learners_test.py -v

All 26 tests should pass.

Additional Context

@github-actions
Copy link
Copy Markdown

OpenAPI Changes

Show/hide ## Changes for v0.yaml:
## Changes for v0.yaml:
No changes detected

## Changes for v1.yaml:
No changes detected

## Changes for v2.yaml:
No changes detected

Unexpected changes? Ensure your branch is up-to-date with main (consider rebasing).

@Anas12091101 Anas12091101 force-pushed the anas/add-bulk-unenroll branch from df36094 to 163556a Compare May 22, 2026 13:36
@jkachel
Copy link
Copy Markdown
Contributor

jkachel commented May 22, 2026

Chiming in briefly on this - not sure if it's ready for review yet or not since tests are failing. But there's two things that I think would be helpful:

  • Ability to tag a courseware ID for unenroll: the CSV format is an improvement but it'd also be nice to just be able to tag a particular course run. (I had to remove pretty much everyone from a handful of course runs; I could pull the enrollments into a CSV file but it'd been easier to just specify the runs.)
  • Ability to suppress the unenrollment email. Especially in the case that I was dealing with, we may not want to send out a bunch of (potentially confusing) email to people telling them they're unenrolled from a course that wasn't valid anyway.

A potential third:

  • Default to dry-run - in other words, preview first, require a flag to make changes, especially for the bulk moves so we're forced to be mindful of what we're doing.

I did read through the code and it looked pretty good but I haven't done a formal review at the moment.

@Anas12091101
Copy link
Copy Markdown
Contributor Author

@jkachel, thanks for the suggestions. I’ve implemented all three of them, and this PR is now ready for review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants