Files
Serhiii Nanai 0c3d709d63 feat: add assignment color codes to Progress API (#37438)
* feat: change list of color codes

* fix: add missing color

* style: rename attribute for assignment color list

* docs: add explanation for current implementation
2025-12-02 13:52:36 -05:00

188 lines
6.9 KiB
Python

"""
Progress Tab Serializers
"""
from datetime import datetime
from rest_framework import serializers
from rest_framework.reverse import reverse
from pytz import UTC
from lms.djangoapps.course_home_api.serializers import ReadOnlySerializer, VerifiedModeSerializer
class CourseGradeSerializer(ReadOnlySerializer):
"""
Serializer for course grade
"""
letter_grade = serializers.CharField()
percent = serializers.FloatField()
is_passing = serializers.BooleanField(source='passed')
class SubsectionScoresSerializer(ReadOnlySerializer):
"""
Serializer for subsections in section_scores
"""
assignment_type = serializers.CharField(source='format')
block_key = serializers.SerializerMethodField()
display_name = serializers.CharField()
due = serializers.DateTimeField(allow_null=True)
has_graded_assignment = serializers.BooleanField(source='graded')
override = serializers.SerializerMethodField()
learner_has_access = serializers.SerializerMethodField()
num_points_earned = serializers.FloatField(source='graded_total.earned')
num_points_possible = serializers.FloatField(source='graded_total.possible')
percent_graded = serializers.FloatField()
problem_scores = serializers.SerializerMethodField()
show_correctness = serializers.CharField()
show_grades = serializers.SerializerMethodField()
url = serializers.SerializerMethodField()
def get_override(self, subsection):
"""Proctoring or grading score override"""
if subsection.override is None:
return None
else:
return {
"system": subsection.override.system,
"reason": subsection.override.override_reason,
}
def get_block_key(self, subsection):
return str(subsection.location)
def get_problem_scores(self, subsection):
"""Problem scores for this subsection"""
problem_scores = [
{
'earned': score.earned,
'possible': score.possible,
}
for score in subsection.problem_scores.values()
]
return problem_scores
def get_url(self, subsection):
"""
Returns the URL for the subsection while taking into account if the course team has
marked the subsection's visibility as hide after due.
"""
hide_url_date = subsection.end if subsection.self_paced else subsection.due
if (not self.context['staff_access'] and subsection.hide_after_due and hide_url_date
and datetime.now(UTC) > hide_url_date):
return None
relative_path = reverse('jump_to', args=[self.context['course_key'], subsection.location])
request = self.context['request']
return request.build_absolute_uri(relative_path)
def get_show_grades(self, subsection):
return subsection.show_grades(self.context['staff_access'])
def get_learner_has_access(self, subsection):
course_blocks = self.context['course_blocks']
return not course_blocks.get_xblock_field(subsection.location, 'contains_gated_content', False)
class SectionScoresSerializer(ReadOnlySerializer):
"""
Serializer for sections in section_scores
"""
display_name = serializers.CharField()
subsections = SubsectionScoresSerializer(source='sections', many=True)
class GradingPolicySerializer(ReadOnlySerializer):
"""
Serializer for grading policy
"""
# This implementation of the color coding feature is temporary and for testing purposes only.
# All CSS should be handled by design tokens, this is not implemented yet in Paragon.
# The backend should only work with labels, and the final design includes dynamic Label assignment per course
# (not CSS codes but color Labels), platform-level configuration, etc.
# See discussions in the associated PR for further details: https://github.com/openedx/edx-platform/pull/37438
ASSIGNMENT_COLORS = [
"#D24242", # Crimson Red
"#7B9645", # Olive Green
"#5A5AD8", # Periwinkle Blue
"#B0842C", # Amber Gold
"#2E90C2", # Ocean Blue
"#D13F88", # Magenta Pink
"#36A17D", # Jade Green
"#AE5AD8", # Lavender Purple
"#3BA03B", # Forest Green
]
assignment_policies = serializers.SerializerMethodField()
grade_range = serializers.DictField(source='GRADE_CUTOFFS')
assignment_colors = serializers.SerializerMethodField()
def get_assignment_policies(self, grading_policy):
return [{
'num_droppable': assignment_policy['drop_count'],
'num_total': float(assignment_policy['min_count']),
'short_label': assignment_policy.get('short_label', ''),
'type': assignment_policy['type'],
'weight': assignment_policy['weight'],
} for assignment_policy in grading_policy['GRADER']]
def get_assignment_colors(self, obj):
return self.ASSIGNMENT_COLORS
class CertificateDataSerializer(ReadOnlySerializer):
"""
Serializer for certificate data
"""
cert_status = serializers.CharField()
cert_web_view_url = serializers.CharField()
download_url = serializers.CharField()
certificate_available_date = serializers.DateTimeField()
class VerificationDataSerializer(ReadOnlySerializer):
"""
Serializer for verification data object
"""
link = serializers.URLField()
status = serializers.CharField()
status_date = serializers.DateTimeField()
class AssignmentTypeScoresSerializer(ReadOnlySerializer):
"""
Serializer for aggregated scores per assignment type.
"""
type = serializers.CharField()
weight = serializers.FloatField()
average_grade = serializers.FloatField()
weighted_grade = serializers.FloatField()
last_grade_publish_date = serializers.DateTimeField()
has_hidden_contribution = serializers.CharField()
short_label = serializers.CharField()
num_droppable = serializers.IntegerField()
class ProgressTabSerializer(VerifiedModeSerializer):
"""
Serializer for progress tab
"""
access_expiration = serializers.DictField()
certificate_data = CertificateDataSerializer()
completion_summary = serializers.DictField()
course_grade = CourseGradeSerializer()
credit_course_requirements = serializers.DictField()
end = serializers.DateTimeField()
enrollment_mode = serializers.CharField()
grading_policy = GradingPolicySerializer()
has_scheduled_content = serializers.BooleanField()
section_scores = SectionScoresSerializer(many=True)
studio_url = serializers.CharField()
username = serializers.CharField()
user_has_passing_grade = serializers.BooleanField()
verification_data = VerificationDataSerializer()
disable_progress_graph = serializers.BooleanField()
assignment_type_grade_summary = AssignmentTypeScoresSerializer(many=True)
final_grades = serializers.FloatField()