Files
edx-platform/openedx/features/personalized_learner_schedules/call_to_action.py
Awais Qureshi ace90d5345 BOM-2442
pyupgrade in leaner-profile, lti-course-tab, personalized-learner
2021-03-11 14:57:20 +05:00

161 lines
7.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Creates Call to Actions for resetting a Personalized Learner Schedule for use inside of Courseware.
"""
import logging
from crum import get_current_request
from django.conf import settings
from django.urls import reverse
from django.utils.translation import ngettext, gettext as _
from common.lib.xmodule.xmodule.util.misc import is_xblock_an_assignment
from openedx.core.lib.mobile_utils import is_request_from_mobile_app
from openedx.features.course_experience.url_helpers import is_request_from_learning_mfe
from openedx.features.course_experience.utils import dates_banner_should_display
log = logging.getLogger(__name__)
class PersonalizedLearnerScheduleCallToAction:
"""
Creates Call to Actions for resetting a Personalized Learner Schedule for use inside of Courseware.
"""
CAPA_SUBMIT_DISABLED = 'capa_submit_disabled'
VERTICAL_BANNER = 'vertical_banner'
past_due_class_warnings = set()
def get_ctas(self, xblock, category, completed):
"""
Return the calls to action associated with the specified category for the given xblock.
Look at CallToActionService docstring to see what will be returned.
"""
ctas = []
request = get_current_request()
course_key = xblock.scope_ids.usage_id.context_key
missed_deadlines, missed_gated_content = dates_banner_should_display(course_key, request.user)
# Not showing in the missed_gated_content case because those learners are not eligible
# to shift due dates.
if not missed_deadlines or missed_gated_content:
return []
# Some checks to disable PLS calls to action until these environments (mobile and MFE) support them natively
if request and is_request_from_mobile_app(request):
return []
is_learning_mfe = request and is_request_from_learning_mfe(request)
if category == self.CAPA_SUBMIT_DISABLED:
# xblock is a capa problem, and the submit button is disabled. Check if it's because of a personalized
# schedule due date being missed, and if so, we can offer to shift it.
if self._is_block_shiftable(xblock, category):
ctas.append(self._make_reset_deadlines_cta(xblock, category, is_learning_mfe))
elif category == self.VERTICAL_BANNER and not completed:
# xblock is a vertical, so we'll check all the problems inside it. If there are any that will show a
# a "shift dates" CTA under CAPA_SUBMIT_DISABLED, then we'll also show the same CTA as a vertical banner.
if any(self._is_block_shiftable(item, category) for item in xblock.get_display_items()):
ctas.append(self._make_reset_deadlines_cta(xblock, category, is_learning_mfe))
return ctas
@classmethod
def _is_block_shiftable(cls, xblock, category):
"""
Test if the xblock would be solvable if we were to shift dates.
Only xblocks with an is_past_due method (e.g. capa and LTI) will be considered possibly shiftable.
"""
if not hasattr(xblock, 'is_past_due'):
return False
if hasattr(xblock, 'attempts') and hasattr(xblock, 'max_attempts'):
can_attempt = xblock.max_attempts is None or xblock.attempts < xblock.max_attempts
else:
can_attempt = True
if callable(xblock.is_past_due):
is_past_due = xblock.is_past_due()
else:
PersonalizedLearnerScheduleCallToAction._log_past_due_warning(type(xblock).__name__)
is_past_due = xblock.is_past_due
can_shift = xblock.self_paced and can_attempt and is_past_due
# Note: we will still show the CTA at the xblock level (next to the submit button) regardless
# of if the xblock is an assignment (meaning graded *and* scored)
if category == cls.VERTICAL_BANNER:
can_shift = can_shift and is_xblock_an_assignment(xblock)
return can_shift
@staticmethod
def _log_past_due_warning(name):
"""
Logs out if an xblock has is_past_due defined as a property
(since we want to move to using it as a function everywhere)
"""
if name in PersonalizedLearnerScheduleCallToAction.past_due_class_warnings:
return
log.warning('PersonalizedLearnerScheduleCallToAction has encountered an xblock that defines is_past_due '
'as a property. This is supported for now, but may not be in the future. Please change '
'%s.is_past_due into a method.', name)
PersonalizedLearnerScheduleCallToAction.past_due_class_warnings.add(name)
@classmethod
def _make_reset_deadlines_cta(cls, xblock, category, is_learning_mfe=False):
"""
Constructs a call to action object containing the necessary information for the view
"""
from lms.urls import RESET_COURSE_DEADLINES_NAME
course_key = xblock.scope_ids.usage_id.context_key
cta_data = {
'link': reverse(RESET_COURSE_DEADLINES_NAME),
'link_name': _('Shift due dates'),
'form_values': {
'course_id': course_key,
},
'description': _('To participate in this assignment, the suggested schedule for your course needs '
'updating. Dont worry, well shift all the due dates for you and you wont lose '
'any of your progress.'),
}
has_attempts = hasattr(xblock, 'attempts') and hasattr(xblock, 'max_attempts')
if category == cls.CAPA_SUBMIT_DISABLED and has_attempts and xblock.attempts:
if xblock.max_attempts:
cta_data['link_name'] = ngettext('Try again ({attempts} attempt remaining)',
'Try again ({attempts} attempts remaining)',
(xblock.max_attempts - xblock.attempts)).format(
attempts=(xblock.max_attempts - xblock.attempts)
)
cta_data['description'] = (_('You have used {attempts} of {max_attempts} attempts for this '
'problem.').format(
attempts=xblock.attempts, max_attempts=xblock.max_attempts
) + ' ' + cta_data['description'])
else:
cta_data['link_name'] = _('Try again (unlimited attempts)')
cta_data['description'] = _('You have used {attempts} of unlimited attempts for this '
'problem.').format(attempts=xblock.attempts) + ' ' + cta_data['description']
if is_learning_mfe:
cta_data['event_data'] = {
'event_name': 'post_event',
'post_data': {
'body_params': {
'course_id': str(course_key),
},
'url': '{}{}'.format(settings.LMS_ROOT_URL, reverse('course-experience-reset-course-deadlines')),
},
'research_event_data': {
'block_id': str(xblock.location),
'location': f'{xblock.category}-view',
},
}
return cta_data