-
Shimul Chowdhury authored
[BD-24] [TNL-7661] [BB-3172] LTI Improvements - Use declarative grading model on XBlock launch (#116) * create default LineItem, WIP grade save * add score to django admin * WIP: find user and save grade to xblock * boolean pragramatic grade interaction flag and optional params in enable_ags method * Submit grades using grade signals * lineitem urls should be optional * lineitem is now readonly in declarative method * test grade_submit called properly * quality issue * raise LTIError * moved listener to signal.py, refactored models.py, added due and start date, updated tests. * use load_block_as_anonymous_user and remove load_block, refactor tests * refactor test to fix quality issue * make lineitems_url required * refactor tests, accept_grades_past_due on check * test accept_grades_past_due * add comma to last items * refactor get_lti_ags_lineitems_url * make sure crum returns user and not None * nitpicks & use maximum score when given score is larger than maximum * fix docstring of load_block_as_anonymous_user
Shimul Chowdhury authored[BD-24] [TNL-7661] [BB-3172] LTI Improvements - Use declarative grading model on XBlock launch (#116) * create default LineItem, WIP grade save * add score to django admin * WIP: find user and save grade to xblock * boolean pragramatic grade interaction flag and optional params in enable_ags method * Submit grades using grade signals * lineitem urls should be optional * lineitem is now readonly in declarative method * test grade_submit called properly * quality issue * raise LTIError * moved listener to signal.py, refactored models.py, added due and start date, updated tests. * use load_block_as_anonymous_user and remove load_block, refactor tests * refactor test to fix quality issue * make lineitems_url required * refactor tests, accept_grades_past_due on check * test accept_grades_past_due * add comma to last items * refactor get_lti_ags_lineitems_url * make sure crum returns user and not None * nitpicks & use maximum score when given score is larger than maximum * fix docstring of load_block_as_anonymous_user
compat.py 3.25 KiB
"""
Compatibility layer to isolate core-platform method calls from implementation.
"""
from django.core.exceptions import ValidationError
from lti_consumer.exceptions import LtiError
def run_xblock_handler(*args, **kwargs):
"""
Import and run `handle_xblock_callback` from LMS
"""
# pylint: disable=import-error,import-outside-toplevel
from lms.djangoapps.courseware.module_render import handle_xblock_callback
return handle_xblock_callback(*args, **kwargs)
def run_xblock_handler_noauth(*args, **kwargs):
"""
Import and run `handle_xblock_callback_noauth` from LMS
"""
# pylint: disable=import-error,import-outside-toplevel
from lms.djangoapps.courseware.module_render import handle_xblock_callback_noauth
return handle_xblock_callback_noauth(*args, **kwargs)
def load_block_as_anonymous_user(location):
"""
Load a block as anonymous user.
This uses a few internal courseware methods to retrieve the descriptor
and bind an AnonymousUser to it, in a similar fashion as a `noauth` XBlock
handler.
"""
# pylint: disable=import-error,import-outside-toplevel
from crum import impersonate
from django.contrib.auth.models import AnonymousUser
from xmodule.modulestore.django import modulestore
from lms.djangoapps.courseware.module_render import get_module_for_descriptor_internal
# Retrieve descriptor from modulestore
descriptor = modulestore().get_item(location)
# ensure `crum.get_current_user` returns AnonymousUser. It returns None when outside
# of request scope which causes error during block loading.
user = AnonymousUser()
with impersonate(user):
# Load block, attaching it to AnonymousUser
get_module_for_descriptor_internal(
user=user,
descriptor=descriptor,
student_data=None,
course_id=location.course_key,
track_function=None,
xqueue_callback_url_prefix="",
request_token="",
)
return descriptor
def get_user_from_external_user_id(external_user_id):
"""
Import ExternalId model and find user by external_user_id
"""
# pylint: disable=import-error,import-outside-toplevel
from openedx.core.djangoapps.external_user_ids.models import ExternalId
try:
external_id = ExternalId.objects.get(
external_user_id=external_user_id,
external_id_type__name='lti'
)
return external_id.user
except ExternalId.DoesNotExist as exception:
raise LtiError('Invalid User') from exception
except ValidationError as exception:
raise LtiError('Invalid userID') from exception
def publish_grade(block, user, score, possible, only_if_higher=False, score_deleted=None, comment=None):
"""
Import grades signals and publishes score by triggering SCORE_PUBLISHED signal.
"""
# pylint: disable=import-error,import-outside-toplevel
from lms.djangoapps.grades.api import signals as grades_signals
# publish score
grades_signals.SCORE_PUBLISHED.send(
sender=None,
block=block,
user=user,
raw_earned=score,
raw_possible=possible,
only_if_higher=only_if_higher,
score_deleted=score_deleted,
grader_response=comment
)