diff --git a/common/lib/xmodule/xmodule/css/capa/display.scss b/common/lib/xmodule/xmodule/css/capa/display.scss index e5a0a373a56a3b108a371d6cb1f6b1cd78bef7ce..3270c6bcde37d4de7d1e95d22c3a870b5cdc21b1 100644 --- a/common/lib/xmodule/xmodule/css/capa/display.scss +++ b/common/lib/xmodule/xmodule/css/capa/display.scss @@ -1019,8 +1019,11 @@ div.problem .action { .submit-cta-description { color: $blue; font-size: small; + padding-right: $baseline / 2; } .submit-cta-link-button { + border: none; + padding-right: $baseline / 4; text-decoration: underline; text-transform: none; } @@ -1035,6 +1038,10 @@ div.problem .action { font-size: $medium-font-size; -webkit-font-smoothing: antialiased; vertical-align: middle; + + &.cta-enabled { + margin-top: 0; + } } } diff --git a/common/lib/xmodule/xmodule/vertical_block.py b/common/lib/xmodule/xmodule/vertical_block.py index 7912a6108fa105403065a7b27c14c620ee1fb928..f5ca8988a0ee5a09d7806ab78cbc0d911b6b43ff 100644 --- a/common/lib/xmodule/xmodule/vertical_block.py +++ b/common/lib/xmodule/xmodule/vertical_block.py @@ -92,11 +92,11 @@ class VerticalBlock(SequenceFields, XModuleFields, StudioEditableBlock, XmlParse 'content': rendered_child.content }) - cta_service = self.runtime.service(self, 'call_to_action') - vertical_banner_ctas = cta_service and cta_service.get_ctas(self, 'vertical_banner') - completed = self.is_block_complete_for_assignments(completion_service) past_due = completed is False and self.due and self.due < datetime.now(pytz.UTC) + cta_service = self.runtime.service(self, 'call_to_action') + vertical_banner_ctas = (cta_service and cta_service.get_ctas(self, 'vertical_banner', completed)) or [] + fragment_context = { 'items': contents, 'xblock_context': context, diff --git a/lms/templates/problem.html b/lms/templates/problem.html index 3c6b5e17b5fafaddc966f5dda7188e3fffe0b550..7ed8b4a16eac427bd9f94ff7dd1552f8019240e2 100644 --- a/lms/templates/problem.html +++ b/lms/templates/problem.html @@ -1,6 +1,6 @@ <%page expression_filter="h"/> <%! -from django.utils.translation import ungettext, ugettext as _ +from django.utils.translation import ngettext, gettext as _ from openedx.core.djangolib.markup import HTML %> @@ -85,9 +85,10 @@ from openedx.core.djangolib.markup import HTML </form> % endif % endif - <div class="submission-feedback" id="submission_feedback_${short_id}"> - % if attempts_allowed: - ${ungettext("You have used {num_used} of {num_total} attempt", "You have used {num_used} of {num_total} attempts", attempts_allowed).format(num_used=attempts_used, num_total=attempts_allowed)} + <div class="submission-feedback ${'cta-enabled' if submit_disabled_cta else ''}" id="submission_feedback_${short_id}"> + ## When attempts are not 0, the CTA above will contain a message about the number of used attempts + % if attempts_allowed and (not submit_disabled_cta or attempts_used == 0): + ${ngettext("You have used {num_used} of {num_total} attempt", "You have used {num_used} of {num_total} attempts", attempts_allowed).format(num_used=attempts_used, num_total=attempts_allowed)} % endif <span class="sr">${_("Some problems have options such as save, reset, hints, or show answer. These options follow the Submit button.")}</span> </div> diff --git a/openedx/core/lib/xblock_services/call_to_action.py b/openedx/core/lib/xblock_services/call_to_action.py index 981ef4570a930b5be9fe666757db006ed6275984..fb4910c2ee9d46b296c54c28908b2b4e9b37cb6f 100644 --- a/openedx/core/lib/xblock_services/call_to_action.py +++ b/openedx/core/lib/xblock_services/call_to_action.py @@ -12,7 +12,7 @@ class CallToActionService(PluginManager): """ NAMESPACE = 'openedx.call_to_action' - def get_ctas(self, xblock, category): + def get_ctas(self, xblock, category, completion=False): """ Return the calls to action associated with the specified category for the given xblock. @@ -45,5 +45,5 @@ class CallToActionService(PluginManager): """ ctas = [] for cta_provider in self.get_available_plugins().values(): - ctas.extend(cta_provider().get_ctas(xblock, category)) + ctas.extend(cta_provider().get_ctas(xblock, category, completion)) return ctas diff --git a/openedx/features/personalized_learner_schedules/call_to_action.py b/openedx/features/personalized_learner_schedules/call_to_action.py index 13277c9a298a9ea7cdfa1fc4e2b8a5636d2f17ad..adaafcc71340c1bb91198e0108d6606907a42fa2 100644 --- a/openedx/features/personalized_learner_schedules/call_to_action.py +++ b/openedx/features/personalized_learner_schedules/call_to_action.py @@ -1,10 +1,14 @@ +""" +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 gettext as _ +from django.utils.translation import ngettext, gettext as _ from lms.djangoapps.course_home_api.utils import is_request_from_learning_mfe from openedx.core.lib.mobile_utils import is_request_from_mobile_app @@ -14,12 +18,14 @@ 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): + def get_ctas(self, xblock, category, completed): """ Return the calls to action associated with the specified category for the given xblock. @@ -44,13 +50,13 @@ class PersonalizedLearnerScheduleCallToAction: # 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): - ctas.append(self._make_reset_deadlines_cta(xblock, is_learning_mfe)) + ctas.append(self._make_reset_deadlines_cta(xblock, category, is_learning_mfe)) - elif category == self.VERTICAL_BANNER: + 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) for item in xblock.get_display_items()): - ctas.append(self._make_reset_deadlines_cta(xblock, is_learning_mfe)) + ctas.append(self._make_reset_deadlines_cta(xblock, category, is_learning_mfe)) return ctas @@ -79,6 +85,10 @@ class PersonalizedLearnerScheduleCallToAction: @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 @@ -87,8 +97,11 @@ class PersonalizedLearnerScheduleCallToAction: '%s.is_past_due into a method.', name) PersonalizedLearnerScheduleCallToAction.past_due_class_warnings.add(name) - @staticmethod - def _make_reset_deadlines_cta(xblock, is_learning_mfe=False): + @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 @@ -103,6 +116,24 @@ class PersonalizedLearnerScheduleCallToAction: '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',