From 7dd4f2db9910568d55adc8f541d1106f4c42dc97 Mon Sep 17 00:00:00 2001 From: Dillon Dumesnil Date: Tue, 20 Jul 2021 08:45:02 -0600 Subject: [PATCH] fix: AA-912: Hide URL after due date if subsection is marked as hide after due A bug was reported of a learner accessing content that should have been hidden due to the hide after due setting in Studio by the course team by clicking on the Progress tab. This takes into account that value and will now hide the URL on the Progress tab in that scenario. --- .../progress/v1/serializers.py | 15 ++++++- .../progress/v1/tests/test_views.py | 19 ++++++++ .../course_home_api/progress/v1/views.py | 3 +- lms/djangoapps/grades/subsection_grade.py | 7 ++- lms/templates/courseware/progress.html | 43 +++++++++++++------ .../block_structure/block_structure.py | 1 - 6 files changed, 70 insertions(+), 18 deletions(-) diff --git a/lms/djangoapps/course_home_api/progress/v1/serializers.py b/lms/djangoapps/course_home_api/progress/v1/serializers.py index 977a6b34dd..13586fc0fc 100644 --- a/lms/djangoapps/course_home_api/progress/v1/serializers.py +++ b/lms/djangoapps/course_home_api/progress/v1/serializers.py @@ -1,8 +1,12 @@ """ Progress Tab Serializers """ +from datetime import datetime + from rest_framework import serializers from rest_framework.reverse import reverse +from pytz import UTC + from lms.djangoapps.course_home_api.mixins import VerifiedModeSerializerMixin @@ -20,8 +24,8 @@ class SubsectionScoresSerializer(serializers.Serializer): Serializer for subsections in section_scores """ assignment_type = serializers.CharField(source='format') - display_name = serializers.CharField() block_key = serializers.SerializerMethodField() + display_name = serializers.CharField() has_graded_assignment = serializers.BooleanField(source='graded') learner_has_access = serializers.SerializerMethodField() num_points_earned = serializers.FloatField(source='graded_total.earned') @@ -46,6 +50,15 @@ class SubsectionScoresSerializer(serializers.Serializer): return problem_scores def get_url(self, subsection): + """ + Returns the URL for the subsection while taking into account if the course team has + marked the subsection's visibility as hide after due. + """ + hide_url_date = (subsection.self_paced and subsection.end) or subsection.due + if (not self.context['staff_access'] and subsection.hide_after_due and hide_url_date + and datetime.now(UTC) > hide_url_date): + return None + relative_path = reverse('jump_to', args=[self.context['course_key'], subsection.location]) request = self.context['request'] return request.build_absolute_uri(relative_path) diff --git a/lms/djangoapps/course_home_api/progress/v1/tests/test_views.py b/lms/djangoapps/course_home_api/progress/v1/tests/test_views.py index 33145249a7..9100299826 100644 --- a/lms/djangoapps/course_home_api/progress/v1/tests/test_views.py +++ b/lms/djangoapps/course_home_api/progress/v1/tests/test_views.py @@ -208,3 +208,22 @@ class ProgressTabTestViews(BaseCourseHomeTests): response = self.client.get(self.url) assert response.data['username'] == other_user.username + + @override_waffle_flag(COURSE_HOME_MICROFRONTEND_PROGRESS_TAB, active=True) + def test_url_hidden_if_subsection_hide_after_due(self): + chapter = ItemFactory(parent=self.course, category='chapter') + yesterday = now() - timedelta(days=1) + hide_after_due_subsection = ItemFactory( + parent=chapter, category='sequential', hide_after_due=True, due=yesterday + ) + + CourseEnrollment.enroll(self.user, self.course.id) + + response = self.client.get(self.url) + assert response.status_code == 200 + + sections = response.data['section_scores'] + regular_subsection = sections[0]['subsections'][0] # default sequence that parent class gives us + hide_after_due_subsection = sections[1]['subsections'][0] + assert regular_subsection['url'] is not None + assert hide_after_due_subsection['url'] is None diff --git a/lms/djangoapps/course_home_api/progress/v1/views.py b/lms/djangoapps/course_home_api/progress/v1/views.py index a1b6985d7c..e8e2e31c19 100644 --- a/lms/djangoapps/course_home_api/progress/v1/views.py +++ b/lms/djangoapps/course_home_api/progress/v1/views.py @@ -86,7 +86,8 @@ class ProgressTabView(RetrieveAPIView): ('always', 'never', 'past_due', values defined in common/lib/xmodule/xmodule/modulestore/inheritance.py) show_grades: (bool) a bool for whether to show grades based on the access the user has - url: (str) the absolute path url to the Subsection + url: (str or None) the absolute path url to the Subsection or None if the Subsection is no longer accessible + to the learner due to a hide_after_due course team setting enrollment_mode: (str) a str representing the enrollment the user has ('audit', 'verified', ...) grading_policy: assignment_policies: List of serialized assignment grading policy objects, each has the following fields: diff --git a/lms/djangoapps/grades/subsection_grade.py b/lms/djangoapps/grades/subsection_grade.py index 72b3f4e7aa..70d7fbfaa5 100644 --- a/lms/djangoapps/grades/subsection_grade.py +++ b/lms/djangoapps/grades/subsection_grade.py @@ -27,9 +27,14 @@ class SubsectionGradeBase(metaclass=ABCMeta): self.display_name = block_metadata_utils.display_name_with_default(subsection) self.url_name = block_metadata_utils.url_name_for_block(subsection) - self.format = getattr(subsection, 'format', '') self.due = getattr(subsection, 'due', None) + self.end = getattr(subsection, 'end', None) + self.format = getattr(subsection, 'format', '') self.graded = getattr(subsection, 'graded', False) + transformer_data = getattr(subsection, 'transformer_data', None) + hidden_content_data = transformer_data and subsection.transformer_data.get('hidden_content') + self.hide_after_due = hidden_content_data and hidden_content_data.fields.get('merged_hide_after_due') + self.self_paced = subsection.self_paced self.show_correctness = getattr(subsection, 'show_correctness', '') self.course_version = getattr(subsection, 'course_version', None) diff --git a/lms/templates/courseware/progress.html b/lms/templates/courseware/progress.html index 037a36b3e1..63b340c1e4 100644 --- a/lms/templates/courseware/progress.html +++ b/lms/templates/courseware/progress.html @@ -3,16 +3,18 @@ <%namespace name='static' file='/static_content.html'/> <%def name="online_help_token()"><% return "progress" %> <%! +from datetime import datetime + +from django.conf import settings +from django.urls import reverse +from django.utils.http import urlquote_plus +from django.utils.translation import ugettext as _ +from pytz import UTC + from common.djangoapps.course_modes.models import CourseMode from lms.djangoapps.certificates.data import CertificateStatuses from lms.djangoapps.grades.api import constants as grades_constants -from django.utils.translation import ugettext as _ from openedx.core.djangolib.markup import HTML, Text -from django.urls import reverse -from django.conf import settings -from django.utils.http import urlquote_plus -from six import text_type - from openedx.features.enterprise_support.utils import get_enterprise_learner_generic_name %> @@ -73,7 +75,7 @@ username = get_enterprise_learner_generic_name(request) or student.username %if certificate_data:
- <% post_url = reverse('generate_user_cert', args=[text_type(course.id)]) %> + <% post_url = reverse('generate_user_cert', args=[str(course.id)]) %>

${_(certificate_data.title)}

${_(certificate_data.msg)}

@@ -169,20 +171,33 @@ username = get_enterprise_learner_generic_name(request) or student.username %for section in chapter['sections']:
<% + hide_url_date = (section.self_paced and section.end) or section.due + hide_url = not staff_access and section.hide_after_due and hide_url_date and datetime.now(UTC) > hide_url_date + earned = section.graded_total.earned total = section.graded_total.possible percentageString = "{0:.0%}".format(section.percent_graded) if total > 0 or earned > 0 else "" %>

- - ${ section.display_name} + %if hide_url: +

${section.display_name} %if (total > 0 or earned > 0) and section.show_grades(staff_access): - - ${_("{earned} of {total} possible points").format(earned='{:.3n}'.format(float(earned)), total='{:.3n}'.format(float(total)))} - - %endif - + + ${_("{earned} of {total} possible points").format(earned='{:.3n}'.format(float(earned)), total='{:.3n}'.format(float(total)))} + + %endif +

+ %else: + + ${ section.display_name} + %if (total > 0 or earned > 0) and section.show_grades(staff_access): + + ${_("{earned} of {total} possible points").format(earned='{:.3n}'.format(float(earned)), total='{:.3n}'.format(float(total)))} + + %endif + + %endif %if (total > 0 or earned > 0) and section.show_grades(staff_access): ${"({0:.3n}/{1:.3n}) {2}".format( float(earned), float(total), percentageString )} %endif diff --git a/openedx/core/djangoapps/content/block_structure/block_structure.py b/openedx/core/djangoapps/content/block_structure/block_structure.py index fe7a42b379..a8d10c4d81 100644 --- a/openedx/core/djangoapps/content/block_structure/block_structure.py +++ b/openedx/core/djangoapps/content/block_structure/block_structure.py @@ -327,7 +327,6 @@ class TransformerData(FieldData): """ Data structure to encapsulate collected data for a transformer. """ - pass # lint-amnesty, pylint: disable=unnecessary-pass class TransformerDataMap(dict):