From 5a1fc5364e8815c698d8ff3420ac5152f7921d89 Mon Sep 17 00:00:00 2001 From: Edward Zarecor Date: Fri, 13 Jan 2023 16:50:07 -0500 Subject: [PATCH 1/6] chore: refactor toward our standard --- common/djangoapps/student/api.py | 163 +++++++++++++++-- common/djangoapps/student/models_api.py | 167 ------------------ .../djangoapps/student/signals/receivers.py | 2 +- .../djangoapps/student/tests/test_models.py | 2 +- lms/djangoapps/certificates/models.py | 3 +- lms/djangoapps/certificates/utils.py | 3 +- lms/djangoapps/verify_student/signals.py | 2 +- .../verify_student/tests/test_signals.py | 2 +- .../user_api/accounts/tests/test_views.py | 2 +- .../djangoapps/user_api/accounts/views.py | 2 +- 10 files changed, 161 insertions(+), 187 deletions(-) delete mode 100644 common/djangoapps/student/models_api.py diff --git a/common/djangoapps/student/api.py b/common/djangoapps/student/api.py index 2bf42f48289c..2162f29af70c 100644 --- a/common/djangoapps/student/api.py +++ b/common/djangoapps/student/api.py @@ -1,20 +1,18 @@ -# pylint: disable=unused-import + # pylint: disable=unused-import """ Python APIs exposed by the student app to other in-process apps. """ - - +import datetime import logging from django.contrib.auth import get_user_model -from django.conf import settings from opaque_keys.edx.keys import CourseKey +from pytz import UTC -from common.djangoapps.student.models import CourseEnrollment -from common.djangoapps.student.models_api import create_manual_enrollment_audit as _create_manual_enrollment_audit -from common.djangoapps.student.models_api import get_course_access_role -from common.djangoapps.student.models_api import get_course_enrollment as _get_course_enrollment -from common.djangoapps.student.models_api import ( +from common.djangoapps.student.models import CourseEnrollment as _CourseEnrollment, UserProfile as _UserProfile, \ + CourseAccessRole as _CourseAccessRole, PendingNameChange as _PendingNameChange, \ + ManualEnrollmentAudit as _ManualEnrollmentAudit +from common.djangoapps.student.models import ( ENROLLED_TO_ENROLLED as _ENROLLED_TO_ENROLLED, ENROLLED_TO_UNENROLLED as _ENROLLED_TO_UNENROLLED, UNENROLLED_TO_ENROLLED as _UNENROLLED_TO_ENROLLED, @@ -30,7 +28,6 @@ GlobalStaff, REGISTERED_ACCESS_ROLES as _REGISTERED_ACCESS_ROLES, ) -from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers # This is done so that if these strings change within the app, we can keep exported constants the same @@ -59,6 +56,35 @@ log = logging.getLogger() +def _create_manual_enrollment_audit( + enrolled_by, + user_email, + state_transition, + reason, + course_enrollment +): + """ + Creates a record of a student being manually enrolled in a course via the ManualEnrollmentAudit + model. The corresponding StudentEnrollment is not created by this function. + @param enrolled_by: + @param user_email: + @param state_transition: + @param reason: + @param course_enrollment: + """ + _ManualEnrollmentAudit.create_manual_enrollment_audit( + user=enrolled_by, + email=user_email, + state_transition=state_transition, + reason=reason, + enrollment=course_enrollment, + ) + + +def get_course_enrollment(user, course_run_key): + return _CourseEnrollment.get_enrollment(user, course_run_key) + + def create_manual_enrollment_audit( enrolled_by, user_email, @@ -88,7 +114,7 @@ def create_manual_enrollment_audit( enrolled_user = None if enrolled_user and course_run_key: - enrollment = _get_course_enrollment(enrolled_user, course_run_key) + enrollment = get_course_enrollment(enrolled_user, course_run_key) else: enrollment = None @@ -119,7 +145,7 @@ def is_user_enrolled_in_course(student, course_key): Determines if a learner is enrolled in a given course-run. """ log.info(f"Checking if {student.id} is enrolled in course {course_key}") - return CourseEnrollment.is_enrolled(student, course_key) + return _CourseEnrollment.is_enrolled(student, course_key) def is_user_staff_or_instructor_in_course(user, course_key): @@ -136,3 +162,116 @@ def is_user_staff_or_instructor_in_course(user, course_key): CourseStaffRole(course_key).has_user(user) or CourseInstructorRole(course_key).has_user(user) ) + + +def get_phone_number(user_id): + """ + Get a user's phone number from the profile, if + one exists. Otherwise, return None. + """ + try: + student = _UserProfile.objects.get(user_id=user_id) + except _UserProfile.DoesNotExist as exception: + log.exception(exception) + return None + return student.phone_number or None + + +def get_name(user_id): + """ + Get a user's name from their profile, if one exists. Otherwise, return None. + """ + try: + student = _UserProfile.objects.get(user_id=user_id) + except _UserProfile.DoesNotExist: + log.exception(f'Could not find UserProfile for id {user_id}') + return None + return student.name or None + + +def get_course_access_role(user, org, course_id, role): + """ + Get a specific CourseAccessRole object. Return None if + it does not exist. + + Arguments: + user: User object for the user who has access in a course + org: the org the course is in + course_id: the course_id of the CourseAccessRole + role: the role type of the role + """ + try: + course_access_role = _CourseAccessRole.objects.get( + user=user, + org=org, + course_id=course_id, + role=role, + ) + except _CourseAccessRole.DoesNotExist: + log.exception('No CourseAccessRole found for user_id=%(user_id)s, org=%(org)s, ' + 'course_id=%(course_id)s, and role=%(role)s.', { + 'user': user.id, + 'org': org, + 'course_id': course_id, + 'role': role, + }) + return None + return course_access_role + + +def get_pending_name_change(user): + """ + Return the user's pending name change, or None if it does not exist. + """ + try: + pending_name_change = _PendingNameChange.objects.get(user=user) + return pending_name_change + except _PendingNameChange.DoesNotExist: + return None + + +def do_name_change_request(user, new_name, rationale): + """ + Create a name change request. This either updates the user's current PendingNameChange, or creates + a new one if it doesn't exist. Returns the PendingNameChange object and a boolean describing whether + or not a new one was created. + """ + user_profile = _UserProfile.objects.get(user=user) + if user_profile.name == new_name: + log_msg = ( + 'user_id={user_id} requested a name change, but the requested name is the same as' + 'their current profile name. Not taking any action.'.format(user_id=user.id) + ) + log.warning(log_msg) + return None, False + + pending_name_change, created = _PendingNameChange.objects.update_or_create( + user=user, + defaults={ + 'new_name': new_name, + 'rationale': rationale + } + ) + + return pending_name_change, created + + +def confirm_name_change(user, pending_name_change): + """ + Confirm a pending name change. This updates the user's profile name and deletes the + PendingNameChange object. + """ + user_profile = _UserProfile.objects.get(user=user) + + # Store old name in profile metadata + meta = user_profile.get_meta() + if 'old_names' not in meta: + meta['old_names'] = [] + meta['old_names'].append( + [user_profile.name, pending_name_change.rationale, datetime.datetime.now(UTC).isoformat()] + ) + user_profile.set_meta(meta) + + user_profile.name = pending_name_change.new_name + user_profile.save() + pending_name_change.delete() diff --git a/common/djangoapps/student/models_api.py b/common/djangoapps/student/models_api.py deleted file mode 100644 index 997f5454db53..000000000000 --- a/common/djangoapps/student/models_api.py +++ /dev/null @@ -1,167 +0,0 @@ -""" -Provides Python APIs exposed from Student models. -""" -import datetime -import logging - -from pytz import UTC - -from common.djangoapps.student.models import CourseAccessRole as _CourseAccessRole -from common.djangoapps.student.models import CourseEnrollment as _CourseEnrollment -from common.djangoapps.student.models import ManualEnrollmentAudit as _ManualEnrollmentAudit -from common.djangoapps.student.models import ( - ENROLLED_TO_ENROLLED as _ENROLLED_TO_ENROLLED, - ENROLLED_TO_UNENROLLED as _ENROLLED_TO_UNENROLLED, - UNENROLLED_TO_ENROLLED as _UNENROLLED_TO_ENROLLED, - UNENROLLED_TO_UNENROLLED as _UNENROLLED_TO_UNENROLLED, - UNENROLLED_TO_ALLOWEDTOENROLL as _UNENROLLED_TO_ALLOWEDTOENROLL, - ALLOWEDTOENROLL_TO_ENROLLED as _ALLOWEDTOENROLL_TO_ENROLLED, - ALLOWEDTOENROLL_TO_UNENROLLED as _ALLOWEDTOENROLL_TO_UNENROLLED, - DEFAULT_TRANSITION_STATE as _DEFAULT_TRANSITION_STATE, -) -from common.djangoapps.student.models import PendingNameChange as _PendingNameChange -from common.djangoapps.student.models import UserProfile as _UserProfile - -# This is done so that if these strings change within the app, we can keep exported constants the same -ENROLLED_TO_ENROLLED = _ENROLLED_TO_ENROLLED -ENROLLED_TO_UNENROLLED = _ENROLLED_TO_UNENROLLED -UNENROLLED_TO_ENROLLED = _UNENROLLED_TO_ENROLLED -UNENROLLED_TO_UNENROLLED = _UNENROLLED_TO_UNENROLLED -UNENROLLED_TO_ALLOWEDTOENROLL = _UNENROLLED_TO_ALLOWEDTOENROLL -ALLOWEDTOENROLL_TO_ENROLLED = _ALLOWEDTOENROLL_TO_ENROLLED -ALLOWEDTOENROLL_TO_UNENROLLED = _ALLOWEDTOENROLL_TO_UNENROLLED -DEFAULT_TRANSITION_STATE = _DEFAULT_TRANSITION_STATE -log = logging.getLogger(__name__) - - -def create_manual_enrollment_audit( # lint-amnesty, pylint: disable=missing-function-docstring - enrolled_by, - user_email, - state_transition, - reason, - course_enrollment -): - _ManualEnrollmentAudit.create_manual_enrollment_audit( - user=enrolled_by, - email=user_email, - state_transition=state_transition, - reason=reason, - enrollment=course_enrollment, - ) - - -def get_course_enrollment(user, course_run_key): - return _CourseEnrollment.get_enrollment(user, course_run_key) - - -def get_phone_number(user_id): - """ - Get a user's phone number from the profile, if - one exists. Otherwise, return None. - """ - try: - student = _UserProfile.objects.get(user_id=user_id) - except _UserProfile.DoesNotExist as exception: - log.exception(exception) - return None - return student.phone_number or None - - -def get_name(user_id): - """ - Get a user's name from their profile, if one exists. Otherwise, return None. - """ - try: - student = _UserProfile.objects.get(user_id=user_id) - except _UserProfile.DoesNotExist: - log.exception(f'Could not find UserProfile for id {user_id}') - return None - return student.name or None - - -def get_course_access_role(user, org, course_id, role): - """ - Get a specific CourseAccessRole object. Return None if - it does not exist. - - Arguments: - user: User object for the user who has access in a course - org: the org the course is in - course_id: the course_id of the CourseAccessRole - role: the role type of the role - """ - try: - course_access_role = _CourseAccessRole.objects.get( - user=user, - org=org, - course_id=course_id, - role=role, - ) - except _CourseAccessRole.DoesNotExist: - log.exception('No CourseAccessRole found for user_id=%(user_id)s, org=%(org)s, ' - 'course_id=%(course_id)s, and role=%(role)s.', { - 'user': user.id, - 'org': org, - 'course_id': course_id, - 'role': role, - }) - return None - return course_access_role - - -def get_pending_name_change(user): - """ - Return the user's pending name change, or None if it does not exist. - """ - try: - pending_name_change = _PendingNameChange.objects.get(user=user) - return pending_name_change - except _PendingNameChange.DoesNotExist: - return None - - -def do_name_change_request(user, new_name, rationale): - """ - Create a name change request. This either updates the user's current PendingNameChange, or creates - a new one if it doesn't exist. Returns the PendingNameChange object and a boolean describing whether - or not a new one was created. - """ - user_profile = _UserProfile.objects.get(user=user) - if user_profile.name == new_name: - log_msg = ( - 'user_id={user_id} requested a name change, but the requested name is the same as' - 'their current profile name. Not taking any action.'.format(user_id=user.id) - ) - log.warning(log_msg) - return None, False - - pending_name_change, created = _PendingNameChange.objects.update_or_create( - user=user, - defaults={ - 'new_name': new_name, - 'rationale': rationale - } - ) - - return pending_name_change, created - - -def confirm_name_change(user, pending_name_change): - """ - Confirm a pending name change. This updates the user's profile name and deletes the - PendingNameChange object. - """ - user_profile = _UserProfile.objects.get(user=user) - - # Store old name in profile metadata - meta = user_profile.get_meta() - if 'old_names' not in meta: - meta['old_names'] = [] - meta['old_names'].append( - [user_profile.name, pending_name_change.rationale, datetime.datetime.now(UTC).isoformat()] - ) - user_profile.set_meta(meta) - - user_profile.name = pending_name_change.new_name - user_profile.save() - pending_name_change.delete() diff --git a/common/djangoapps/student/signals/receivers.py b/common/djangoapps/student/signals/receivers.py index 6d0cf4f061ca..115dfcd86154 100644 --- a/common/djangoapps/student/signals/receivers.py +++ b/common/djangoapps/student/signals/receivers.py @@ -21,7 +21,7 @@ is_email_retired, is_username_retired ) -from common.djangoapps.student.models_api import confirm_name_change +from common.djangoapps.student.api import confirm_name_change from common.djangoapps.student.signals import USER_EMAIL_CHANGED from openedx.features.name_affirmation_api.utils import is_name_affirmation_installed diff --git a/common/djangoapps/student/tests/test_models.py b/common/djangoapps/student/tests/test_models.py index 2388a21c7d5d..e9e6740d1acb 100644 --- a/common/djangoapps/student/tests/test_models.py +++ b/common/djangoapps/student/tests/test_models.py @@ -31,7 +31,7 @@ UserCelebration, UserProfile ) -from common.djangoapps.student.models_api import confirm_name_change, do_name_change_request, get_name +from common.djangoapps.student.api import get_name, do_name_change_request, confirm_name_change from common.djangoapps.student.tests.factories import AccountRecoveryFactory, CourseEnrollmentFactory, UserFactory from lms.djangoapps.courseware.models import DynamicUpgradeDeadlineConfiguration from lms.djangoapps.courseware.toggles import ( diff --git a/lms/djangoapps/certificates/models.py b/lms/djangoapps/certificates/models.py index dd6be3ccbdbd..0d9a759e89d1 100644 --- a/lms/djangoapps/certificates/models.py +++ b/lms/djangoapps/certificates/models.py @@ -23,6 +23,7 @@ from opaque_keys.edx.django.models import CourseKeyField from simple_history.models import HistoricalRecords +import common.djangoapps.student.api from common.djangoapps.student import models_api as student_api from common.djangoapps.student.models import CourseEnrollment from common.djangoapps.util.milestones_helpers import fulfill_course_milestone, is_prerequisite_courses_enabled @@ -439,7 +440,7 @@ def _get_preferred_certificate_name(self, user): Copy of `get_preferred_certificate_name` from utils.py - importing it here would introduce a circular dependency. """ - name_to_use = student_api.get_name(user.id) + name_to_use = common.djangoapps.student.api.get_name(user.id) name_affirmation_service = get_name_affirmation_service() if name_affirmation_service and name_affirmation_service.should_use_verified_name_for_certs(user): diff --git a/lms/djangoapps/certificates/utils.py b/lms/djangoapps/certificates/utils.py index 725c54ac09bc..e7cb179d4e24 100644 --- a/lms/djangoapps/certificates/utils.py +++ b/lms/djangoapps/certificates/utils.py @@ -10,6 +10,7 @@ from opaque_keys.edx.keys import CourseKey from pytz import utc +import common.djangoapps.student.api from common.djangoapps.student import models_api as student_api from lms.djangoapps.certificates.data import CertificateStatuses from lms.djangoapps.certificates.models import GeneratedCertificate @@ -238,7 +239,7 @@ def get_preferred_certificate_name(user): verified name for certificates, return their verified name. Else, return the user's profile name, or an empty string if it doesn't exist. """ - name_to_use = student_api.get_name(user.id) + name_to_use = common.djangoapps.student.api.get_name(user.id) name_affirmation_service = get_name_affirmation_service() if name_affirmation_service and name_affirmation_service.should_use_verified_name_for_certs(user): diff --git a/lms/djangoapps/verify_student/signals.py b/lms/djangoapps/verify_student/signals.py index d929af68dd06..cf1ad89c1266 100644 --- a/lms/djangoapps/verify_student/signals.py +++ b/lms/djangoapps/verify_student/signals.py @@ -9,7 +9,7 @@ from django.dispatch.dispatcher import receiver from xmodule.modulestore.django import SignalHandler, modulestore -from common.djangoapps.student.models_api import get_name, get_pending_name_change +from common.djangoapps.student.api import get_name, get_pending_name_change from openedx.core.djangoapps.user_api.accounts.signals import USER_RETIRE_LMS_CRITICAL from .models import SoftwareSecurePhotoVerification, VerificationDeadline diff --git a/lms/djangoapps/verify_student/tests/test_signals.py b/lms/djangoapps/verify_student/tests/test_signals.py index fb32edeccde0..c82c921b6585 100644 --- a/lms/djangoapps/verify_student/tests/test_signals.py +++ b/lms/djangoapps/verify_student/tests/test_signals.py @@ -8,7 +8,7 @@ from django.utils.timezone import now from unittest.mock import patch # lint-amnesty, pylint: disable=wrong-import-order -from common.djangoapps.student.models_api import do_name_change_request +from common.djangoapps.student.api import do_name_change_request from common.djangoapps.student.tests.factories import UserFactory from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification, VerificationDeadline from lms.djangoapps.verify_student.signals import _listen_for_course_publish, _listen_for_lms_retire diff --git a/openedx/core/djangoapps/user_api/accounts/tests/test_views.py b/openedx/core/djangoapps/user_api/accounts/tests/test_views.py index 63cb59cace5f..8ed7232eddf7 100644 --- a/openedx/core/djangoapps/user_api/accounts/tests/test_views.py +++ b/openedx/core/djangoapps/user_api/accounts/tests/test_views.py @@ -19,7 +19,7 @@ from rest_framework.test import APIClient, APITestCase from common.djangoapps.student.models import PendingEmailChange, UserProfile -from common.djangoapps.student.models_api import do_name_change_request, get_pending_name_change +from common.djangoapps.student.api import get_pending_name_change, do_name_change_request from common.djangoapps.student.tests.factories import ( TEST_PASSWORD, ContentTypeFactory, diff --git a/openedx/core/djangoapps/user_api/accounts/views.py b/openedx/core/djangoapps/user_api/accounts/views.py index a558227b2443..956bc01949c3 100644 --- a/openedx/core/djangoapps/user_api/accounts/views.py +++ b/openedx/core/djangoapps/user_api/accounts/views.py @@ -51,7 +51,7 @@ is_email_retired, is_username_retired ) -from common.djangoapps.student.models_api import confirm_name_change, do_name_change_request, get_pending_name_change +from common.djangoapps.student.api import get_pending_name_change, do_name_change_request, confirm_name_change from openedx.core.djangoapps.ace_common.template_context import get_base_template_context from openedx.core.djangoapps.api_admin.models import ApiAccessRequest from openedx.core.djangoapps.course_groups.models import UnregisteredLearnerCohortAssignments From b7179daaf5f237777435fb1895ce52f1e56350c7 Mon Sep 17 00:00:00 2001 From: Edward Zarecor Date: Fri, 13 Jan 2023 17:19:11 -0500 Subject: [PATCH 2/6] fix: outdated reference --- lms/djangoapps/certificates/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/djangoapps/certificates/models.py b/lms/djangoapps/certificates/models.py index 0d9a759e89d1..4d7f3828a716 100644 --- a/lms/djangoapps/certificates/models.py +++ b/lms/djangoapps/certificates/models.py @@ -24,7 +24,7 @@ from simple_history.models import HistoricalRecords import common.djangoapps.student.api -from common.djangoapps.student import models_api as student_api +from common.djangoapps.student import api as student_api from common.djangoapps.student.models import CourseEnrollment from common.djangoapps.util.milestones_helpers import fulfill_course_milestone, is_prerequisite_courses_enabled from lms.djangoapps.badges.events.course_complete import course_badge_check From 5227bb77f78d5c13c4eb2a868537bc2e8afeda87 Mon Sep 17 00:00:00 2001 From: Edward Zarecor Date: Fri, 13 Jan 2023 17:30:24 -0500 Subject: [PATCH 3/6] fix: missed refactor --- lms/djangoapps/certificates/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lms/djangoapps/certificates/utils.py b/lms/djangoapps/certificates/utils.py index e7cb179d4e24..d240f45baff5 100644 --- a/lms/djangoapps/certificates/utils.py +++ b/lms/djangoapps/certificates/utils.py @@ -11,7 +11,6 @@ from pytz import utc import common.djangoapps.student.api -from common.djangoapps.student import models_api as student_api from lms.djangoapps.certificates.data import CertificateStatuses from lms.djangoapps.certificates.models import GeneratedCertificate from openedx.core.djangoapps.content.course_overviews.api import get_course_overview_or_none From a20cd56c1bcebd852f76164eafd605f2ccfdb4e6 Mon Sep 17 00:00:00 2001 From: Edward Zarecor Date: Fri, 13 Jan 2023 17:49:43 -0500 Subject: [PATCH 4/6] fix: duplicated import --- lms/djangoapps/certificates/models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lms/djangoapps/certificates/models.py b/lms/djangoapps/certificates/models.py index 4d7f3828a716..438c9c456163 100644 --- a/lms/djangoapps/certificates/models.py +++ b/lms/djangoapps/certificates/models.py @@ -24,7 +24,6 @@ from simple_history.models import HistoricalRecords import common.djangoapps.student.api -from common.djangoapps.student import api as student_api from common.djangoapps.student.models import CourseEnrollment from common.djangoapps.util.milestones_helpers import fulfill_course_milestone, is_prerequisite_courses_enabled from lms.djangoapps.badges.events.course_complete import course_badge_check From 024451a12797aff03a58326cdb187757e6c9a132 Mon Sep 17 00:00:00 2001 From: Edward Zarecor Date: Fri, 13 Jan 2023 17:51:02 -0500 Subject: [PATCH 5/6] fix: accidental indent --- common/djangoapps/student/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/djangoapps/student/api.py b/common/djangoapps/student/api.py index 2162f29af70c..3462307158cf 100644 --- a/common/djangoapps/student/api.py +++ b/common/djangoapps/student/api.py @@ -1,4 +1,4 @@ - # pylint: disable=unused-import +# pylint: disable=unused-import """ Python APIs exposed by the student app to other in-process apps. """ From 83e3e17919d6d568f493653be6e7eef6218cf618 Mon Sep 17 00:00:00 2001 From: Edward Zarecor Date: Fri, 13 Jan 2023 17:55:25 -0500 Subject: [PATCH 6/6] fix: string refs to function --- lms/djangoapps/certificates/tests/test_generation.py | 2 +- lms/djangoapps/certificates/tests/test_models.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lms/djangoapps/certificates/tests/test_generation.py b/lms/djangoapps/certificates/tests/test_generation.py index 6b7f3f1f3f72..e463c2bf3142 100644 --- a/lms/djangoapps/certificates/tests/test_generation.py +++ b/lms/djangoapps/certificates/tests/test_generation.py @@ -19,7 +19,7 @@ log = logging.getLogger(__name__) -PROFILE_NAME_METHOD = 'common.djangoapps.student.models_api.get_name' +PROFILE_NAME_METHOD = 'common.djangoapps.student.api.get_name' name_affirmation_service = get_name_affirmation_service() diff --git a/lms/djangoapps/certificates/tests/test_models.py b/lms/djangoapps/certificates/tests/test_models.py index cd778bb8d8f2..0afef34f296b 100644 --- a/lms/djangoapps/certificates/tests/test_models.py +++ b/lms/djangoapps/certificates/tests/test_models.py @@ -42,7 +42,7 @@ from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order ENROLLMENT_METHOD = 'common.djangoapps.student.models.course_enrollment.CourseEnrollment.enrollment_mode_for_user' -PROFILE_METHOD = 'common.djangoapps.student.models_api.get_name' +PROFILE_METHOD = 'common.djangoapps.student.api.get_name' FEATURES_INVALID_FILE_PATH = settings.FEATURES.copy() FEATURES_INVALID_FILE_PATH['CERTS_HTML_VIEW_CONFIG_PATH'] = 'invalid/path/to/config.json'