Skip to content
Snippets Groups Projects
  • Shimul Chowdhury's avatar
    8b72fb9a
    [BD-24] [TNL-7661] [BB-3172] LTI Improvements - Use declarative grading model... · 8b72fb9a
    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
    [BD-24] [TNL-7661] [BB-3172] LTI Improvements - Use declarative grading model...
    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
    )