Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 30 additions & 53 deletions lms/djangoapps/support/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,6 @@
from pytz import UTC
from rest_framework import status
from social_django.models import UserSocialAuth
from xmodule.modulestore.tests.django_utils import (
TEST_DATA_SPLIT_MODULESTORE, ModuleStoreTestCase, SharedModuleStoreTestCase,
)
from xmodule.modulestore.tests.factories import CourseFactory

from common.djangoapps.course_modes.models import CourseMode
from common.djangoapps.course_modes.tests.factories import CourseModeFactory
Expand All @@ -47,33 +43,29 @@
)
from common.djangoapps.student.roles import GlobalStaff, SupportStaffRole
from common.djangoapps.student.tests.factories import (
CourseEnrollmentFactory,
CourseEnrollmentAttributeFactory,
UserFactory,
CourseEnrollmentFactory,
UserFactory
)
from common.djangoapps.third_party_auth.tests.factories import SAMLProviderConfigFactory
from common.test.utils import disable_signal, assert_dict_contains_subset
from common.test.utils import assert_dict_contains_subset, disable_signal
from lms.djangoapps.program_enrollments.tests.factories import ProgramCourseEnrollmentFactory, ProgramEnrollmentFactory
from lms.djangoapps.support.models import CourseResetAudit
from lms.djangoapps.support.serializers import ProgramEnrollmentSerializer
from lms.djangoapps.support.tests.factories import CourseResetCourseOptInFactory, CourseResetAuditFactory
from lms.djangoapps.support.tests.factories import CourseResetAuditFactory, CourseResetCourseOptInFactory
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.core.djangoapps.oauth_dispatch.tests import factories
from openedx.features.content_type_gating.models import ContentTypeGatingConfig
from openedx.features.course_duration_limits.models import CourseDurationLimitConfig
from openedx.features.enterprise_support.api import enterprise_is_enabled
from openedx.features.enterprise_support.tests.factories import (
EnterpriseCourseEnrollmentFactory,
EnterpriseCustomerUserFactory
from xmodule.modulestore.tests.django_utils import (
TEST_DATA_SPLIT_MODULESTORE,
ModuleStoreTestCase,
SharedModuleStoreTestCase
)

try:
from consent.models import DataSharingConsent
except ImportError: # pragma: no cover
pass
from xmodule.modulestore.tests.factories import CourseFactory


class SupportViewTestCase(ModuleStoreTestCase):
Expand Down Expand Up @@ -357,7 +349,7 @@ def test_get_enrollments(self, search_string_type):
)
assert {CourseMode.VERIFIED, CourseMode.AUDIT, CourseMode.HONOR, CourseMode.NO_ID_PROFESSIONAL_MODE,
CourseMode.PROFESSIONAL, CourseMode.CREDIT_MODE} == {mode['slug'] for mode in data[0]['course_modes']}
assert 'enterprise_course_enrollments' not in data[0]
assert data[0]['enterprise_course_enrollments'] == []
assert data[0]['order_number'] == ''
assert data[0]['source_system'] == ''

Expand Down Expand Up @@ -398,52 +390,37 @@ def test_order_source_system_information(self):
assert len(data) == 1
assert data[0]['source_system'] == 'commercetools'

@override_settings(FEATURES=dict(ENABLE_ENTERPRISE_INTEGRATION=True))
@enterprise_is_enabled()
def test_get_enrollments_enterprise_enabled(self):
@patch(
'lms.djangoapps.support.views.enrollments.SupportEnrollmentDataRequested.run_filter'
)
def test_get_enrollments_with_enterprise_filter(self, mock_run_filter):
"""
Test that enterprise enrollment data from the filter is included in the response.
"""
course_id = str(self.course.id)
mock_enterprise_enrollment = {
'course_id': course_id,
'enterprise_customer_name': 'Test Enterprise',
'enterprise_customer_user_id': 42,
'license': None,
'saved_for_later': False,
'data_sharing_consent': None,
}
mock_run_filter.return_value = {course_id: [mock_enterprise_enrollment]}

url = reverse(
'support:enrollment_list',
kwargs={'username_or_email': self.student.username}
)

enterprise_customer_user = EnterpriseCustomerUserFactory(
user_id=self.student.id
)
enterprise_course_enrollment = EnterpriseCourseEnrollmentFactory(
course_id=self.course.id,
enterprise_customer_user=enterprise_customer_user
)
data_sharing_consent = DataSharingConsent(
course_id=self.course.id,
enterprise_customer=enterprise_customer_user.enterprise_customer,
username=self.student.username,
granted=True
)
data_sharing_consent.save()

response = self.client.get(url)
assert response.status_code == 200
data = json.loads(response.content.decode('utf-8'))
assert len(data) == 1

mock_run_filter.assert_called_once()
enterprise_course_enrollments_data = data[0]['enterprise_course_enrollments']
assert len(enterprise_course_enrollments_data) == 1
expected = {
'course_id': str(enterprise_course_enrollment.course_id),
'enterprise_customer_name': enterprise_customer_user.enterprise_customer.name,
'enterprise_customer_user_id': enterprise_customer_user.id,
'license': None,
'saved_for_later': enterprise_course_enrollment.saved_for_later,
'data_sharing_consent': {
'username': self.student.username,
'enterprise_customer_uuid': str(enterprise_customer_user.enterprise_customer_id),
'exists': data_sharing_consent.exists,
'consent_provided': data_sharing_consent.granted,
'consent_required': data_sharing_consent.consent_required(),
'course_id': str(enterprise_course_enrollment.course_id),
}
}
assert enterprise_course_enrollments_data[0] == expected
assert enterprise_course_enrollments_data[0] == mock_enterprise_enrollment

@ddt.data(
(True, 'Self Paced'),
Expand Down
8 changes: 4 additions & 4 deletions lms/djangoapps/support/views/contact_us.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
from django.http import Http404
from django.shortcuts import redirect
from django.views.generic import View
from openedx_filters.learning.filters import SupportContactContextRequested

from common.djangoapps.edxmako.shortcuts import render_to_response
from common.djangoapps.student.models import CourseEnrollment
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.features.enterprise_support import api as enterprise_api


class ContactUsView(View):
Expand Down Expand Up @@ -46,9 +46,9 @@ def get(self, request): # lint-amnesty, pylint: disable=missing-function-docstr
if request.user.is_authenticated:
context['course_id'] = request.session.get('course_id', '')
context['user_enrollments'] = CourseEnrollment.enrollments_for_user_with_overviews_preload(request.user)
enterprise_customer = enterprise_api.enterprise_customer_for_request(request)
if enterprise_customer:
tags.append('enterprise_learner')
tags = SupportContactContextRequested.run_filter(
tags=tags, request=request, user=request.user
)

context['tags'] = tags

Expand Down
52 changes: 9 additions & 43 deletions lms/djangoapps/support/views/enrollments.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
Support tool for changing course enrollments.
"""
import logging
from collections import defaultdict

import markupsafe
from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user
Expand All @@ -14,6 +13,7 @@
from django.views.generic import View
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from openedx_filters.learning.filters import SupportEnrollmentDataRequested
from rest_framework.generics import GenericAPIView

from common.djangoapps.course_modes.models import CourseMode
Expand All @@ -31,16 +31,9 @@
from lms.djangoapps.support.serializers import ManualEnrollmentSerializer
from lms.djangoapps.verify_student.models import VerificationDeadline
from openedx.core.djangoapps.credit.email_utils import get_credit_provider_attribute_values
from openedx.core.djangoapps.enrollments.api import get_enrollments, get_enrollment_attributes, update_enrollment
from openedx.core.djangoapps.enrollments.api import get_enrollment_attributes, get_enrollments, update_enrollment
from openedx.core.djangoapps.enrollments.errors import CourseModeNotFoundError
from openedx.core.djangoapps.enrollments.serializers import ModeSerializer
from openedx.features.enterprise_support.api import (
enterprise_enabled,
get_data_sharing_consents,
get_enterprise_course_enrollments
)
from openedx.features.enterprise_support.serializers import EnterpriseCourseEnrollmentSerializer


logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -71,35 +64,6 @@ class EnrollmentSupportListView(GenericAPIView):
# does not specify a serializer class.
exclude_from_schema = True

def _enterprise_course_enrollments_by_course_id(self, user):
"""
Returns a dict containing enterprise course enrollments data with
course ids as keys.
"""
enterprise_course_enrollments = get_enterprise_course_enrollments(user)
data_sharing_consents_for_user = get_data_sharing_consents(user)

enterprise_enrollments_by_course_id = defaultdict(list)
consent_by_course_and_enterprise_customer_id = {}

# Get data sharing consent for each enterprise enrollment
for consent in data_sharing_consents_for_user:
key = f'{consent.course_id}-{consent.enterprise_customer_id}'
consent_by_course_and_enterprise_customer_id[key] = consent.serialize()

for enterprise_course_enrollment in enterprise_course_enrollments:
serialized_enterprise_course_enrollment = EnterpriseCourseEnrollmentSerializer(
enterprise_course_enrollment
).data
course_id = enterprise_course_enrollment.course_id
enterprise_customer_id = enterprise_course_enrollment.enterprise_customer_user.enterprise_customer_id
key = f'{course_id}-{enterprise_customer_id}'
consent = consent_by_course_and_enterprise_customer_id.get(key)
serialized_enterprise_course_enrollment['data_sharing_consent'] = consent
enterprise_enrollments_by_course_id[course_id].append(serialized_enterprise_course_enrollment)

return enterprise_enrollments_by_course_id

@method_decorator(require_support_permission)
def get(self, request, username_or_email):
"""
Expand Down Expand Up @@ -127,11 +91,13 @@ def get(self, request, username_or_email):
# Add manual enrollment history, if it exists
enrollment['manual_enrollment'] = self.manual_enrollment_data(enrollment, course_key)

if enterprise_enabled():
enterprise_enrollments_by_course_id = self._enterprise_course_enrollments_by_course_id(user)
for enrollment in enrollments:
enterprise_course_enrollments = enterprise_enrollments_by_course_id.get(enrollment['course_id'], [])
enrollment['enterprise_course_enrollments'] = enterprise_course_enrollments
enterprise_enrollments_by_course_id = SupportEnrollmentDataRequested.run_filter(
enrollment_data={}, user=user
)
for enrollment in enrollments:
enrollment['enterprise_course_enrollments'] = enterprise_enrollments_by_course_id.get(
enrollment['course_id'], []
)

return JsonResponse(enrollments)

Expand Down
51 changes: 51 additions & 0 deletions lms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -3172,3 +3172,54 @@ def _should_send_certificate_events(settings):
SSL_AUTH_DN_FORMAT_STRING = (
"/C=US/ST=Massachusetts/O=Massachusetts Institute of Technology/OU=Client CA v1/CN={0}/emailAddress={1}"
)

# .. setting_name: OPEN_EDX_FILTERS_CONFIG
# .. setting_default: {}
# .. setting_description: Configuration dict for openedx-filters pipeline steps.
# Keys are filter type strings; values are dicts with 'fail_silently' (bool) and
# 'pipeline' (list of dotted-path strings to PipelineStep subclasses).
OPEN_EDX_FILTERS_CONFIG = {
"org.openedx.learning.grade.context.requested.v1": {
"fail_silently": True,
"pipeline": ["enterprise.filters.grades.GradeEventContextEnricher"],
},
"org.openedx.learning.account.settings.read_only_fields.requested.v1": {
"fail_silently": True,
"pipeline": ["enterprise.filters.accounts.AccountSettingsReadOnlyFieldsStep"],
},
"org.openedx.learning.discount.eligibility.check.requested.v1": {
"fail_silently": True,
"pipeline": ["enterprise.filters.discounts.DiscountEligibilityStep"],
},
"org.openedx.learning.courseware.view.redirect_url.requested.v1": {
"fail_silently": True,
"pipeline": [
"enterprise.filters.courseware.ConsentRedirectStep",
"enterprise.filters.courseware.LearnerPortalRedirectStep",
],
},
"org.openedx.learning.logistration.context.requested.v1": {
"fail_silently": True,
"pipeline": ["enterprise.filters.logistration.LogistrationContextEnricher"],
},
"org.openedx.learning.auth.post_login.redirect_url.requested.v1": {
"fail_silently": True,
"pipeline": ["enterprise.filters.logistration.PostLoginEnterpriseRedirect"],
},
"org.openedx.learning.dashboard.render.started.v1": {
"fail_silently": True,
"pipeline": ["enterprise.filters.dashboard.DashboardContextEnricher"],
},
"org.openedx.learning.course_mode.checkout.started.v1": {
"fail_silently": True,
"pipeline": ["enterprise.filters.course_modes.CourseModeEnterpriseStep"],
},
"org.openedx.learning.support.contact.context.requested.v1": {
"fail_silently": True,
"pipeline": ["enterprise.filters.support.SupportContactEnterpriseTagInjector"],
},
"org.openedx.learning.support.enrollment.data.requested.v1": {
"fail_silently": True,
"pipeline": ["enterprise.filters.support.SupportEnterpriseEnrollmentDataInjector"],
},
}
14 changes: 14 additions & 0 deletions lms/envs/production.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ def get_env_setting(setting):
'EVENT_BUS_PRODUCER_CONFIG',
'DEFAULT_FILE_STORAGE',
'STATICFILES_STORAGE',
'OPEN_EDX_FILTERS_CONFIG',
]
})

Expand Down Expand Up @@ -515,6 +516,19 @@ def get_env_setting(setting):
_YAML_TOKENS.get('EVENT_BUS_PRODUCER_CONFIG', {})
)

# Merge OPEN_EDX_FILTERS_CONFIG from YAML into the default defined in common.py.
# Pipeline steps from YAML are appended after steps defined in common.py.
# The fail_silently value from YAML takes precedence over the one in common.py.
for _filter_type, _filter_config in _YAML_TOKENS.get('OPEN_EDX_FILTERS_CONFIG', {}).items():
if _filter_type in OPEN_EDX_FILTERS_CONFIG:
OPEN_EDX_FILTERS_CONFIG[_filter_type]['pipeline'].extend(
_filter_config.get('pipeline', [])
)
if 'fail_silently' in _filter_config:
OPEN_EDX_FILTERS_CONFIG[_filter_type]['fail_silently'] = _filter_config['fail_silently']
else:
OPEN_EDX_FILTERS_CONFIG[_filter_type] = _filter_config

#######################################################################################################################
# HEY! Don't add anything to the end of this file.
# Add your defaults to common.py instead!
Expand Down
Loading