Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion common/djangoapps/student/models/course_enrollment.py
Original file line number Diff line number Diff line change
Expand Up @@ -1581,7 +1581,13 @@ def retire_manual_enrollments(cls, user, retired_email):
return False

for manual_enrollment_audit in manual_enrollment_audits:
manual_enrollment_audit.history.update(reason="", enrolled_email=retired_email)
try:
manual_enrollment_audit.history.update(reason="", enrolled_email=retired_email)
except CourseOverview.DoesNotExist:
log.warning(
f"CourseOverview missing for enrollment during retirement, skipping historical update "
f"for audit {manual_enrollment_audit.id}"
)
manual_enrollment_audits.update(reason="", enrolled_email=retired_email)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this also require similar bullet proofing.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this will not require bullet proofing. This is a bulk SQL update that directly updates student_manualenrollmentaudit, modifying only reason and enrolled_email.

It runs as a single UPDATE statement, does not trigger django-simple-history, and does not access related models like CourseOverview.

return True

Expand Down
24 changes: 24 additions & 0 deletions common/djangoapps/student/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,30 @@ def test_retirement(self):
assert not ManualEnrollmentAudit.objects.filter(enrollment=enrollment).exclude(enrolled_email='xxx')
assert not ManualEnrollmentAudit.objects.filter(enrollment=enrollment).exclude(reason='')

def test_retirement_with_missing_course_overview(self):
"""
Tests that retirement succeeds gracefully when CourseOverview is missing,
skipping historical updates but still updating current records.
"""
enrollment = CourseEnrollment.enroll(self.user, self.course.id)
ManualEnrollmentAudit.create_manual_enrollment_audit(
self.instructor, self.user.email, ALLOWEDTOENROLL_TO_ENROLLED,
'test enrollment with missing overview', enrollment
)

# Delete the CourseOverview to simulate the missing overview scenario
CourseOverview.objects.filter(id=self.course.id).delete()

# Retirement should complete successfully without raising CourseOverview.DoesNotExist
ManualEnrollmentAudit.retire_manual_enrollments(user=self.user, retired_email="retired@test.com")

# Verify the main audit records were still updated despite missing CourseOverview
assert ManualEnrollmentAudit.objects.filter(enrollment=enrollment).exists()
assert not ManualEnrollmentAudit.objects.filter(enrollment=enrollment).exclude(
enrolled_email='retired@test.com'
)
assert not ManualEnrollmentAudit.objects.filter(enrollment=enrollment).exclude(reason='')


class TestAccountRecovery(TestCase):
"""
Expand Down
Loading