fix: User should not be shown ID Verification prompt on progress page if disabled (#29662)
Co-authored-by: Simon Chen <schen@edx-c02fw0guml85.lan>
This commit is contained in:
@@ -8,11 +8,13 @@ import itertools
|
||||
import json
|
||||
import re
|
||||
from datetime import datetime, timedelta
|
||||
from uuid import uuid4
|
||||
|
||||
from unittest.mock import MagicMock, PropertyMock, call, create_autospec, patch
|
||||
from urllib.parse import quote, urlencode
|
||||
from uuid import uuid4
|
||||
|
||||
import ddt
|
||||
from capa.tests.response_xml_factory import \
|
||||
MultipleChoiceResponseXMLFactory
|
||||
from completion.test_utils import CompletionWaffleTestMixin
|
||||
from crum import set_current_request
|
||||
from django.conf import settings
|
||||
@@ -24,6 +26,7 @@ from django.test.client import Client
|
||||
from django.test.utils import override_settings
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from edx_toggles.toggles.testutils import override_waffle_flag, override_waffle_switch
|
||||
from freezegun import freeze_time
|
||||
from markupsafe import escape
|
||||
from milestones.tests.utils import MilestonesTestCaseMixin
|
||||
from opaque_keys.edx.keys import CourseKey, UsageKey
|
||||
@@ -31,17 +34,47 @@ from pytz import UTC, utc
|
||||
from web_fragments.fragment import Fragment
|
||||
from xblock.core import XBlock
|
||||
from xblock.fields import Scope, String
|
||||
from xmodule.course_module import (
|
||||
COURSE_VISIBILITY_PRIVATE,
|
||||
COURSE_VISIBILITY_PUBLIC,
|
||||
COURSE_VISIBILITY_PUBLIC_OUTLINE
|
||||
)
|
||||
from xmodule.data import CertificatesDisplayBehaviors
|
||||
from xmodule.graders import ShowCorrectness
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.tests.django_utils import (
|
||||
TEST_DATA_SPLIT_MODULESTORE,
|
||||
CourseUserType,
|
||||
ModuleStoreTestCase,
|
||||
SharedModuleStoreTestCase
|
||||
)
|
||||
from xmodule.modulestore.tests.factories import (
|
||||
CourseFactory,
|
||||
ItemFactory,
|
||||
check_mongo_calls
|
||||
)
|
||||
|
||||
import lms.djangoapps.courseware.views.views as views
|
||||
from capa.tests.response_xml_factory import MultipleChoiceResponseXMLFactory # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from common.djangoapps.course_modes.models import CourseMode
|
||||
from common.djangoapps.course_modes.tests.factories import CourseModeFactory
|
||||
from freezegun import freeze_time # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from common.djangoapps.student.tests.factories import GlobalStaffFactory
|
||||
from common.djangoapps.student.tests.factories import RequestFactoryNoCsrf
|
||||
from common.djangoapps.student.models import CourseEnrollment
|
||||
from common.djangoapps.student.roles import CourseStaffRole
|
||||
from common.djangoapps.student.tests.factories import (
|
||||
TEST_PASSWORD,
|
||||
AdminFactory,
|
||||
CourseEnrollmentFactory,
|
||||
GlobalStaffFactory,
|
||||
RequestFactoryNoCsrf,
|
||||
UserFactory
|
||||
)
|
||||
from common.djangoapps.util.tests.test_date_utils import fake_pgettext, fake_ugettext
|
||||
from common.djangoapps.util.url import reload_django_url_config
|
||||
from common.djangoapps.util.views import ensure_valid_course_key
|
||||
from lms.djangoapps.certificates import api as certs_api
|
||||
from lms.djangoapps.certificates.models import (
|
||||
CertificateGenerationConfiguration,
|
||||
CertificateGenerationCourseSetting,
|
||||
CertificateStatuses
|
||||
)
|
||||
from lms.djangoapps.certificates.tests.factories import (
|
||||
@@ -63,7 +96,7 @@ from lms.djangoapps.courseware.toggles import (
|
||||
COURSEWARE_MICROFRONTEND_COURSE_TEAM_PREVIEW,
|
||||
COURSEWARE_OPTIMIZED_RENDER_XBLOCK,
|
||||
COURSEWARE_USE_LEGACY_FRONTEND,
|
||||
courseware_mfe_is_advertised,
|
||||
courseware_mfe_is_advertised
|
||||
)
|
||||
from lms.djangoapps.courseware.user_state_client import DjangoXBlockUserStateClient
|
||||
from lms.djangoapps.grades.config.waffle import ASSUME_ZERO_GRADE_IF_ABSENT
|
||||
@@ -71,6 +104,7 @@ from lms.djangoapps.grades.config.waffle import waffle_switch as grades_waffle_s
|
||||
from lms.djangoapps.instructor.access import allow_access
|
||||
from lms.djangoapps.verify_student.models import VerificationDeadline
|
||||
from lms.djangoapps.verify_student.services import IDVerificationService
|
||||
from openedx.core.djangoapps.agreements.toggles import ENABLE_INTEGRITY_SIGNATURE
|
||||
from openedx.core.djangoapps.catalog.tests.factories import CourseFactory as CatalogCourseFactory
|
||||
from openedx.core.djangoapps.catalog.tests.factories import CourseRunFactory, ProgramFactory
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
@@ -91,30 +125,12 @@ from openedx.features.course_experience import (
|
||||
)
|
||||
from openedx.features.course_experience.tests.views.helpers import add_course_mode
|
||||
from openedx.features.course_experience.url_helpers import (
|
||||
ExperienceOption,
|
||||
get_courseware_url,
|
||||
get_learning_mfe_home_url,
|
||||
make_learning_mfe_courseware_url,
|
||||
ExperienceOption,
|
||||
make_learning_mfe_courseware_url
|
||||
)
|
||||
from openedx.features.enterprise_support.tests.mixins.enterprise import EnterpriseTestConsentRequired
|
||||
from common.djangoapps.student.models import CourseEnrollment
|
||||
from common.djangoapps.student.roles import CourseStaffRole
|
||||
from common.djangoapps.student.tests.factories import TEST_PASSWORD, AdminFactory, CourseEnrollmentFactory, UserFactory
|
||||
from common.djangoapps.util.tests.test_date_utils import fake_pgettext, fake_ugettext
|
||||
from common.djangoapps.util.url import reload_django_url_config
|
||||
from common.djangoapps.util.views import ensure_valid_course_key
|
||||
from xmodule.course_module import COURSE_VISIBILITY_PRIVATE, COURSE_VISIBILITY_PUBLIC, COURSE_VISIBILITY_PUBLIC_OUTLINE # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.data import CertificatesDisplayBehaviors # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.graders import ShowCorrectness # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.modulestore import ModuleStoreEnum # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.modulestore.tests.django_utils import ( # lint-amnesty, pylint: disable=wrong-import-order
|
||||
TEST_DATA_SPLIT_MODULESTORE,
|
||||
CourseUserType,
|
||||
ModuleStoreTestCase,
|
||||
SharedModuleStoreTestCase
|
||||
)
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory, check_mongo_calls # lint-amnesty, pylint: disable=wrong-import-order
|
||||
|
||||
QUERY_COUNT_TABLE_BLACKLIST = WAFFLE_TABLES
|
||||
|
||||
@@ -1928,6 +1944,30 @@ class ProgressPageTests(ProgressPageBaseTests):
|
||||
assert response.cert_status == 'earned_but_not_available'
|
||||
assert response.title == 'Your certificate will be available soon!'
|
||||
|
||||
@ddt.data(True, False)
|
||||
def test_no_certs_generated_and_not_verified(self, waffle_override):
|
||||
"""
|
||||
Verify if the learner is not ID Verified, and the certs are not yet generated,
|
||||
but the learner is eligible, the get_cert_data would return cert status Unverified
|
||||
"""
|
||||
CertificateGenerationConfiguration(enabled=True).save()
|
||||
CertificateGenerationCourseSetting(
|
||||
course_key=self.course.id, self_generation_enabled=True
|
||||
).save()
|
||||
with override_waffle_flag(ENABLE_INTEGRITY_SIGNATURE, active=waffle_override):
|
||||
with patch(
|
||||
'lms.djangoapps.certificates.api.certificate_downloadable_status',
|
||||
return_value=self.mock_certificate_downloadable_status()
|
||||
):
|
||||
response = views.get_cert_data(self.user, self.course, CourseMode.VERIFIED, MagicMock(passed=True))
|
||||
|
||||
if waffle_override:
|
||||
assert response.cert_status == 'requesting'
|
||||
assert response.title == 'Congratulations, you qualified for a certificate!'
|
||||
else:
|
||||
assert response.cert_status == 'unverified'
|
||||
assert response.title == 'Certificate unavailable'
|
||||
|
||||
def assert_invalidate_certificate(self, certificate):
|
||||
""" Dry method to mark certificate as invalid. And assert the response. """
|
||||
CertificateInvalidationFactory.create(
|
||||
@@ -3477,47 +3517,40 @@ class DatesTabTestCase(ModuleStoreTestCase):
|
||||
vertical = ItemFactory.create(category='vertical', parent_location=subsection.location)
|
||||
ItemFactory.create(category='problem', parent_location=vertical.location, has_score=True)
|
||||
|
||||
with patch('lms.djangoapps.courseware.views.views.get_enrollment') as mock_get_enrollment:
|
||||
mock_get_enrollment.return_value = {
|
||||
'mode': enrollment.mode
|
||||
}
|
||||
response = self._get_response(self.course)
|
||||
self.assertContains(response, subsection.display_name)
|
||||
# Show the Verification Deadline for verified only
|
||||
self.assertContains(response, 'Verification Deadline')
|
||||
# Make sure pill exists for today's date
|
||||
self.assertContains(response, '<div class="pill today">')
|
||||
# Make sure pill exists for next due assignment
|
||||
self.assertContains(response, '<div class="pill due-next">')
|
||||
# No pills for verified enrollments
|
||||
self.assertNotContains(response, '<div class="pill verified">')
|
||||
# Make sure the assignment type is rendered
|
||||
self.assertContains(response, 'Homework:')
|
||||
response = self._get_response(self.course)
|
||||
self.assertContains(response, subsection.display_name)
|
||||
# Show the Verification Deadline for verified only
|
||||
self.assertContains(response, 'Verification Deadline')
|
||||
# Make sure pill exists for today's date
|
||||
self.assertContains(response, '<div class="pill today">')
|
||||
# Make sure pill exists for next due assignment
|
||||
self.assertContains(response, '<div class="pill due-next">')
|
||||
# No pills for verified enrollments
|
||||
self.assertNotContains(response, '<div class="pill verified">')
|
||||
# Make sure the assignment type is rendered
|
||||
self.assertContains(response, 'Homework:')
|
||||
|
||||
enrollment.delete()
|
||||
enrollment = CourseEnrollmentFactory(course_id=self.course.id, user=self.user, mode=CourseMode.AUDIT)
|
||||
mock_get_enrollment.return_value = {
|
||||
'mode': enrollment.mode
|
||||
}
|
||||
enrollment.delete()
|
||||
enrollment = CourseEnrollmentFactory(course_id=self.course.id, user=self.user, mode=CourseMode.AUDIT)
|
||||
|
||||
expected_calls = [
|
||||
call('course_id', str(self.course.id)),
|
||||
call('user_id', self.user.id),
|
||||
call('is_staff', self.user.is_staff),
|
||||
]
|
||||
expected_calls = [
|
||||
call('course_id', str(self.course.id)),
|
||||
call('user_id', self.user.id),
|
||||
call('is_staff', self.user.is_staff),
|
||||
]
|
||||
|
||||
response = self._get_response(self.course)
|
||||
response = self._get_response(self.course)
|
||||
|
||||
mock_set_custom_attribute.assert_has_calls(expected_calls, any_order=True)
|
||||
self.assertContains(response, subsection.display_name)
|
||||
# Don't show the Verification Deadline for audit
|
||||
self.assertNotContains(response, 'Verification Deadline')
|
||||
# Pill doesn't exist for assignment due tomorrow
|
||||
self.assertNotContains(response, '<div class="pill due-next">')
|
||||
# Should have verified pills for audit enrollments
|
||||
self.assertContains(response, '<div class="pill verified">')
|
||||
# Make sure the assignment type is rendered
|
||||
self.assertContains(response, 'Homework:')
|
||||
mock_set_custom_attribute.assert_has_calls(expected_calls, any_order=True)
|
||||
self.assertContains(response, subsection.display_name)
|
||||
# Don't show the Verification Deadline for audit
|
||||
self.assertNotContains(response, 'Verification Deadline')
|
||||
# Pill doesn't exist for assignment due tomorrow
|
||||
self.assertNotContains(response, '<div class="pill due-next">')
|
||||
# Should have verified pills for audit enrollments
|
||||
self.assertContains(response, '<div class="pill verified">')
|
||||
# Make sure the assignment type is rendered
|
||||
self.assertContains(response, 'Homework:')
|
||||
|
||||
@override_waffle_flag(RELATIVE_DATES_FLAG, active=True)
|
||||
def test_reset_deadlines_banner_displays(self):
|
||||
|
||||
@@ -5,10 +5,11 @@ Courseware views functions
|
||||
|
||||
import json
|
||||
import logging
|
||||
import urllib
|
||||
from collections import OrderedDict, namedtuple
|
||||
from datetime import datetime
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
import urllib
|
||||
import bleach
|
||||
import requests
|
||||
from django.conf import settings
|
||||
@@ -22,7 +23,6 @@ from django.shortcuts import redirect
|
||||
from django.template.context_processors import csrf
|
||||
from django.urls import reverse
|
||||
from django.utils.decorators import method_decorator
|
||||
from urllib.parse import quote_plus # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from django.utils.text import slugify
|
||||
from django.utils.translation import gettext
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@@ -45,21 +45,32 @@ from rest_framework.decorators import api_view, throttle_classes
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.throttling import UserRateThrottle
|
||||
from web_fragments.fragment import Fragment
|
||||
from xmodule.course_module import (
|
||||
COURSE_VISIBILITY_PUBLIC,
|
||||
COURSE_VISIBILITY_PUBLIC_OUTLINE
|
||||
)
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.exceptions import (
|
||||
ItemNotFoundError,
|
||||
NoPathToItem
|
||||
)
|
||||
from xmodule.tabs import CourseTabList
|
||||
from xmodule.x_module import STUDENT_VIEW
|
||||
|
||||
from lms.djangoapps.survey import views as survey_views
|
||||
from common.djangoapps.course_modes.models import CourseMode, get_course_prices
|
||||
from common.djangoapps.edxmako.shortcuts import marketing_link, render_to_response, render_to_string
|
||||
from lms.djangoapps.edxnotes.helpers import is_feature_enabled
|
||||
from common.djangoapps.student.models import CourseEnrollment, UserTestGroup
|
||||
from common.djangoapps.util.cache import cache, cache_if_anonymous
|
||||
from common.djangoapps.util.course import course_location_from_key
|
||||
from common.djangoapps.util.db import outer_atomic
|
||||
from common.djangoapps.util.milestones_helpers import get_prerequisite_courses_display
|
||||
from common.djangoapps.util.views import ensure_valid_course_key, ensure_valid_usage_key
|
||||
from lms.djangoapps.ccx.custom_exception import CCXLocatorValidationException
|
||||
from lms.djangoapps.certificates import api as certs_api
|
||||
from lms.djangoapps.certificates.data import CertificateStatuses
|
||||
from lms.djangoapps.commerce.utils import EcommerceService
|
||||
from lms.djangoapps.course_goals.models import UserActivity
|
||||
from lms.djangoapps.course_home_api.toggles import (
|
||||
course_home_legacy_is_active,
|
||||
course_home_mfe_progress_tab_is_active
|
||||
)
|
||||
from openedx.features.course_experience.url_helpers import get_learning_mfe_home_url, is_request_from_learning_mfe
|
||||
from lms.djangoapps.course_home_api.toggles import course_home_legacy_is_active, course_home_mfe_progress_tab_is_active
|
||||
from lms.djangoapps.courseware.access import has_access, has_ccx_coach_role
|
||||
from lms.djangoapps.courseware.access_utils import check_course_open_for_learner, check_public_access
|
||||
from lms.djangoapps.courseware.courses import (
|
||||
@@ -78,23 +89,24 @@ from lms.djangoapps.courseware.courses import (
|
||||
)
|
||||
from lms.djangoapps.courseware.date_summary import verified_upgrade_deadline_link
|
||||
from lms.djangoapps.courseware.exceptions import CourseAccessRedirect, Redirect
|
||||
from lms.djangoapps.courseware.masquerade import setup_masquerade, is_masquerading_as_specific_student
|
||||
from lms.djangoapps.courseware.masquerade import is_masquerading_as_specific_student, setup_masquerade
|
||||
from lms.djangoapps.courseware.model_data import FieldDataCache
|
||||
from lms.djangoapps.courseware.models import BaseStudentModuleHistory, StudentModule
|
||||
from lms.djangoapps.courseware.permissions import ( # lint-amnesty, pylint: disable=unused-import
|
||||
from lms.djangoapps.courseware.permissions import (
|
||||
MASQUERADE_AS_STUDENT,
|
||||
VIEW_COURSE_HOME,
|
||||
VIEW_COURSEWARE,
|
||||
VIEW_XQA_INTERFACE
|
||||
)
|
||||
|
||||
from lms.djangoapps.courseware.toggles import is_courses_default_invite_only_enabled
|
||||
from lms.djangoapps.courseware.user_state_client import DjangoXBlockUserStateClient
|
||||
from lms.djangoapps.edxnotes.helpers import is_feature_enabled
|
||||
from lms.djangoapps.experiments.utils import get_experiment_user_metadata_context
|
||||
from lms.djangoapps.grades.api import CourseGradeFactory
|
||||
from lms.djangoapps.instructor.enrollment import uses_shib
|
||||
from lms.djangoapps.instructor.views.api import require_global_staff
|
||||
from lms.djangoapps.survey import views as survey_views
|
||||
from lms.djangoapps.verify_student.services import IDVerificationService
|
||||
from openedx.core.djangoapps.agreements.toggles import is_integrity_signature_enabled
|
||||
from openedx.core.djangoapps.catalog.utils import get_programs, get_programs_with_type
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from openedx.core.djangoapps.credit.api import (
|
||||
@@ -102,7 +114,7 @@ from openedx.core.djangoapps.credit.api import (
|
||||
is_credit_course,
|
||||
is_user_eligible_for_credit
|
||||
)
|
||||
from openedx.core.djangoapps.enrollments.api import add_enrollment, get_enrollment # lint-amnesty, pylint: disable=unused-import
|
||||
from openedx.core.djangoapps.enrollments.api import add_enrollment
|
||||
from openedx.core.djangoapps.enrollments.permissions import ENROLL_IN_COURSE
|
||||
from openedx.core.djangoapps.models.course_details import CourseDetails
|
||||
from openedx.core.djangoapps.plugin_api.views import EdxFragmentView
|
||||
@@ -117,23 +129,17 @@ from openedx.features.content_type_gating.models import ContentTypeGatingConfig
|
||||
from openedx.features.course_duration_limits.access import generate_course_expired_fragment
|
||||
from openedx.features.course_experience import DISABLE_UNIFIED_COURSE_TAB_FLAG, course_home_url_name
|
||||
from openedx.features.course_experience.course_tools import CourseToolsPluginManager
|
||||
from openedx.features.course_experience.url_helpers import get_courseware_url, ExperienceOption
|
||||
from openedx.features.course_experience.url_helpers import (
|
||||
ExperienceOption,
|
||||
get_courseware_url,
|
||||
get_learning_mfe_home_url,
|
||||
is_request_from_learning_mfe
|
||||
)
|
||||
from openedx.features.course_experience.utils import dates_banner_should_display
|
||||
from openedx.features.course_experience.views.course_dates import CourseDatesFragmentView
|
||||
from openedx.features.course_experience.waffle import ENABLE_COURSE_ABOUT_SIDEBAR_HTML
|
||||
from openedx.features.course_experience.waffle import waffle as course_experience_waffle
|
||||
from openedx.features.enterprise_support.api import data_sharing_consent_required
|
||||
from common.djangoapps.student.models import CourseEnrollment, UserTestGroup
|
||||
from common.djangoapps.util.cache import cache, cache_if_anonymous
|
||||
from common.djangoapps.util.course import course_location_from_key
|
||||
from common.djangoapps.util.db import outer_atomic
|
||||
from common.djangoapps.util.milestones_helpers import get_prerequisite_courses_display
|
||||
from common.djangoapps.util.views import ensure_valid_course_key, ensure_valid_usage_key
|
||||
from xmodule.course_module import COURSE_VISIBILITY_PUBLIC, COURSE_VISIBILITY_PUBLIC_OUTLINE # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError, NoPathToItem # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.tabs import CourseTabList # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.x_module import STUDENT_VIEW # lint-amnesty, pylint: disable=wrong-import-order
|
||||
|
||||
from ..context_processor import user_timezone_locale_prefs
|
||||
from ..entrance_exams import user_can_skip_entrance_exam
|
||||
@@ -1248,8 +1254,8 @@ def _downloadable_certificate_message(course, cert_downloadable_status): # lint
|
||||
return _downloadable_cert_data(download_url=cert_downloadable_status['download_url'])
|
||||
|
||||
|
||||
def _missing_required_verification(student, enrollment_mode):
|
||||
return (
|
||||
def _missing_required_verification(student, enrollment_mode, course_key):
|
||||
return not is_integrity_signature_enabled(course_key) and (
|
||||
enrollment_mode in CourseMode.VERIFIED_MODES and not IDVerificationService.user_is_verified(student)
|
||||
)
|
||||
|
||||
@@ -1272,7 +1278,7 @@ def _certificate_message(student, course, enrollment_mode): # lint-amnesty, pyl
|
||||
if cert_downloadable_status['is_downloadable']:
|
||||
return _downloadable_certificate_message(course, cert_downloadable_status)
|
||||
|
||||
if _missing_required_verification(student, enrollment_mode):
|
||||
if _missing_required_verification(student, enrollment_mode, course.id):
|
||||
return UNVERIFIED_CERT_DATA
|
||||
|
||||
return REQUESTING_CERT_DATA
|
||||
|
||||
Reference in New Issue
Block a user