From db93aa9f43e835139041b6898a173949686b3be7 Mon Sep 17 00:00:00 2001 From: Muhammad Tayayb Tahir Qureshi Date: Wed, 13 Nov 2024 12:05:34 +0500 Subject: [PATCH] refactor: Remove dependencies from edx-platform in extracted LTI block - Defined constants for anonymous user ID and user role. - Replaced HTML/Text helpers with markupsafe. --- xblocks_contrib/lti/lti.py | 55 +++++++++++++++---------------- xblocks_contrib/lti/lti_2_util.py | 25 ++++++++++++-- 2 files changed, 49 insertions(+), 31 deletions(-) diff --git a/xblocks_contrib/lti/lti.py b/xblocks_contrib/lti/lti.py index 1295c2f..3ea3bb6 100644 --- a/xblocks_contrib/lti/lti.py +++ b/xblocks_contrib/lti/lti.py @@ -59,6 +59,7 @@ import datetime import hashlib import logging +import markupsafe import textwrap from xml.sax.saxutils import escape from unittest import mock @@ -81,13 +82,12 @@ from xblockutils.resources import ResourceLoader from xblockutils.studio_editable import StudioEditableXBlockMixin -from openedx.core.djangolib.markup import HTML, Text from .lti_2_util import LTI20BlockMixin, LTIError -from common.djangoapps.xblock_django.constants import ( - ATTR_KEY_ANONYMOUS_USER_ID, - ATTR_KEY_USER_ROLE, -) +# The anonymous user ID for the user in the course. +ATTR_KEY_ANONYMOUS_USER_ID = 'edx-platform.anonymous_user_id' +# The user's role in the course ('staff', 'instructor', or 'student'). +ATTR_KEY_USER_ROLE = 'edx-platform.user_role' resource_loader = ResourceLoader(__name__) @@ -228,15 +228,15 @@ class LTIBlock( lti_id = String( display_name=_("LTI ID"), - help=Text(_( + help=markupsafe.escape(_( "Enter the LTI ID for the external LTI provider. " "This value must be the same LTI ID that you entered in the " "LTI Passports setting on the Advanced Settings page." "{break_tag}See {docs_anchor_open}the edX LTI documentation{anchor_close} for more details on this setting." )).format( - break_tag=HTML(BREAK_TAG), - docs_anchor_open=HTML(DOCS_ANCHOR_TAG_OPEN), - anchor_close=HTML("") + break_tag=markupsafe.Markup(BREAK_TAG), + docs_anchor_open=markupsafe.Markup(DOCS_ANCHOR_TAG_OPEN), + anchor_close=markupsafe.Markup("") ), default='', scope=Scope.settings @@ -244,31 +244,31 @@ class LTIBlock( launch_url = String( display_name=_("LTI URL"), - help=Text(_( + help=markupsafe.escape(_( "Enter the URL of the external tool that this component launches. " "This setting is only used when Hide External Tool is set to False." "{break_tag}See {docs_anchor_open}the edX LTI documentation{anchor_close} for more details on this setting." )).format( - break_tag=HTML(BREAK_TAG), - docs_anchor_open=HTML(DOCS_ANCHOR_TAG_OPEN), - anchor_close=HTML("") + break_tag=markupsafe.Markup(BREAK_TAG), + docs_anchor_open=markupsafe.Markup(DOCS_ANCHOR_TAG_OPEN), + anchor_close=markupsafe.Markup("") ), default='http://www.example.com', scope=Scope.settings) - + custom_parameters = List( display_name=_("Custom Parameters"), - help=Text(_( + help=markupsafe.escape(_( "Add the key/value pair for any custom parameters, such as the page your e-book should open to or " "the background color for this component." "{break_tag}See {docs_anchor_open}the edX LTI documentation{anchor_close} for more details on this setting." )).format( - break_tag=HTML(BREAK_TAG), - docs_anchor_open=HTML(DOCS_ANCHOR_TAG_OPEN), - anchor_close=HTML("") + break_tag=markupsafe.Markup(BREAK_TAG), + docs_anchor_open=markupsafe.Markup(DOCS_ANCHOR_TAG_OPEN), + anchor_close=markupsafe.Markup("") ), scope=Scope.settings) - + open_in_a_new_page = Boolean( display_name=_("Open in New Page"), help=_( @@ -333,7 +333,7 @@ class LTIBlock( default=False, scope=Scope.settings ) - + ask_to_send_email = Boolean( display_name=_("Request user's email"), # Translators: This is used to request the user's email for a third party service. @@ -367,7 +367,7 @@ class LTIBlock( default=True, scope=Scope.settings ) - + editable_fields = ( "accept_grades_past_due", "button_text", "custom_parameters", "display_name", "hide_launch", "description", "lti_id", "launch_url", "open_in_a_new_page", @@ -959,12 +959,12 @@ def verify_oauth_body_sign(self, request, content_type='application/x-www-form-u if (not signature.verify_hmac_sha1(mock_request_lti_1, client_secret) and not signature.verify_hmac_sha1(mock_request_lti_2, client_secret)): - log.error("OAuth signature verification failed, for " - "headers:{} url:{} method:{}".format( - oauth_headers, - self.get_outcome_service_url(), - str(request.method) - )) + log.error( + "OAuth signature verification failed, for " + "headers:{} url:{} method:{}".format( + oauth_headers, self.get_outcome_service_url(), str(request.method) + ) + ) raise LTIError("OAuth signature verification has failed.") def get_client_key_secret(self): @@ -996,4 +996,3 @@ def is_past_due(self): else: close_date = due_date return close_date is not None and datetime.datetime.now(UTC) > close_date - diff --git a/xblocks_contrib/lti/lti_2_util.py b/xblocks_contrib/lti/lti_2_util.py index 11eba5e..f7d05a9 100644 --- a/xblocks_contrib/lti/lti_2_util.py +++ b/xblocks_contrib/lti/lti_2_util.py @@ -8,6 +8,7 @@ import hashlib import json import logging +import math import re from unittest import mock from urllib import parse @@ -17,8 +18,6 @@ from webob import Response from xblock.core import XBlock -from openedx.core.lib.grade_utils import round_away_from_zero - log = logging.getLogger(__name__) LTI_2_0_REST_SUFFIX_PARSER = re.compile(r"^user/(?P\w+)", re.UNICODE) @@ -154,6 +153,26 @@ def parse_lti_2_0_handler_suffix(self, suffix): log.info(f"[LTI]: {msg}") raise LTIError(msg) + def _round_away_from_zero(number, digits=0): + """ + Round numbers using the 'away from zero' strategy as opposed to the + 'Banker's rounding strategy.' The strategy refers to how we round when + a number is half way between two numbers. eg. 0.5, 1.5, etc. In python 3 + numbers round towards even. So 0.5 would round to 0 but 1.5 would round to 2. + + See here for more on floating point rounding strategies: + https://en.wikipedia.org/wiki/IEEE_754#Rounding_rules + + We want to continue to round away from zero so that student grades remain + consistent and don't suddenly change. + """ + p = 10.0 ** digits + + if number >= 0: + return float(math.floor((number * p) + 0.5)) / p + else: + return float(math.ceil((number * p) - 0.5)) / p + def _lti_2_0_result_get_handler(self, request, real_user): """ Helper request handler for GET requests to LTI 2.0 result endpoint @@ -176,7 +195,7 @@ def _lti_2_0_result_get_handler(self, request, real_user): return Response(json.dumps(base_json_obj).encode('utf-8'), content_type=LTI_2_0_JSON_CONTENT_TYPE) # Fall through to returning grade and comment - base_json_obj['resultScore'] = round_away_from_zero(self.module_score, 2) + base_json_obj['resultScore'] = self._round_away_from_zero(self.module_score, 2) base_json_obj['comment'] = self.score_comment return Response(json.dumps(base_json_obj).encode('utf-8'), content_type=LTI_2_0_JSON_CONTENT_TYPE)