diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index da4d6176c1..8919100d2f 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -54,16 +54,16 @@ from six.moves.urllib.parse import urlencode from slumber.exceptions import HttpClientError, HttpServerError from user_util import user_util -import openedx.core.djangoapps.django_comment_common.comment_client as cc from course_modes.models import CourseMode, get_cosmetic_verified_display_price from courseware.models import ( CourseDynamicUpgradeDeadlineConfiguration, DynamicUpgradeDeadlineConfiguration, OrgDynamicUpgradeDeadlineConfiguration ) -from enrollment.api import _default_course_mode from lms.djangoapps.certificates.models import GeneratedCertificate from openedx.core.djangoapps.content.course_overviews.models import CourseOverview +import openedx.core.djangoapps.django_comment_common.comment_client as cc +from openedx.core.djangoapps.enrollments.api import _default_course_mode from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from openedx.core.djangoapps.xmodule_django.models import NoneToEmptyManager from openedx.core.djangolib.model_mixins import DeletableByUserValue diff --git a/lms/djangoapps/bulk_email/models.py b/lms/djangoapps/bulk_email/models.py index 2ce7c182c6..630e12f99d 100644 --- a/lms/djangoapps/bulk_email/models.py +++ b/lms/djangoapps/bulk_email/models.py @@ -11,10 +11,10 @@ from opaque_keys.edx.django.models import CourseKeyField from six import text_type from course_modes.models import CourseMode -from enrollment.api import validate_course_mode -from enrollment.errors import CourseModeNotFoundError from openedx.core.djangoapps.course_groups.cohorts import get_cohort_by_name from openedx.core.djangoapps.course_groups.models import CourseUserGroup +from openedx.core.djangoapps.enrollments.api import validate_course_mode +from openedx.core.djangoapps.enrollments.errors import CourseModeNotFoundError from openedx.core.lib.html_to_text import html_to_text from openedx.core.lib.mail_utils import wrap_message from student.roles import CourseInstructorRole, CourseStaffRole diff --git a/lms/djangoapps/bulk_email/tests/test_email.py b/lms/djangoapps/bulk_email/tests/test_email.py index 95f8d56045..5d24bc4011 100644 --- a/lms/djangoapps/bulk_email/tests/test_email.py +++ b/lms/djangoapps/bulk_email/tests/test_email.py @@ -25,10 +25,10 @@ from bulk_email.models import BulkEmailFlag, Optout from bulk_email.tasks import _get_course_email_context, _get_source_address from course_modes.models import CourseMode from courseware.tests.factories import InstructorFactory, StaffFactory -from enrollment.api import update_enrollment from lms.djangoapps.instructor_task.subtasks import update_subtask_status from openedx.core.djangoapps.course_groups.cohorts import add_user_to_cohort from openedx.core.djangoapps.course_groups.models import CourseCohort +from openedx.core.djangoapps.enrollments.api import update_enrollment from student.models import CourseEnrollment from student.roles import CourseStaffRole from student.tests.factories import CourseEnrollmentFactory, UserFactory diff --git a/lms/djangoapps/bulk_enroll/views.py b/lms/djangoapps/bulk_enroll/views.py index 22cf97d309..f5a1bef36b 100644 --- a/lms/djangoapps/bulk_enroll/views.py +++ b/lms/djangoapps/bulk_enroll/views.py @@ -14,10 +14,10 @@ from rest_framework.response import Response from rest_framework.views import APIView from bulk_enroll.serializers import BulkEnrollmentSerializer -from enrollment.views import EnrollmentUserThrottle from instructor.views.api import students_update_enrollment from openedx.core.djangoapps.course_groups.cohorts import add_user_to_cohort, get_cohort_by_name from openedx.core.djangoapps.course_groups.models import CourseUserGroup +from openedx.core.djangoapps.enrollments.views import EnrollmentUserThrottle from openedx.core.lib.api.authentication import OAuth2Authentication from openedx.core.lib.api.permissions import IsStaff from util.disable_rate_limit import can_disable_rate_limit diff --git a/lms/djangoapps/commerce/api/v0/tests/test_views.py b/lms/djangoapps/commerce/api/v0/tests/test_views.py index 8724ffb86b..f274577ae0 100644 --- a/lms/djangoapps/commerce/api/v0/tests/test_views.py +++ b/lms/djangoapps/commerce/api/v0/tests/test_views.py @@ -14,8 +14,8 @@ from django.test.utils import override_settings from course_modes.models import CourseMode from course_modes.tests.factories import CourseModeFactory -from enrollment.api import get_enrollment from openedx.core.djangoapps.embargo.test_utils import restrict_course +from openedx.core.djangoapps.enrollments.api import get_enrollment from openedx.core.lib.django_test_client_utils import get_absolute_url from student.models import CourseEnrollment from student.tests.tests import EnrollmentEventTestMixin diff --git a/lms/djangoapps/commerce/api/v0/views.py b/lms/djangoapps/commerce/api/v0/views.py index 7a39f6ba44..77757c632d 100644 --- a/lms/djangoapps/commerce/api/v0/views.py +++ b/lms/djangoapps/commerce/api/v0/views.py @@ -14,11 +14,11 @@ from six import text_type from course_modes.models import CourseMode from courseware import courses -from enrollment.api import add_enrollment -from enrollment.views import EnrollmentCrossDomainSessionAuth from entitlements.models import CourseEntitlement from openedx.core.djangoapps.commerce.utils import ecommerce_api_client from openedx.core.djangoapps.embargo import api as embargo_api +from openedx.core.djangoapps.enrollments.api import add_enrollment +from openedx.core.djangoapps.enrollments.views import EnrollmentCrossDomainSessionAuth from openedx.core.djangoapps.user_api.preferences.api import update_email_opt_in from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUser from student.models import CourseEnrollment diff --git a/lms/djangoapps/courseware/courses.py b/lms/djangoapps/courseware/courses.py index cc6ee05f2e..4c154d454e 100644 --- a/lms/djangoapps/courseware/courses.py +++ b/lms/djangoapps/courseware/courses.py @@ -28,7 +28,6 @@ from django.conf import settings from django.db.models import Prefetch from django.urls import reverse from django.http import Http404, QueryDict -from enrollment.api import get_course_enrollment_details from edxmako.shortcuts import render_to_string from fs.errors import ResourceNotFound from lms.djangoapps.certificates import api as certs_api @@ -37,6 +36,7 @@ from lms.djangoapps.courseware.exceptions import CourseAccessRedirect from opaque_keys.edx.keys import UsageKey from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers +from openedx.core.djangoapps.enrollments.api import get_course_enrollment_details from openedx.core.lib.api.view_utils import LazySequence from openedx.features.course_experience import COURSE_ENABLE_UNENROLLED_ACCESS_FLAG from path import Path as path diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py index e0df65c83c..a5f6d4c3f0 100644 --- a/lms/djangoapps/courseware/views/views.py +++ b/lms/djangoapps/courseware/views/views.py @@ -59,7 +59,6 @@ from courseware.models import BaseStudentModuleHistory, StudentModule from courseware.url_helpers import get_redirect_url from courseware.user_state_client import DjangoXBlockUserStateClient from edxmako.shortcuts import marketing_link, render_to_response, render_to_string -from enrollment.api import add_enrollment from ipware.ip import get_ip from lms.djangoapps.ccx.custom_exception import CCXLocatorValidationException from lms.djangoapps.certificates import api as certs_api @@ -80,6 +79,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 from openedx.core.djangoapps.models.course_details import CourseDetails from openedx.core.djangoapps.plugin_api.views import EdxFragmentView from openedx.core.djangoapps.programs.utils import ProgramMarketingDataExtender diff --git a/lms/djangoapps/support/views/enrollments.py b/lms/djangoapps/support/views/enrollments.py index a6f12c76cc..54cec1e0f2 100644 --- a/lms/djangoapps/support/views/enrollments.py +++ b/lms/djangoapps/support/views/enrollments.py @@ -18,12 +18,12 @@ from six import text_type from course_modes.models import CourseMode from edxmako.shortcuts import render_to_response -from enrollment.api import get_enrollments, update_enrollment -from enrollment.errors import CourseModeNotFoundError -from enrollment.serializers import ModeSerializer from lms.djangoapps.support.decorators import require_support_permission from lms.djangoapps.support.serializers import ManualEnrollmentSerializer from lms.djangoapps.verify_student.models import VerificationDeadline +from openedx.core.djangoapps.enrollments.api import get_enrollments, update_enrollment +from openedx.core.djangoapps.enrollments.errors import CourseModeNotFoundError +from openedx.core.djangoapps.enrollments.serializers import ModeSerializer from student.models import ENROLLED_TO_ENROLLED, CourseEnrollment, ManualEnrollmentAudit from util.json_request import JsonResponse diff --git a/lms/envs/common.py b/lms/envs/common.py index 782908fdd8..1418842ae0 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -2156,7 +2156,7 @@ INSTALLED_APPS = [ 'course_modes.apps.CourseModesConfig', # Enrollment API - 'enrollment', + 'openedx.core.djangoapps.enrollments', # Entitlement API 'entitlements.apps.EntitlementsConfig', diff --git a/lms/urls.py b/lms/urls.py index 57199e19ff..2c40254d43 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -92,7 +92,7 @@ urlpatterns = [ url(r'^submit_feedback$', util_views.submit_feedback), # Enrollment API RESTful endpoints - url(r'^api/enrollment/v1/', include('enrollment.urls')), + url(r'^api/enrollment/v1/', include('openedx.core.djangoapps.enrollments.urls')), # Entitlement API RESTful endpoints url(r'^api/entitlements/', include('entitlements.api.urls', namespace='entitlements_api')), diff --git a/openedx/core/djangoapps/enrollments/README.rst b/openedx/core/djangoapps/enrollments/README.rst new file mode 100644 index 0000000000..59bb27645c --- /dev/null +++ b/openedx/core/djangoapps/enrollments/README.rst @@ -0,0 +1,16 @@ +Status: Maintenance + +Responsibilities +================ +The enrollments app provides basic CRUD functionality and APIs for managing Course-Run enrollments. +Enrollments in Programs is managed by the ``lms/djangoapps/program_enrollments\`` app. + +Direction: Keep +=============== + + +Glossary +======== + +More Documentation +================== diff --git a/common/djangoapps/enrollment/__init__.py b/openedx/core/djangoapps/enrollments/__init__.py similarity index 100% rename from common/djangoapps/enrollment/__init__.py rename to openedx/core/djangoapps/enrollments/__init__.py diff --git a/common/djangoapps/enrollment/api.py b/openedx/core/djangoapps/enrollments/api.py similarity index 99% rename from common/djangoapps/enrollment/api.py rename to openedx/core/djangoapps/enrollments/api.py index 05996519b1..c3b68f2701 100644 --- a/common/djangoapps/enrollment/api.py +++ b/openedx/core/djangoapps/enrollments/api.py @@ -13,11 +13,11 @@ from django.core.cache import cache from opaque_keys.edx.keys import CourseKey from course_modes.models import CourseMode -from enrollment import errors +from openedx.core.djangoapps.enrollments import errors log = logging.getLogger(__name__) -DEFAULT_DATA_API = 'enrollment.data' +DEFAULT_DATA_API = 'openedx.core.djangoapps.enrollments.data' def get_enrollments(user_id, include_inactive=False): @@ -324,7 +324,7 @@ def get_course_enrollment_details(course_id, include_expired=False): cached_enrollment_data = None try: cached_enrollment_data = cache.get(cache_key) - except Exception: + except Exception: # pylint: disable=broad-except # The cache backend could raise an exception (for example, memcache keys that contain spaces) log.exception(u"Error occurred while retrieving course enrollment details from the cache") diff --git a/common/djangoapps/enrollment/data.py b/openedx/core/djangoapps/enrollments/data.py similarity index 98% rename from common/djangoapps/enrollment/data.py rename to openedx/core/djangoapps/enrollments/data.py index b316a28c48..c8021c6777 100644 --- a/common/djangoapps/enrollment/data.py +++ b/openedx/core/djangoapps/enrollments/data.py @@ -11,15 +11,15 @@ from django.db import transaction from opaque_keys.edx.keys import CourseKey from six import text_type -from enrollment.errors import ( +from openedx.core.djangoapps.content.course_overviews.models import CourseOverview +from openedx.core.djangoapps.enrollments.errors import ( CourseEnrollmentClosedError, CourseEnrollmentExistsError, CourseEnrollmentFullError, InvalidEnrollmentAttribute, UserNotFoundError ) -from enrollment.serializers import CourseEnrollmentSerializer, CourseSerializer -from openedx.core.djangoapps.content.course_overviews.models import CourseOverview +from openedx.core.djangoapps.enrollments.serializers import CourseEnrollmentSerializer, CourseSerializer from openedx.core.lib.exceptions import CourseNotFoundError from student.models import ( AlreadyEnrolledError, @@ -348,6 +348,7 @@ def get_user_roles(user_id): :param user_id: The id of the selected user. :return: All roles for all courses that this user has. """ + # pylint: disable=protected-access user = _get_user(user_id) if not hasattr(user, '_roles'): user._roles = RoleCache(user) diff --git a/common/djangoapps/enrollment/errors.py b/openedx/core/djangoapps/enrollments/errors.py similarity index 100% rename from common/djangoapps/enrollment/errors.py rename to openedx/core/djangoapps/enrollments/errors.py diff --git a/common/djangoapps/enrollment/forms.py b/openedx/core/djangoapps/enrollments/forms.py similarity index 89% rename from common/djangoapps/enrollment/forms.py rename to openedx/core/djangoapps/enrollments/forms.py index 7a52c9e477..d2530ae868 100644 --- a/common/djangoapps/enrollment/forms.py +++ b/openedx/core/djangoapps/enrollments/forms.py @@ -28,7 +28,7 @@ class CourseEnrollmentsApiListForm(Form): try: return CourseKey.from_string(course_id) except InvalidKeyError: - raise ValidationError("'{}' is not a valid course id.".format(course_id)) + raise ValidationError(u"'{}' is not a valid course id.".format(course_id)) return course_id def clean_username(self): @@ -40,7 +40,7 @@ class CourseEnrollmentsApiListForm(Form): usernames = usernames_csv_string.split(',') if len(usernames) > self.MAX_USERNAME_COUNT: raise ValidationError( - "Too many usernames in a single request - {}. A maximum of {} is allowed".format( + u"Too many usernames in a single request - {}. A maximum of {} is allowed".format( len(usernames), self.MAX_USERNAME_COUNT, ) diff --git a/common/djangoapps/enrollment/management/__init__.py b/openedx/core/djangoapps/enrollments/management/__init__.py similarity index 100% rename from common/djangoapps/enrollment/management/__init__.py rename to openedx/core/djangoapps/enrollments/management/__init__.py diff --git a/common/djangoapps/enrollment/management/commands/__init__.py b/openedx/core/djangoapps/enrollments/management/commands/__init__.py similarity index 100% rename from common/djangoapps/enrollment/management/commands/__init__.py rename to openedx/core/djangoapps/enrollments/management/commands/__init__.py diff --git a/common/djangoapps/enrollment/management/commands/enroll_user_in_course.py b/openedx/core/djangoapps/enrollments/management/commands/enroll_user_in_course.py similarity index 92% rename from common/djangoapps/enrollment/management/commands/enroll_user_in_course.py rename to openedx/core/djangoapps/enrollments/management/commands/enroll_user_in_course.py index 95ab5e18f0..d41a8a70ce 100644 --- a/common/djangoapps/enrollment/management/commands/enroll_user_in_course.py +++ b/openedx/core/djangoapps/enrollments/management/commands/enroll_user_in_course.py @@ -4,8 +4,8 @@ Management command for enrolling a user into a course via the enrollment api from __future__ import absolute_import from django.contrib.auth.models import User from django.core.management.base import BaseCommand -from enrollment.data import CourseEnrollmentExistsError -from enrollment.api import add_enrollment +from openedx.core.djangoapps.enrollments.data import CourseEnrollmentExistsError +from openedx.core.djangoapps.enrollments.api import add_enrollment class Command(BaseCommand): diff --git a/common/djangoapps/enrollment/management/tests/__init__.py b/openedx/core/djangoapps/enrollments/management/tests/__init__.py similarity index 100% rename from common/djangoapps/enrollment/management/tests/__init__.py rename to openedx/core/djangoapps/enrollments/management/tests/__init__.py diff --git a/common/djangoapps/enrollment/management/tests/test_enroll_user_in_course.py b/openedx/core/djangoapps/enrollments/management/tests/test_enroll_user_in_course.py similarity index 97% rename from common/djangoapps/enrollment/management/tests/test_enroll_user_in_course.py rename to openedx/core/djangoapps/enrollments/management/tests/test_enroll_user_in_course.py index d643f9b5a1..18c204eee2 100644 --- a/common/djangoapps/enrollment/management/tests/test_enroll_user_in_course.py +++ b/openedx/core/djangoapps/enrollments/management/tests/test_enroll_user_in_course.py @@ -1,15 +1,15 @@ """ Test the change_enrollment command line script.""" from __future__ import absolute_import -import ddt import unittest from uuid import uuid4 +import ddt from django.conf import settings from django.core.management import call_command from django.core.management.base import CommandError -from enrollment.api import get_enrollment +from openedx.core.djangoapps.enrollments.api import get_enrollment from student.tests.factories import UserFactory from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase diff --git a/common/djangoapps/enrollment/paginators.py b/openedx/core/djangoapps/enrollments/paginators.py similarity index 100% rename from common/djangoapps/enrollment/paginators.py rename to openedx/core/djangoapps/enrollments/paginators.py diff --git a/common/djangoapps/enrollment/serializers.py b/openedx/core/djangoapps/enrollments/serializers.py similarity index 96% rename from common/djangoapps/enrollment/serializers.py rename to openedx/core/djangoapps/enrollments/serializers.py index 7018bcce63..5e946527f9 100644 --- a/common/djangoapps/enrollment/serializers.py +++ b/openedx/core/djangoapps/enrollments/serializers.py @@ -20,7 +20,7 @@ class StringListField(serializers.CharField): [1,2,3] """ - def field_to_native(self, obj, field_name): + def field_to_native(self, obj, field_name): # pylint: disable=unused-argument """ Serialize the object's class name. """ @@ -99,7 +99,7 @@ class CourseEnrollmentsApiListSerializer(CourseEnrollmentSerializer): fields = CourseEnrollmentSerializer.Meta.fields + ('course_id', ) -class ModeSerializer(serializers.Serializer): +class ModeSerializer(serializers.Serializer): # pylint: disable=abstract-method """Serializes a course's 'Mode' tuples Returns a serialized representation of the modes available for course enrollment. The course diff --git a/common/djangoapps/enrollment/tests/__init__.py b/openedx/core/djangoapps/enrollments/tests/__init__.py similarity index 100% rename from common/djangoapps/enrollment/tests/__init__.py rename to openedx/core/djangoapps/enrollments/tests/__init__.py diff --git a/common/djangoapps/enrollment/tests/fake_data_api.py b/openedx/core/djangoapps/enrollments/tests/fake_data_api.py similarity index 100% rename from common/djangoapps/enrollment/tests/fake_data_api.py rename to openedx/core/djangoapps/enrollments/tests/fake_data_api.py diff --git a/common/djangoapps/enrollment/tests/fixtures/course-enrollments-api-list-valid-data.json b/openedx/core/djangoapps/enrollments/tests/fixtures/course-enrollments-api-list-valid-data.json similarity index 100% rename from common/djangoapps/enrollment/tests/fixtures/course-enrollments-api-list-valid-data.json rename to openedx/core/djangoapps/enrollments/tests/fixtures/course-enrollments-api-list-valid-data.json diff --git a/common/djangoapps/enrollment/tests/test_api.py b/openedx/core/djangoapps/enrollments/tests/test_api.py similarity index 96% rename from common/djangoapps/enrollment/tests/test_api.py rename to openedx/core/djangoapps/enrollments/tests/test_api.py index 8b937452a5..50de1b6459 100644 --- a/common/djangoapps/enrollment/tests/test_api.py +++ b/openedx/core/djangoapps/enrollments/tests/test_api.py @@ -12,14 +12,16 @@ from django.test.utils import override_settings from mock import Mock, patch from course_modes.models import CourseMode -from enrollment import api -from enrollment.errors import CourseModeNotFoundError, EnrollmentApiLoadError, EnrollmentNotFoundError -from enrollment.tests import fake_data_api +from openedx.core.djangoapps.enrollments import api +from openedx.core.djangoapps.enrollments.errors import ( + CourseModeNotFoundError, EnrollmentApiLoadError, EnrollmentNotFoundError, +) +from openedx.core.djangoapps.enrollments.tests import fake_data_api from openedx.core.djangolib.testing.utils import CacheIsolationTestCase @ddt.ddt -@override_settings(ENROLLMENT_DATA_API="enrollment.tests.fake_data_api") +@override_settings(ENROLLMENT_DATA_API="openedx.core.djangoapps.enrollments.tests.fake_data_api") @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') class EnrollmentTest(CacheIsolationTestCase): """ @@ -71,7 +73,7 @@ class EnrollmentTest(CacheIsolationTestCase): def test_enroll_no_mode_success(self, course_modes, expected_mode): # Add a fake course enrollment information to the fake data API fake_data_api.add_course(self.COURSE_ID, course_modes=course_modes) - with patch('enrollment.api.CourseMode.modes_for_course') as mock_modes_for_course: + with patch('openedx.core.djangoapps.enrollments.api.CourseMode.modes_for_course') as mock_modes_for_course: mock_course_modes = [Mock(slug=mode) for mode in course_modes] mock_modes_for_course.return_value = mock_course_modes # Enroll in the course and verify the URL we get sent to diff --git a/common/djangoapps/enrollment/tests/test_data.py b/openedx/core/djangoapps/enrollments/tests/test_data.py similarity index 97% rename from common/djangoapps/enrollment/tests/test_data.py rename to openedx/core/djangoapps/enrollments/tests/test_data.py index 7d3e82e036..bc68f2f95f 100644 --- a/common/djangoapps/enrollment/tests/test_data.py +++ b/openedx/core/djangoapps/enrollments/tests/test_data.py @@ -17,14 +17,14 @@ from six.moves import range from course_modes.models import CourseMode from course_modes.tests.factories import CourseModeFactory -from enrollment import data -from enrollment.errors import ( +from openedx.core.djangoapps.enrollments import data +from openedx.core.djangoapps.enrollments.errors import ( CourseEnrollmentClosedError, CourseEnrollmentExistsError, CourseEnrollmentFullError, UserNotFoundError ) -from enrollment.serializers import CourseEnrollmentSerializer +from openedx.core.djangoapps.enrollments.serializers import CourseEnrollmentSerializer from openedx.core.lib.exceptions import CourseNotFoundError from student.models import AlreadyEnrolledError, CourseEnrollment, CourseFullError, EnrollmentClosedError from student.tests.factories import CourseAccessRoleFactory, UserFactory @@ -385,7 +385,9 @@ class EnrollmentDataTest(ModuleStoreTestCase): def test_get_roles(self): """Create a role for a user, then get it""" - expected_role = CourseAccessRoleFactory.create(course_id=self.course.id, user=self.user, role="SuperCoolTestRole") + expected_role = CourseAccessRoleFactory.create( + course_id=self.course.id, user=self.user, role="SuperCoolTestRole", + ) roles = data.get_user_roles(self.user.username) self.assertEqual(roles, {expected_role}) diff --git a/common/djangoapps/enrollment/tests/test_views.py b/openedx/core/djangoapps/enrollments/tests/test_views.py similarity index 97% rename from common/djangoapps/enrollment/tests/test_views.py rename to openedx/core/djangoapps/enrollments/tests/test_views.py index 7b211f6c26..85dbfbad1d 100644 --- a/common/djangoapps/enrollment/tests/test_views.py +++ b/openedx/core/djangoapps/enrollments/tests/test_views.py @@ -1,3 +1,4 @@ +# pylint: disable=missing-docstring,redefined-outer-name """ Tests for user enrollment. """ @@ -28,13 +29,13 @@ from six.moves import range from course_modes.models import CourseMode from course_modes.tests.factories import CourseModeFactory -from enrollment import api, data -from enrollment.errors import CourseEnrollmentError -from enrollment.views import EnrollmentUserThrottle from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from openedx.core.djangoapps.course_groups import cohorts from openedx.core.djangoapps.embargo.models import Country, CountryAccessRule, RestrictedCourse from openedx.core.djangoapps.embargo.test_utils import restrict_course +from openedx.core.djangoapps.enrollments import api, data +from openedx.core.djangoapps.enrollments.errors import CourseEnrollmentError +from openedx.core.djangoapps.enrollments.views import EnrollmentUserThrottle from openedx.core.djangoapps.oauth_dispatch.jwt import create_jwt_for_user from openedx.core.djangoapps.user_api.models import RetirementState, UserOrgTag, UserRetirementStatus from openedx.core.lib.django_test_client_utils import get_absolute_url @@ -105,7 +106,7 @@ class EnrollmentTestMixin(object): # Verify that the modulestore is queried as expected. with check_mongo_calls_range(min_finds=min_mongo_calls, max_finds=max_mongo_calls): - with patch('enrollment.views.audit_log') as mock_audit_log: + with patch('openedx.core.djangoapps.enrollments.views.audit_log') as mock_audit_log: url = reverse('courseenrollments') response = self.client.post(url, json.dumps(data), content_type='application/json', **extra) self.assertEqual(response.status_code, expected_status) @@ -237,7 +238,10 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase, Ente # Create an enrollment self.assert_enrollment_status() resp = self.client.get( - reverse('courseenrollment', kwargs={'username': self.user.username, "course_id": six.text_type(self.course.id)}) + reverse( + 'courseenrollment', + kwargs={'username': self.user.username, "course_id": six.text_type(self.course.id)}, + ) ) self.assertEqual(resp.status_code, status.HTTP_200_OK) data = json.loads(resp.content) @@ -473,7 +477,12 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase, Ente (None, None, None, None), (datetime.datetime(2015, 1, 2, 3, 4, 5, tzinfo=pytz.UTC), None, "2015-01-02T03:04:05Z", None), (None, datetime.datetime(2015, 1, 2, 3, 4, 5, tzinfo=pytz.UTC), None, "2015-01-02T03:04:05Z"), - (datetime.datetime(2014, 6, 7, 8, 9, 10, tzinfo=pytz.UTC), datetime.datetime(2015, 1, 2, 3, 4, 5, tzinfo=pytz.UTC), "2014-06-07T08:09:10Z", "2015-01-02T03:04:05Z"), + ( + datetime.datetime(2014, 6, 7, 8, 9, 10, tzinfo=pytz.UTC), + datetime.datetime(2015, 1, 2, 3, 4, 5, tzinfo=pytz.UTC), + "2014-06-07T08:09:10Z", + "2015-01-02T03:04:05Z", + ), ) @ddt.unpack def test_get_course_details_course_dates(self, start_datetime, end_datetime, expected_start, expected_end): @@ -528,7 +537,10 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase, Ente def test_get_enrollment_internal_error(self, mock_get_enrollment): mock_get_enrollment.side_effect = CourseEnrollmentError("Something bad happened.") resp = self.client.get( - reverse('courseenrollment', kwargs={'username': self.user.username, "course_id": six.text_type(self.course.id)}) + reverse( + 'courseenrollment', + kwargs={'username': self.user.username, "course_id": six.text_type(self.course.id)}, + ) ) self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST) @@ -576,7 +588,7 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase, Ente try: throttle.parse_rate(throttle.get_rate()) except ImproperlyConfigured: - self.fail("No throttle rate set for {}".format(user_scope)) + self.fail(u"No throttle rate set for {}".format(user_scope)) def test_create_enrollment_with_cohort(self): """Enroll in the course, and also add to a cohort.""" @@ -588,7 +600,7 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase, Ente self.assert_enrollment_status(cohort=cohort_name) self.assertTrue(CourseEnrollment.is_enrolled(self.user, self.course.id)) - course_mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id) + _, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id) self.assertTrue(is_active) self.assertEqual(cohorts.get_cohort(self.user, self.course.id, assign=False).name, cohort_name) @@ -636,7 +648,11 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase, Ente # Passes the include_expired parameter to the API call v_response = self.client.get( - reverse('courseenrollmentdetails', kwargs={"course_id": six.text_type(self.course.id)}), {'include_expired': True} + reverse( + 'courseenrollmentdetails', + kwargs={"course_id": six.text_type(self.course.id)} + ), + {'include_expired': True}, ) v_data = json.loads(v_response.content) @@ -644,7 +660,9 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase, Ente self.assertEqual(len(v_data['course_modes']), 2) # Omits the include_expired parameter from the API call - h_response = self.client.get(reverse('courseenrollmentdetails', kwargs={"course_id": six.text_type(self.course.id)})) + h_response = self.client.get( + reverse('courseenrollmentdetails', kwargs={"course_id": six.text_type(self.course.id)}), + ) h_data = json.loads(h_response.content) # Ensure that only one course mode is returned and that it is honor @@ -1236,8 +1254,8 @@ class EnrollmentCrossDomainTest(ModuleStoreTestCase): }) resp = self.client.get(url, HTTP_REFERER=self.REFERER) self.assertEqual(resp.status_code, 200) - self.assertIn('prod-edx-csrftoken', resp.cookies) # pylint: disable=no-member - return resp.cookies['prod-edx-csrftoken'].value # pylint: disable=no-member + self.assertIn('prod-edx-csrftoken', resp.cookies) + return resp.cookies['prod-edx-csrftoken'].value def _cross_domain_post(self, csrf_cookie): """Perform a cross-domain POST request. """ @@ -1306,7 +1324,7 @@ class UnenrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase): """ Helper method to create a RetirementStatus with useful defaults """ - pending_state = RetirementState.objects.create( + RetirementState.objects.create( state_name='PENDING', state_execution_order=1, is_dead_end_state=False, @@ -1494,7 +1512,7 @@ class UserRoleTest(ModuleStoreTestCase): self._assert_roles([expected_role2], False, course_id=text_type(self.course2.id)) def test_roles_exception(self): - with patch('enrollment.api.get_user_roles') as mock_get_user_roles: + with patch('openedx.core.djangoapps.enrollments.api.get_user_roles') as mock_get_user_roles: mock_get_user_roles.side_effect = Exception() response = self.client.get(reverse('roles')) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) diff --git a/common/djangoapps/enrollment/urls.py b/openedx/core/djangoapps/enrollments/urls.py similarity index 100% rename from common/djangoapps/enrollment/urls.py rename to openedx/core/djangoapps/enrollments/urls.py diff --git a/common/djangoapps/enrollment/views.py b/openedx/core/djangoapps/enrollments/views.py similarity index 94% rename from common/djangoapps/enrollment/views.py rename to openedx/core/djangoapps/enrollments/views.py index bdca16581d..3f035b28ff 100644 --- a/common/djangoapps/enrollment/views.py +++ b/openedx/core/djangoapps/enrollments/views.py @@ -14,17 +14,19 @@ from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.utils.decorators import method_decorator from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser -from enrollment import api -from enrollment.errors import CourseEnrollmentError, CourseEnrollmentExistsError, CourseModeNotFoundError -from enrollment.forms import CourseEnrollmentsApiListForm -from enrollment.paginators import CourseEnrollmentsApiListPagination -from enrollment.serializers import CourseEnrollmentsApiListSerializer from opaque_keys import InvalidKeyError from opaque_keys.edx.keys import CourseKey from openedx.core.djangoapps.cors_csrf.authentication import SessionAuthenticationCrossDomainCsrf from openedx.core.djangoapps.cors_csrf.decorators import ensure_csrf_cookie_cross_domain from openedx.core.djangoapps.course_groups.cohorts import CourseUserGroup, add_user_to_cohort, get_cohort_by_name from openedx.core.djangoapps.embargo import api as embargo_api +from openedx.core.djangoapps.enrollments import api +from openedx.core.djangoapps.enrollments.errors import ( + CourseEnrollmentError, CourseEnrollmentExistsError, CourseModeNotFoundError, +) +from openedx.core.djangoapps.enrollments.forms import CourseEnrollmentsApiListForm +from openedx.core.djangoapps.enrollments.paginators import CourseEnrollmentsApiListPagination +from openedx.core.djangoapps.enrollments.serializers import CourseEnrollmentsApiListSerializer from openedx.core.djangoapps.user_api.accounts.permissions import CanRetireUser from openedx.core.djangoapps.user_api.models import UserRetirementStatus from openedx.core.djangoapps.user_api.preferences.api import update_email_opt_in @@ -164,10 +166,13 @@ class EnrollmentView(APIView, ApiKeyPermissionMixIn): * user: The ID of the user. """ - authentication_classes = (JwtAuthentication, OAuth2AuthenticationAllowInactiveUser, - SessionAuthenticationAllowInactiveUser,) - permission_classes = ApiKeyHeaderPermissionIsAuthenticated, - throttle_classes = EnrollmentUserThrottle, + authentication_classes = ( + JwtAuthentication, + OAuth2AuthenticationAllowInactiveUser, + SessionAuthenticationAllowInactiveUser, + ) + permission_classes = (ApiKeyHeaderPermissionIsAuthenticated,) + throttle_classes = (EnrollmentUserThrottle,) # Since the course about page on the marketing site uses this API to auto-enroll users, # we need to support cross-domain CSRF. @@ -236,11 +241,13 @@ class EnrollmentUserRolesView(APIView): logged-in user, filtered by course_id if given, along with whether or not the user is global staff """ - authentication_classes = (JwtAuthentication, - OAuth2AuthenticationAllowInactiveUser, - EnrollmentCrossDomainSessionAuth) - permission_classes = ApiKeyHeaderPermissionIsAuthenticated, - throttle_classes = EnrollmentUserThrottle, + authentication_classes = ( + JwtAuthentication, + OAuth2AuthenticationAllowInactiveUser, + EnrollmentCrossDomainSessionAuth, + ) + permission_classes = (ApiKeyHeaderPermissionIsAuthenticated,) + throttle_classes = (EnrollmentUserThrottle,) @method_decorator(ensure_csrf_cookie_cross_domain) def get(self, request): @@ -252,7 +259,7 @@ class EnrollmentUserRolesView(APIView): roles_data = api.get_user_roles(request.user.username) if course_id: roles_data = [role for role in roles_data if text_type(role.course_id) == course_id] - except Exception: + except Exception: # pylint: disable=broad-except return Response( status=status.HTTP_400_BAD_REQUEST, data={ @@ -339,7 +346,7 @@ class EnrollmentCourseDetailView(APIView): authentication_classes = [] permission_classes = [] - throttle_classes = EnrollmentUserThrottle, + throttle_classes = (EnrollmentUserThrottle,) def get(self, request, course_id=None): """Read enrollment information for a particular course. @@ -405,7 +412,7 @@ class UnenrollmentView(APIView): returned along with a list of all courses from which the user was unenrolled. """ authentication_classes = (JwtAuthentication,) - permission_classes = (permissions.IsAuthenticated, CanRetireUser) + permission_classes = (permissions.IsAuthenticated, CanRetireUser,) def post(self, request): """ @@ -603,10 +610,13 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn): * user: The username of the user. """ - authentication_classes = (JwtAuthentication, OAuth2AuthenticationAllowInactiveUser, - EnrollmentCrossDomainSessionAuth,) - permission_classes = ApiKeyHeaderPermissionIsAuthenticated, - throttle_classes = EnrollmentUserThrottle, + authentication_classes = ( + JwtAuthentication, + OAuth2AuthenticationAllowInactiveUser, + EnrollmentCrossDomainSessionAuth, + ) + permission_classes = (ApiKeyHeaderPermissionIsAuthenticated,) + throttle_classes = (EnrollmentUserThrottle,) # Since the course about page on the marketing site # uses this API to auto-enroll users, we need to support @@ -652,6 +662,7 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn): return Response(filtered_data) def post(self, request): + # pylint: disable=too-many-statements """Enrolls the currently logged-in user in a course. Server-to-server calls may deactivate or modify the mode of existing enrollments. All other requests @@ -734,8 +745,8 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn): try: enterprise_api_client.post_enterprise_course_enrollment(username, text_type(course_id), None) except EnterpriseApiException as error: - log.exception("An unexpected error occurred while creating the new EnterpriseCourseEnrollment " - "for user [%s] in course run [%s]", username, course_id) + log.exception(u"An unexpected error occurred while creating the new EnterpriseCourseEnrollment " + u"for user [%s] in course run [%s]", username, course_id) raise CourseEnrollmentError(text_type(error)) kwargs = { 'username': username, @@ -766,7 +777,7 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn): log.warning(msg) return Response(status=status.HTTP_400_BAD_REQUEST, data={"message": msg}) - if len(missing_attrs) > 0: + if missing_attrs: msg = u"Missing enrollment attributes: requested mode={} required attributes={}".format( mode, REQUIRED_ATTRIBUTES.get(mode) ) @@ -805,7 +816,7 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn): org = course_id.org update_email_opt_in(request.user, org, email_opt_in) - log.info('The user [%s] has already been enrolled in course run [%s].', username, course_id) + log.info(u'The user [%s] has already been enrolled in course run [%s].', username, course_id) return Response(response) except CourseModeNotFoundError as error: return Response( @@ -824,11 +835,11 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn): } ) except CourseEnrollmentExistsError as error: - log.warning('An enrollment already exists for user [%s] in course run [%s].', username, course_id) + log.warning(u'An enrollment already exists for user [%s] in course run [%s].', username, course_id) return Response(data=error.enrollment) except CourseEnrollmentError: - log.exception("An error occurred while creating the new course enrollment for user " - "[%s] in course run [%s]", username, course_id) + log.exception(u"An error occurred while creating the new course enrollment for user " + u"[%s] in course run [%s]", username, course_id) return Response( status=status.HTTP_400_BAD_REQUEST, data={ @@ -839,11 +850,11 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn): } ) except CourseUserGroup.DoesNotExist: - log.exception('Missing cohort [%s] in course run [%s]', cohort_name, course_id) + log.exception(u'Missing cohort [%s] in course run [%s]', cohort_name, course_id) return Response( status=status.HTTP_400_BAD_REQUEST, data={ - "message": "An error occured while adding to cohort [%s]" % cohort_name + "message": u"An error occured while adding to cohort [%s]" % cohort_name }) finally: # Assumes that the ecommerce service uses an API key to authenticate. @@ -930,8 +941,8 @@ class CourseEnrollmentsApiListView(DeveloperErrorViewMixin, ListAPIView): OAuth2AuthenticationAllowInactiveUser, SessionAuthenticationAllowInactiveUser, ) - permission_classes = (permissions.IsAdminUser, ) - throttle_classes = (EnrollmentUserThrottle, ) + permission_classes = (permissions.IsAdminUser,) + throttle_classes = (EnrollmentUserThrottle,) serializer_class = CourseEnrollmentsApiListSerializer pagination_class = CourseEnrollmentsApiListPagination diff --git a/openedx/core/djangoapps/user_api/accounts/tests/retirement_helpers.py b/openedx/core/djangoapps/user_api/accounts/tests/retirement_helpers.py index d54d3ec081..8b0e0c234b 100644 --- a/openedx/core/djangoapps/user_api/accounts/tests/retirement_helpers.py +++ b/openedx/core/djangoapps/user_api/accounts/tests/retirement_helpers.py @@ -10,7 +10,7 @@ import pytz from django.test import TestCase from social_django.models import UserSocialAuth -from enrollment import api +from openedx.core.djangoapps.enrollments import api from openedx.core.djangoapps.user_api.models import RetirementState, UserRetirementStatus from student.models import get_retired_email_by_email, get_retired_username_by_username from student.tests.factories import UserFactory