diff --git a/lms/djangoapps/course_home_api/progress/api.py b/lms/djangoapps/course_home_api/progress/api.py index 53895d83232c..6f219be9f269 100644 --- a/lms/djangoapps/course_home_api/progress/api.py +++ b/lms/djangoapps/course_home_api/progress/api.py @@ -40,7 +40,7 @@ class _AssignmentBucket: """ assignment_type: str num_total: int - last_grade_publish_date: datetime + last_grade_publish_date: datetime | None scores: list[float] = field(default_factory=list) visibilities: list[bool | None] = field(default_factory=list) included: list[bool | None] = field(default_factory=list) @@ -52,7 +52,7 @@ def with_placeholders(cls, assignment_type: str, num_total: int, now: datetime): return cls( assignment_type=assignment_type, num_total=num_total, - last_grade_publish_date=now, + last_grade_publish_date=None, scores=[0] * num_total, visibilities=[None] * num_total, included=[None] * num_total, @@ -168,8 +168,13 @@ def collect(self): bucket.add_subsection(score, is_visible, is_included) visibilities_with_due_dates = [ShowCorrectness.PAST_DUE, ShowCorrectness.NEVER_BUT_INCLUDE_GRADE] if subsection_grade.show_correctness in visibilities_with_due_dates: - if subsection_grade.due and subsection_grade.due > bucket.last_grade_publish_date: - bucket.last_grade_publish_date = subsection_grade.due + if subsection_grade.due: + should_update = ( + bucket.last_grade_publish_date is None or + subsection_grade.due > bucket.last_grade_publish_date + ) + if should_update: + bucket.last_grade_publish_date = subsection_grade.due def build_results(self) -> dict: """Apply drops, compute averages, and return aggregated results and total grade.""" diff --git a/lms/djangoapps/course_home_api/progress/tests/test_api.py b/lms/djangoapps/course_home_api/progress/tests/test_api.py index 963dd419a2d2..c91b226a0e9d 100644 --- a/lms/djangoapps/course_home_api/progress/tests/test_api.py +++ b/lms/djangoapps/course_home_api/progress/tests/test_api.py @@ -15,7 +15,7 @@ from types import SimpleNamespace -def _make_subsection(fmt, earned, possible, show_corr, *, due_delta_days=None): +def _make_subsection(fmt, earned, possible, show_corr, *, due_delta_days=None, is_included=True): """Build a lightweight subsection object for testing aggregation scenarios.""" graded_total = SimpleNamespace(earned=earned, possible=possible) due = None @@ -27,7 +27,7 @@ def _make_subsection(fmt, earned, possible, show_corr, *, due_delta_days=None): graded_total=graded_total, show_correctness=show_corr, due=due, - show_grades=lambda staff: True, + show_grades=lambda staff: is_included, ) @@ -63,10 +63,10 @@ def _make_subsection(fmt, earned, possible, show_corr, *, due_delta_days=None): 'past_due_mixed_visibility', {'type': 'Lab', 'weight': 1.0, 'drop_count': 0, 'min_count': 2, 'short_label': 'LB'}, [ - _make_subsection('Lab', 0.8, 1, ShowCorrectness.PAST_DUE, due_delta_days=-1), - _make_subsection('Lab', 0.2, 1, ShowCorrectness.PAST_DUE, due_delta_days=+3), + _make_subsection('Lab', 0.8, 1, ShowCorrectness.PAST_DUE, due_delta_days=-1, is_included=True), + _make_subsection('Lab', 0.2, 1, ShowCorrectness.PAST_DUE, due_delta_days=+3, is_included=True), ], - {'avg': 0.4, 'weighted': 0.4, 'hidden': 'some', 'final': 0.5}, + {'avg': 0.4, 'weighted': 0.4, 'hidden': 'some', 'final': 0.5, 'last_grade_publish_date_days': 3}, ), ( 'drop_lowest_keeps_high_scores', @@ -79,6 +79,14 @@ def _make_subsection(fmt, earned, possible, show_corr, *, due_delta_days=None): ], {'avg': 1.0, 'weighted': 1.0, 'hidden': 'none', 'final': 1.0}, ), + ( + 'unreleased_with_future_due_date', + {'type': 'Midterm', 'weight': 1.0, 'drop_count': 0, 'min_count': 1, 'short_label': 'MT'}, + [ + _make_subsection('Midterm', 0.5, 1, ShowCorrectness.PAST_DUE, due_delta_days=7, is_included=False), + ], + {'avg': 0.0, 'weighted': 0.0, 'hidden': 'all', 'final': 0.0, 'last_grade_publish_date_days': 7}, + ), ] @@ -180,3 +188,6 @@ def test_aggregate_assignment_type_grade_summary_scenarios(self): assert row['weighted_grade'] == expected['weighted'] assert row['has_hidden_contribution'] == expected['hidden'] assert row['num_droppable'] == policy['drop_count'] + assert (row['last_grade_publish_date'] is not None) == ( + 'last_grade_publish_date_days' in expected + )