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.
This commit is contained in:
Dillon Dumesnil
2021-07-20 08:45:02 -06:00
parent 7261bc42bd
commit 7dd4f2db99
6 changed files with 70 additions and 18 deletions

View File

@@ -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)

View File

@@ -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

View File

@@ -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:

View File

@@ -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)

View File

@@ -3,16 +3,18 @@
<%namespace name='static' file='/static_content.html'/>
<%def name="online_help_token()"><% return "progress" %></%def>
<%!
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:
<div class="auto-cert-message" id="course-success">
<div class="has-actions">
<% post_url = reverse('generate_user_cert', args=[text_type(course.id)]) %>
<% post_url = reverse('generate_user_cert', args=[str(course.id)]) %>
<div class="msg-content">
<h4 class="hd hd-4 title">${_(certificate_data.title)}</h4>
<p class="copy">${_(certificate_data.msg)}</p>
@@ -169,20 +171,33 @@ username = get_enterprise_learner_generic_name(request) or student.username
%for section in chapter['sections']:
<div>
<%
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 ""
%>
<h4 class="hd hd-4">
<a href="${reverse('courseware_section', kwargs=dict(course_id=text_type(course.id), chapter=chapter['url_name'], section=section.url_name))}">
${ section.display_name}
%if hide_url:
<p class="d-inline">${section.display_name}
%if (total > 0 or earned > 0) and section.show_grades(staff_access):
<span class="sr">
${_("{earned} of {total} possible points").format(earned='{:.3n}'.format(float(earned)), total='{:.3n}'.format(float(total)))}
</span>
%endif
</a>
<span class="sr">
${_("{earned} of {total} possible points").format(earned='{:.3n}'.format(float(earned)), total='{:.3n}'.format(float(total)))}
</span>
%endif
</p>
%else:
<a href="${reverse('courseware_section', kwargs=dict(course_id=str(course.id), chapter=chapter['url_name'], section=section.url_name))}">
${ section.display_name}
%if (total > 0 or earned > 0) and section.show_grades(staff_access):
<span class="sr">
${_("{earned} of {total} possible points").format(earned='{:.3n}'.format(float(earned)), total='{:.3n}'.format(float(total)))}
</span>
%endif
</a>
%endif
%if (total > 0 or earned > 0) and section.show_grades(staff_access):
<span> ${"({0:.3n}/{1:.3n}) {2}".format( float(earned), float(total), percentageString )}</span>
%endif

View File

@@ -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):