This commit implements a comprehensive solution for test score integration in the enhancement system along with improvements to the score rendering mechanism. Key changes include: - Add event handler for rendering blocks with edx-submissions scores - Implement event-based mechanism to render XBlocks with scoring data - Create signal handlers in handlers.py to process external grader scores - Develop specialized XBlock loader for rendering without HTTP requests - Add queue_key propagation across the submission pipeline - Register submission URLs in LMS routing configuration - Add complete docstrings to score render module for better code maintainability - Add ADR for XBlock rendering with external grader integration - Add openedx-events fork branch as a dependency in testing.in - Upgrade edx submission dependency These changes support the migration from traditional XQueue callback HTTP requests to a more robust event-based architecture, improving performance and reliability when processing submission scores. The included ADR documents the architectural decision and implementation approach for this significant improvement to the external grading workflow.
91 lines
2.7 KiB
Python
91 lines
2.7 KiB
Python
"""
|
|
Score rendering when submission is evaluated for external grader and has been saved successfully
|
|
"""
|
|
import logging
|
|
from functools import partial
|
|
|
|
from django.http import Http404
|
|
from edx_when.field_data import DateLookupFieldData
|
|
from opaque_keys.edx.keys import CourseKey, UsageKey
|
|
from xblock.runtime import KvsFieldData
|
|
|
|
from common.djangoapps.student.models import AnonymousUserId
|
|
from lms.djangoapps.courseware.block_render import prepare_runtime_for_user
|
|
from lms.djangoapps.courseware.field_overrides import OverrideFieldData
|
|
from lms.djangoapps.courseware.model_data import DjangoKeyValueStore, FieldDataCache
|
|
from lms.djangoapps.lms_xblock.field_data import LmsFieldData
|
|
from xmodule.modulestore.django import modulestore
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
def load_xblock_for_external_grader(
|
|
user_id: str,
|
|
course_key: CourseKey,
|
|
usage_key: UsageKey,
|
|
course=None,
|
|
):
|
|
"""
|
|
Load a single XBlock for external grading without user access checks.
|
|
"""
|
|
|
|
user = AnonymousUserId.objects.get(anonymous_user_id=user_id).user
|
|
|
|
# pylint: disable=broad-exception-caught
|
|
try:
|
|
block = modulestore().get_item(usage_key)
|
|
except Exception as e:
|
|
log.exception(f"Could not find block {usage_key} in modulestore: {e}")
|
|
raise Http404(f"Module {usage_key} was not found") from e
|
|
|
|
field_data_cache = FieldDataCache.cache_for_block_descendents(
|
|
course_key, user, block, depth=0
|
|
)
|
|
|
|
student_kvs = DjangoKeyValueStore(field_data_cache)
|
|
student_data = KvsFieldData(student_kvs)
|
|
|
|
instance = get_block_for_descriptor_without_access_check(
|
|
user=user,
|
|
block=block,
|
|
student_data=student_data,
|
|
course_key=course_key,
|
|
course=course
|
|
)
|
|
|
|
if instance is None:
|
|
msg = f"Could not bind XBlock instance for usage key: {usage_key}"
|
|
log.error(msg)
|
|
raise Http404(msg)
|
|
|
|
return instance
|
|
|
|
|
|
def get_block_for_descriptor_without_access_check(user, block, student_data, course_key, course=None):
|
|
"""
|
|
Modified version of get_block_for_descriptor that skips access checks for system operations.
|
|
"""
|
|
|
|
prepare_runtime_for_user(
|
|
user=user,
|
|
student_data=student_data,
|
|
runtime=block.runtime,
|
|
course_id=course_key,
|
|
course=course,
|
|
track_function=lambda event_type, event: None,
|
|
request_token="external-grader-token",
|
|
position=None,
|
|
wrap_xblock_display=True,
|
|
)
|
|
|
|
block.bind_for_student(
|
|
user.id,
|
|
[
|
|
partial(DateLookupFieldData, course_id=course_key, user=user),
|
|
partial(OverrideFieldData.wrap, user, course),
|
|
partial(LmsFieldData, student_data=student_data),
|
|
],
|
|
)
|
|
|
|
return block
|