Skip to content

Commit e5b497c

Browse files
fix: prevent None entrance_exam_minimum_score_pct from breaking CourseOverview sync (#37339)
* fix: prevent None entrance_exam_minimum_score_pct from breaking CourseOverview sync When entrance exams are disabled in Studio, the field `entrance_exam_minimum_score_pct` was set to `None`. This caused silent failures when saving `CourseOverview` because the database column requires a float (NOT NULL). This patch ensures that: - CourseOverview sanitizes None values by falling back to `settings.ENTRANCE_EXAM_MIN_SCORE_PCT` (default=50). - Studio avoids writing `None` and instead applies the configured default. Impact: - Prevents IntegrityErrors and silent failures when updating course settings. - Restores proper syncing between modulestore (Mongo) and CourseOverview (MySQL). - Fixes reported issues such as display name changes not persisting and course start dates not syncing. Closes: #37319 * refactor: clean up entrance_exam_minimum_score_pct handling - Consolidate logic to avoid repeated assignments - Centralize None fallback and int/float normalization - Improve readability with inline comment and consistency with Open edX style * test: update entrance exam deletion test to expect default min score - Adjusted `test_entrance_exam_created_updated_and_deleted_successfully` to check for `settings.ENTRANCE_EXAM_MIN_SCORE_PCT` instead of `None` after exam deletion - Added handling for both int and float defaults (`/100` for integer case)
1 parent e46cfa6 commit e5b497c

3 files changed

Lines changed: 19 additions & 5 deletions

File tree

cms/djangoapps/contentstore/tests/test_course_settings.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -501,7 +501,11 @@ def test_entrance_exam_created_updated_and_deleted_successfully(self):
501501
course = modulestore().get_course(self.course.id)
502502
self.assertEqual(response.status_code, 200)
503503
self.assertFalse(course.entrance_exam_enabled)
504-
self.assertEqual(course.entrance_exam_minimum_score_pct, None)
504+
entrance_exam_minimum_score_pct = float(settings.ENTRANCE_EXAM_MIN_SCORE_PCT)
505+
if entrance_exam_minimum_score_pct.is_integer():
506+
entrance_exam_minimum_score_pct = entrance_exam_minimum_score_pct / 100
507+
508+
self.assertEqual(course.entrance_exam_minimum_score_pct, entrance_exam_minimum_score_pct)
505509

506510
self.assertFalse(milestones_helpers.any_unfulfilled_milestones(self.course.id, self.user.id),
507511
msg='The entrance exam should not be required anymore')

cms/djangoapps/contentstore/views/entrance_exam.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ def _delete_entrance_exam(request, course_key):
224224
if course.entrance_exam_id:
225225
metadata = {
226226
'entrance_exam_enabled': False,
227-
'entrance_exam_minimum_score_pct': None,
227+
'entrance_exam_minimum_score_pct': _get_default_entrance_exam_minimum_pct(),
228228
'entrance_exam_id': None,
229229
}
230230
CourseMetadata.update_from_dict(metadata, course, request.user)

openedx/core/djangoapps/content/course_overviews/models.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -266,10 +266,20 @@ def _create_or_update(cls, course): # lint-amnesty, pylint: disable=too-many-st
266266
course_overview.entrance_exam_id = course.entrance_exam_id or ''
267267
# Despite it being a float, the course object defaults to an int. So we will detect that case and update
268268
# it to be a float like everything else.
269-
if isinstance(course.entrance_exam_minimum_score_pct, int):
270-
course_overview.entrance_exam_minimum_score_pct = course.entrance_exam_minimum_score_pct / 100
269+
# Extra handling: entrance_exam_minimum_score_pct can be None (e.g. when exams are disabled in Studio),
270+
# so we fall back to settings.ENTRANCE_EXAM_MIN_SCORE_PCT to prevent CourseOverview save failures.
271+
if course.entrance_exam_minimum_score_pct is None:
272+
entrance_exam_minimum_score_pct = float(settings.ENTRANCE_EXAM_MIN_SCORE_PCT)
271273
else:
272-
course_overview.entrance_exam_minimum_score_pct = course.entrance_exam_minimum_score_pct
274+
entrance_exam_minimum_score_pct = course.entrance_exam_minimum_score_pct
275+
276+
if (
277+
isinstance(entrance_exam_minimum_score_pct, int)
278+
or (isinstance(entrance_exam_minimum_score_pct, float) and entrance_exam_minimum_score_pct.is_integer())
279+
):
280+
entrance_exam_minimum_score_pct = entrance_exam_minimum_score_pct / 100
281+
282+
course_overview.entrance_exam_minimum_score_pct = entrance_exam_minimum_score_pct
273283

274284
course_overview.force_on_flexible_peer_openassessments = course.force_on_flexible_peer_openassessments
275285

0 commit comments

Comments
 (0)