diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py
index 1a0c61984d..46accda240 100644
--- a/lms/djangoapps/courseware/tests/test_views.py
+++ b/lms/djangoapps/courseware/tests/test_views.py
@@ -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, '
')
- # Make sure pill exists for next due assignment
- self.assertContains(response, '
')
- # No pills for verified enrollments
- self.assertNotContains(response, '
')
- # 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, '
')
+ # Make sure pill exists for next due assignment
+ self.assertContains(response, '
')
+ # No pills for verified enrollments
+ self.assertNotContains(response, '
')
+ # 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, '
')
- # Should have verified pills for audit enrollments
- self.assertContains(response, '
')
- # 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, '
')
+ # Should have verified pills for audit enrollments
+ self.assertContains(response, '
')
+ # 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):
diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py
index 52b8760a83..c73f8c7e23 100644
--- a/lms/djangoapps/courseware/views/views.py
+++ b/lms/djangoapps/courseware/views/views.py
@@ -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