Merge pull request #28754 from edx/dsheraz/PROD-2477

feat: add support-only endpoint to get FBE details
This commit is contained in:
Syed Muhammad Dawoud Sheraz Ali
2021-09-16 21:06:57 +05:00
committed by GitHub
4 changed files with 138 additions and 40 deletions

View File

@@ -40,6 +40,8 @@ from lms.djangoapps.verify_student.models import VerificationDeadline
from lms.djangoapps.verify_student.services import IDVerificationService
from lms.djangoapps.verify_student.tests.factories import SSOVerificationFactory
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.features.content_type_gating.models import ContentTypeGatingConfig
from openedx.features.course_duration_limits.models import CourseDurationLimitConfig
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
@@ -1144,3 +1146,46 @@ class SsoRecordsTests(SupportViewTestCase): # lint-amnesty, pylint: disable=mis
assert response.status_code == 200
assert len(data) == 1
self.assertContains(response, '"uid": "test@example.com"')
class FeatureBasedEnrollmentSupportApiViewTests(SupportViewTestCase):
"""
Test suite for FBE Support API view.
"""
def setUp(self):
super().setUp()
SupportStaffRole().add_users(self.user)
def test_fbe_enabled_response(self):
"""
Test the response for the api view when the gating and duration configs
are enabled.
"""
for course_mode in [CourseMode.AUDIT, CourseMode.VERIFIED]:
CourseModeFactory.create(mode_slug=course_mode, course_id=self.course.id)
ContentTypeGatingConfig.objects.create(enabled=True, enabled_as_of=datetime(2018, 1, 1))
CourseDurationLimitConfig.objects.create(enabled=True, enabled_as_of=datetime(2018, 1, 1))
response = self.client.get(
reverse("support:feature_based_enrollment_details", kwargs={'course_id': str(self.course.id)})
)
data = json.loads(response.content.decode('utf-8'))
gating_config = data['gating_config']
duration_config = data['duration_config']
assert str(self.course.id) == data['course_id']
assert gating_config['enabled']
assert gating_config['enabled_as_of'] == '2018-01-01 00:00:00+00:00'
assert duration_config['enabled']
assert duration_config['enabled_as_of'] == '2018-01-01 00:00:00+00:00'
def test_fbe_disabled_response(self):
"""
Test the FBE support api view response to be empty when no gating and duration
config is present.
"""
response = self.client.get(
reverse("support:feature_based_enrollment_details", kwargs={'course_id': str(self.course.id)})
)
data = json.loads(response.content.decode('utf-8'))
assert data == {}

View File

@@ -2,14 +2,14 @@
URLs for the student support app.
"""
from django.conf import settings
from django.conf.urls import url
from .views.certificate import CertificatesSupportView
from .views.contact_us import ContactUsView
from .views.course_entitlements import EntitlementSupportView
from .views.enrollments import EnrollmentSupportListView, EnrollmentSupportView
from .views.feature_based_enrollments import FeatureBasedEnrollmentsSupportView
from .views.feature_based_enrollments import FeatureBasedEnrollmentsSupportView, FeatureBasedEnrollmentSupportAPIView
from .views.index import index
from .views.manage_user import ManageUserDetailView, ManageUserSupportView
from .views.program_enrollments import LinkProgramEnrollmentSupportView, ProgramEnrollmentsInspectorView
@@ -40,6 +40,11 @@ urlpatterns = [
FeatureBasedEnrollmentsSupportView.as_view(),
name="feature_based_enrollments"
),
url(
fr'^feature_based_enrollment_details/{settings.COURSE_ID_PATTERN}$',
FeatureBasedEnrollmentSupportAPIView.as_view(),
name="feature_based_enrollment_details"
),
url(r'link_program_enrollments/?$', LinkProgramEnrollmentSupportView.as_view(), name='link_program_enrollments'),
url(
r'program_enrollments_inspector/?$',

View File

@@ -2,18 +2,17 @@
Support tool for viewing course duration information
"""
from django.core.exceptions import ObjectDoesNotExist
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
from django.utils.decorators import method_decorator
from django.views.generic import View
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.generics import GenericAPIView
from common.djangoapps.edxmako.shortcuts import render_to_response
from common.djangoapps.util.json_request import JsonResponse
from lms.djangoapps.support.decorators import require_support_permission
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.features.content_type_gating.models import ContentTypeGatingConfig
from openedx.features.course_duration_limits.models import CourseDurationLimitConfig
from lms.djangoapps.support.views.utils import get_course_duration_info
class FeatureBasedEnrollmentsSupportView(View):
@@ -29,7 +28,7 @@ class FeatureBasedEnrollmentsSupportView(View):
course_key = request.GET.get('course_key', '')
if course_key:
results = self._get_course_duration_info(course_key)
results = get_course_duration_info(course_key)
else:
results = {}
@@ -38,35 +37,40 @@ class FeatureBasedEnrollmentsSupportView(View):
'results': results,
})
def _get_course_duration_info(self, course_key):
class FeatureBasedEnrollmentSupportAPIView(GenericAPIView):
"""
Support-only API View for getting feature based enrollment configuration details
for a course.
"""
authentication_classes = (
JwtAuthentication, SessionAuthentication
)
permission_classes = (IsAuthenticated,)
@method_decorator(require_support_permission)
def get(self, request, course_id):
"""
Fetch course duration information from database
Returns the duration config information if FBE is enabled. If
FBE is not enabled, empty dict is returned.
* Example Request:
- GET /support/feature_based_enrollment_details/<course_id>
* Example Response:
{
"course_id": <course_id>,
"course_name": "FBE course",
"gating_config": {
"enabled": true,
"enabled_as_of": "2030-01-01 00:00:00+00:00",
"reason": "Site"
},
"duration_config": {
"enabled": true,
"enabled_as_of": "2030-01-01 00:00:00+00:00",
"reason": "Site"
}
}
"""
try:
key = CourseKey.from_string(course_key)
course = CourseOverview.objects.values('display_name').get(id=key)
duration_config = CourseDurationLimitConfig.current(course_key=key)
gating_config = ContentTypeGatingConfig.current(course_key=key)
duration_enabled = CourseDurationLimitConfig.enabled_for_course(course_key=key)
gating_enabled = ContentTypeGatingConfig.enabled_for_course(course_key=key)
gating_dict = {
'enabled': gating_enabled,
'enabled_as_of': str(gating_config.enabled_as_of) if gating_config.enabled_as_of else 'N/A',
'reason': gating_config.provenances['enabled'].value
}
duration_dict = {
'enabled': duration_enabled,
'enabled_as_of': str(duration_config.enabled_as_of) if duration_config.enabled_as_of else 'N/A',
'reason': duration_config.provenances['enabled'].value
}
return {
'course_id': course_key,
'course_name': course.get('display_name'),
'gating_config': gating_dict,
'duration_config': duration_dict,
}
except (ObjectDoesNotExist, InvalidKeyError):
return {}
return JsonResponse(get_course_duration_info(course_id))

View File

@@ -0,0 +1,44 @@
"""
Various utility methods used by support app views.
"""
from django.core.exceptions import ObjectDoesNotExist
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.features.content_type_gating.models import ContentTypeGatingConfig
from openedx.features.course_duration_limits.models import CourseDurationLimitConfig
def get_course_duration_info(course_key):
"""
Fetch course duration information from database.
"""
try:
key = CourseKey.from_string(course_key)
course = CourseOverview.objects.values('display_name').get(id=key)
duration_config = CourseDurationLimitConfig.current(course_key=key)
gating_config = ContentTypeGatingConfig.current(course_key=key)
duration_enabled = CourseDurationLimitConfig.enabled_for_course(course_key=key)
gating_enabled = ContentTypeGatingConfig.enabled_for_course(course_key=key)
gating_dict = {
'enabled': gating_enabled,
'enabled_as_of': str(gating_config.enabled_as_of) if gating_config.enabled_as_of else 'N/A',
'reason': gating_config.provenances['enabled'].value
}
duration_dict = {
'enabled': duration_enabled,
'enabled_as_of': str(duration_config.enabled_as_of) if duration_config.enabled_as_of else 'N/A',
'reason': duration_config.provenances['enabled'].value
}
return {
'course_id': course_key,
'course_name': course.get('display_name'),
'gating_config': gating_dict,
'duration_config': duration_dict,
}
except (ObjectDoesNotExist, InvalidKeyError):
return {}