feat: add learner_has_access field to progress tab data

In order to allow the learning MFE's progress tab to show a
different UX for FBE exceptions (where some exams can still be
completed by audit learners), this commit adds access information
to each exam.

AA-829
This commit is contained in:
Michael Terry
2021-06-21 10:49:08 -04:00
parent 3616910841
commit c8a62bd4d2
6 changed files with 51 additions and 14 deletions

View File

@@ -27,10 +27,12 @@ class DateSummarySerializer(serializers.Serializer):
first_component_block_id = serializers.SerializerMethodField()
def get_learner_has_access(self, block):
learner_is_full_access = self.context.get('learner_is_full_access', False)
block_is_verified = (getattr(block, 'contains_gated_content', False) or
isinstance(block, VerificationDeadlineDate))
return (not block_is_verified) or learner_is_full_access
"""Whether the learner is blocked (gated) from this content or not"""
if isinstance(block, VerificationDeadlineDate):
# This date block isn't an assignment, so doesn't have contains_gated_content set for it
return self.context.get('learner_is_full_access', False)
return not getattr(block, 'contains_gated_content', False)
def get_link(self, block):
if block.link:

View File

@@ -17,7 +17,6 @@ from common.djangoapps.student.roles import CourseInstructorRole
from common.djangoapps.student.tests.factories import UserFactory
from lms.djangoapps.course_home_api.tests.utils import BaseCourseHomeTests
from lms.djangoapps.course_home_api.toggles import COURSE_HOME_USE_LEGACY_FRONTEND
from lms.djangoapps.experiments.testutils import override_experiment_waffle_flag
from openedx.core.djangoapps.content.learning_sequences.api import replace_course_outline
from openedx.core.djangoapps.content.learning_sequences.data import CourseOutlineData, CourseVisibility
from openedx.core.djangoapps.content.learning_sequences.toggles import USE_FOR_OUTLINES
@@ -354,7 +353,6 @@ class OutlineTabTestViews(BaseCourseHomeTests):
response = self.client.get(self.url)
assert response.data['verified_mode'] == {'access_expiration_date': (enrollment.created + MIN_DURATION), 'currency': 'USD', 'currency_symbol': '$', 'price': 149, 'sku': 'ABCD1234', 'upgrade_url': '/dashboard'}
@override_experiment_waffle_flag(COURSE_HOME_MICROFRONTEND, active=True)
def test_hide_learning_sequences(self):
"""
Check that Learning Sequences filters out sequences.

View File

@@ -23,6 +23,7 @@ class SubsectionScoresSerializer(serializers.Serializer):
display_name = serializers.CharField()
block_key = serializers.SerializerMethodField()
has_graded_assignment = serializers.BooleanField(source='graded')
learner_has_access = serializers.SerializerMethodField()
num_points_earned = serializers.IntegerField(source='graded_total.earned')
num_points_possible = serializers.IntegerField(source='graded_total.possible')
percent_graded = serializers.FloatField()
@@ -41,6 +42,10 @@ class SubsectionScoresSerializer(serializers.Serializer):
def get_show_grades(self, subsection):
return subsection.show_grades(self.context['staff_access'])
def get_learner_has_access(self, subsection):
course_blocks = self.context['course_blocks']
return not course_blocks.get_xblock_field(subsection.location, 'contains_gated_content', False)
class SectionScoresSerializer(serializers.Serializer):
"""

View File

@@ -4,7 +4,6 @@ Tests for Progress Tab API in the Course Home API
import dateutil
import ddt
import mock
from datetime import datetime, timedelta
from pytz import UTC
from unittest.mock import patch
@@ -18,16 +17,14 @@ from common.djangoapps.student.tests.factories import UserFactory
from lms.djangoapps.course_home_api.tests.utils import BaseCourseHomeTests
from lms.djangoapps.course_home_api.models import DisableProgressPageStackedConfig
from lms.djangoapps.course_home_api.toggles import COURSE_HOME_MICROFRONTEND_PROGRESS_TAB
from lms.djangoapps.experiments.testutils import override_experiment_waffle_flag
from lms.djangoapps.verify_student.models import ManualVerification
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.course_date_signals.utils import MIN_DURATION
from openedx.core.djangoapps.user_api.preferences.api import set_user_preference
from openedx.features.content_type_gating.helpers import CONTENT_GATING_PARTITION_ID, CONTENT_TYPE_GATE_GROUP_IDS
from openedx.features.content_type_gating.models import ContentTypeGatingConfig
from openedx.features.course_duration_limits.models import CourseDurationLimitConfig
from xmodule.modulestore.tests.factories import ItemFactory
CREDIT_SUPPORT_URL = 'https://support.edx.org/hc/en-us/sections/115004154688-Purchasing-Academic-Credit'
@ddt.ddt
class ProgressTabTestViews(BaseCourseHomeTests):
@@ -159,3 +156,30 @@ class ProgressTabTestViews(BaseCourseHomeTests):
response = self.client.get(self.url)
assert response.status_code == 404
@override_waffle_flag(COURSE_HOME_MICROFRONTEND_PROGRESS_TAB, active=True)
def test_learner_has_access(self):
chapter = ItemFactory(parent=self.course, category='chapter')
gated = ItemFactory(parent=chapter, category='sequential')
ItemFactory.create(parent=gated, category='problem', graded=True, has_score=True)
ungated = ItemFactory(parent=chapter, category='sequential')
ItemFactory.create(parent=ungated, category='problem', graded=True, has_score=True,
group_access={
CONTENT_GATING_PARTITION_ID: [CONTENT_TYPE_GATE_GROUP_IDS['full_access'],
CONTENT_TYPE_GATE_GROUP_IDS['limited_access']],
})
CourseEnrollment.enroll(self.user, self.course.id)
CourseDurationLimitConfig.objects.create(enabled=True, enabled_as_of=datetime(2018, 1, 1))
ContentTypeGatingConfig.objects.create(enabled=True, enabled_as_of=datetime(2018, 1, 1))
response = self.client.get(self.url)
assert response.status_code == 200
sections = response.data['section_scores']
ungraded_score = sections[0]['subsections'][0] # default sequence that parent class gives us
gated_score = sections[1]['subsections'][0]
ungated_score = sections[1]['subsections'][1]
assert ungraded_score['learner_has_access']
assert not gated_score['learner_has_access']
assert ungated_score['learner_has_access']

View File

@@ -29,6 +29,7 @@ from openedx.core.djangoapps.content.block_structure.transformers import BlockSt
from openedx.core.djangoapps.content.block_structure.api import get_block_structure_manager
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser
from openedx.features.content_type_gating.block_transformers import ContentTypeGateTransformer
class ProgressTabView(RetrieveAPIView):
@@ -69,6 +70,7 @@ class ProgressTabView(RetrieveAPIView):
block_key: (str) the key of the given subsection block
display_name: (str) a str of what the name of the Subsection is for displaying on the site
has_graded_assignment: (bool) whether or not the Subsection is a graded assignment
learner_has_access: (bool) whether the learner has access to the subsection (could be FBE gated)
num_points_earned: (int) the amount of points the user has earned for the given subsection
num_points_possible: (int) the total amount of points possible for the given subsection
percent_graded: (float) the percentage of total points the user has received a grade for in a given subsection
@@ -144,7 +146,7 @@ class ProgressTabView(RetrieveAPIView):
# Get has_scheduled_content data
transformers = BlockStructureTransformers()
transformers += [start_date.StartDateTransformer()]
transformers += [start_date.StartDateTransformer(), ContentTypeGateTransformer()]
usage_key = collected_block_structure.root_block_usage_key
course_blocks = get_course_blocks(
request.user,
@@ -190,6 +192,7 @@ class ProgressTabView(RetrieveAPIView):
}
context = self.get_serializer_context()
context['staff_access'] = is_staff
context['course_blocks'] = course_blocks
context['course_key'] = course_key
# course_overview and enrollment will be used by VerifiedModeSerializerMixin
context['course_overview'] = course_overview

View File

@@ -2790,9 +2790,14 @@ YOUTUBE_API_KEY = 'PUT_YOUR_API_KEY_HERE'
################################### APPS ######################################
# The order of INSTALLED_APPS is important, when adding new apps here
# remember to check that you are not creating new
# The order of INSTALLED_APPS is important, when adding new apps here remember to check that you are not creating new
# RemovedInDjango19Warnings in the test logs.
#
# If you want to add a new djangoapp that isn't suitable for everyone, you have some options:
# - Add it to OPTIONAL_APPS below (registered if importable)
# - Add it to the ADDL_INSTALLED_APPS configuration variable (acts like EXTRA_APPS in other IDAs)
# - Make it a plugin (which are auto-registered) and add it to the EDXAPP_PRIVATE_REQUIREMENTS configuration variable
# (See https://github.com/edx/edx-django-utils/tree/master/edx_django_utils/plugins)
INSTALLED_APPS = [
# Standard ones that are always installed...
'django.contrib.auth',