Files
edx-platform/lms/djangoapps/grades/transformer.py
Feanil Patel 8143796b26 docs: update references from setup.py to pyproject.toml
Update documentation, comments, and docstrings throughout the codebase
to reflect the migration from setup.py to pyproject.toml:

- Transformer class docstrings: changed to reference "entry point name
  in the package configuration" for better future-proofing
- Block structure module docs: updated to reference pyproject.toml
- Test file comments: updated entry point references
- Config files (tox.ini, pytest.ini): updated references
- Documentation (extension_points.rst, course apps ADRs): updated to
  reference pyproject.toml with inclusive language for external packages
- Requirements documentation (github.in): updated with inclusive language
- edxmako README: modernized install command to use pip install

Historical ADRs and references to external packages that may still use
setup.py were intentionally left unchanged or updated with inclusive
language acknowledging both pyproject.toml and setup.py.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-03 10:46:16 -05:00

171 lines
6.1 KiB
Python

"""
Grades Transformer
"""
import json
from base64 import b64encode
from functools import reduce as functools_reduce
from hashlib import sha1
from logging import getLogger
from lms.djangoapps.course_blocks.transformers.utils import collect_unioned_set_field, get_field_on_block
from openedx.core.djangoapps.content.block_structure.transformer import BlockStructureTransformer
log = getLogger(__name__)
class GradesTransformer(BlockStructureTransformer):
"""
The GradesTransformer collects grading information and stores it on
the block structure.
No runtime transformations are performed.
The following values are stored as xblock_fields on their respective blocks
in the block structure:
due: (datetime) when the problem is due.
format: (string) what type of problem it is
graded: (boolean)
has_score: (boolean)
weight: (numeric)
show_correctness: (string) when to show grades (one of 'always', 'past_due', 'never')
Additionally, the following value is calculated and stored as a
transformer_block_field for each block:
max_score: (numeric)
"""
WRITE_VERSION = 4
READ_VERSION = 4
FIELDS_TO_COLLECT = [
'due',
'format',
'graded',
'has_score',
'weight',
'course_version',
'subtree_edited_on',
'show_correctness',
]
EXPLICIT_GRADED_FIELD_NAME = 'explicit_graded'
@classmethod
def name(cls):
"""
Unique identifier for the transformer's class.
This must match the entry point name in the package configuration.
"""
return 'grades'
@classmethod
def collect(cls, block_structure):
"""
Collects any information that's necessary to execute this
transformer's transform method.
"""
block_structure.request_xblock_fields(*cls.FIELDS_TO_COLLECT)
cls._collect_max_scores(block_structure)
collect_unioned_set_field(
block_structure=block_structure,
transformer=cls,
merged_field_name='subsections',
filter_by=lambda block_key: block_key.block_type == 'sequential',
)
cls._collect_explicit_graded(block_structure)
cls._collect_grading_policy_hash(block_structure)
def transform(self, block_structure, usage_context): # lint-amnesty, pylint: disable=arguments-differ
"""
Perform no transformations.
"""
pass # lint-amnesty, pylint: disable=unnecessary-pass
@classmethod
def grading_policy_hash(cls, course):
"""
Returns the grading policy hash for the given course.
"""
ordered_policy = json.dumps(
course.grading_policy,
separators=(',', ':'), # Remove spaces from separators for more compact representation
sort_keys=True,
)
return b64encode(sha1(ordered_policy.encode('utf-8')).digest()).decode('utf-8')
@classmethod
def _collect_explicit_graded(cls, block_structure):
"""
Collect the 'explicit_graded' field for every block.
"""
def _set_field(block_key, field_value):
"""
Sets the explicit graded field to the given value for the
given block.
"""
block_structure.set_transformer_block_field(block_key, cls, cls.EXPLICIT_GRADED_FIELD_NAME, field_value)
def _get_field(block_key):
"""
Gets the explicit graded field to the given value for the
given block.
"""
return block_structure.get_transformer_block_field(block_key, cls, cls.EXPLICIT_GRADED_FIELD_NAME)
block_types_to_ignore = {'course', 'chapter', 'sequential'}
for block_key in block_structure.topological_traversal():
if block_key.block_type in block_types_to_ignore:
_set_field(block_key, None)
else:
explicit_field_on_block = get_field_on_block(block_structure.get_xblock(block_key), 'graded')
if explicit_field_on_block is not None:
_set_field(block_key, explicit_field_on_block)
else:
values_from_parents = [
_get_field(parent)
for parent in block_structure.get_parents(block_key)
if parent.block_type not in block_types_to_ignore
]
non_null_values_from_parents = [value for value in values_from_parents if not None]
explicit_from_parents = functools_reduce(lambda x, y: x or y, non_null_values_from_parents, None)
_set_field(block_key, explicit_from_parents)
@classmethod
def _collect_max_scores(cls, block_structure):
"""
Collect the `max_score` for every block in the provided `block_structure`.
"""
for block_locator in block_structure.post_order_traversal():
block = block_structure.get_xblock(block_locator)
if getattr(block, 'has_score', False):
cls._collect_max_score(block_structure, block)
@classmethod
def _collect_max_score(cls, block_structure, block):
"""
Collect the `max_score` from the given block, storing it as a
`transformer_block_field` associated with the `GradesTransformer`.
"""
max_score = block.max_score()
block_structure.set_transformer_block_field(block.location, cls, 'max_score', max_score)
if max_score is None:
log.warning(f"GradesTransformer: max_score is None for {block.location}")
@classmethod
def _collect_grading_policy_hash(cls, block_structure):
"""
Collect a hash of the course's grading policy, storing it as a
`transformer_block_field` associated with the `GradesTransformer`.
"""
course_location = block_structure.root_block_usage_key
course_block = block_structure.get_xblock(course_location)
block_structure.set_transformer_block_field(
course_block.location,
cls,
"grading_policy_hash",
cls.grading_policy_hash(course_block),
)