Skip to content

Commit

Permalink
Merge pull request #620 from edx/aj/ENT-2082_api
Browse files Browse the repository at this point in the history
[MICROBA-56] Added Source to the EnterpriseEnrollments for Enterprise APIs
  • Loading branch information
Albert (AJ) St. Aubin authored Nov 13, 2019
2 parents f6f55fd + 69504b8 commit e8fa734
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 30 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ Change Log
Unreleased
----------

[2.0.20] - 2019-11-13
---------------------

* Added Source to Enterprise API Enrollments.

[2.0.19] - 2019-13-08
---------------------

Expand Down
2 changes: 1 addition & 1 deletion enterprise/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@

from __future__ import absolute_import, unicode_literals

__version__ = "2.0.19"
__version__ = "2.0.20"

default_app_config = "enterprise.apps.EnterpriseConfig" # pylint: disable=invalid-name
12 changes: 10 additions & 2 deletions enterprise/api/v1/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,12 @@ def create(self, validated_data):
validated_data['enterprise_customer_user'] = enterprise_customer_user
try:
if is_active:
enterprise_customer_user.enroll(course_run_id, course_mode, cohort=cohort)
enterprise_customer_user.enroll(
course_run_id,
course_mode,
cohort=cohort,
source_slug=models.EnterpriseEnrollmentSource.API
)
else:
enterprise_customer_user.unenroll(course_run_id)
except (CourseEnrollmentDowngradeError, CourseEnrollmentPermissionError, HttpClientError) as exc:
Expand Down Expand Up @@ -594,7 +599,10 @@ def create(self, validated_data):
user_email,
course_mode,
course_run_id,
cohort=cohort
cohort=cohort,
enrollment_source=models.EnterpriseEnrollmentSource.get_source(
models.EnterpriseEnrollmentSource.API
)
)
else:
enterprise_customer.clear_pending_registration(user_email, course_run_id)
Expand Down
40 changes: 23 additions & 17 deletions enterprise/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -718,7 +718,7 @@ def get_remote_id(self):
return client.get_remote_id(self.enterprise_customer.identity_provider, user.username)
return None

def enroll(self, course_run_id, mode, cohort=None):
def enroll(self, course_run_id, mode, cohort=None, source_slug=None):
"""
Enroll a user into a course track, and register an enterprise course enrollment.
"""
Expand All @@ -734,7 +734,29 @@ def enroll(self, course_run_id, mode, cohort=None):
if not enrolled_in_course or is_upgrading:
if cohort and not self.enterprise_customer.enable_autocohorting:
raise CourseEnrollmentPermissionError("Auto-cohorting is not enabled for this enterprise")

try:
EnterpriseCourseEnrollment.objects.get_or_create(
enterprise_customer_user=self,
course_id=course_run_id,
defaults={
'source': EnterpriseEnrollmentSource.get_source(source_slug)
}
)
except IntegrityError:
# Added to try and fix ENT-2463. This can happen if the user is already a part of the enterprise
# because of the following:
# 1. (non-enterprise) CourseEnrollment data is created
# 2. An async task to is signaled to run after CourseEnrollment creation
# (create_enterprise_enrollment_receiver)
# 3. Both async task and the code in the try block run `get_or_create` on
# `EnterpriseCourseEnrollment`
# 4. A race condition happens and it tries to create the same data twice
# Catching will allow us to continue and ensure we can still create an order for this enrollment.
LOGGER.exception("IntegrityError on attempt at EnterpriseCourseEnrollment for user with id [%s] "
"and course id [%s]", self.user_id, course_run_id)
# Directly enroll into the specified track.
# This should happen after we create the EnterpriseCourseEnrollment
enrollment_api_client.enroll_user_in_course(self.username, course_run_id, mode, cohort=cohort)
utils.track_event(self.user_id, 'edx.bi.user.enterprise.enrollment.course', {
'category': 'enterprise',
Expand All @@ -745,22 +767,6 @@ def enroll(self, course_run_id, mode, cohort=None):
'cohort': cohort,
'is_upgrading': is_upgrading,
})
try:
EnterpriseCourseEnrollment.objects.get_or_create(
enterprise_customer_user=self,
course_id=course_run_id
)
except IntegrityError:
# Added to try and fix ENT-2463. This can happen if the user is already a part of the enterprise because
# of the following:
# 1. (non-enterprise) CourseEnrollment data is created
# 2. An async task to is signaled to run after CourseEnrollment creation
# (create_enterprise_enrollment_receiver)
# 3. Both async task and the code in the try block run `get_or_create` on `EnterpriseCourseEnrollment`
# 4. A race condition happens and it tries to create the same data twice
# Catching will allow us to continue and ensure we can still create an order for this enrollment.
LOGGER.exception("IntegrityError on attempt at EnterpriseCourseEnrollment for user with id [%s] and "
"course id [%s]", self.user_id, course_run_id)
# create an ecommerce order for the course enrollment
self.create_order_for_enrollment(course_run_id)
elif enrolled_in_course and course_enrollment.get('mode') in paid_modes and mode in audit_modes:
Expand Down
18 changes: 13 additions & 5 deletions enterprise/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,21 @@ def handle_user_post_save(sender, **kwargs): # pylint: disable=unused-argument
)
pending_enrollments = list(pending_ecu.pendingenrollment_set.all())
if pending_enrollments:
def _complete_user_enrollment(): # pylint: disable=missing-docstring
def _complete_user_enrollment():
"""
Complete an Enterprise User's enrollment.
EnterpriseCustomers may enroll users in courses before the users themselves
actually exist in the system; in such a case, the enrollment for each such
course is finalized when the user registers with the OpenEdX platform.
"""
for enrollment in pending_enrollments:
# EnterpriseCustomers may enroll users in courses before the users themselves
# actually exist in the system; in such a case, the enrollment for each such
# course is finalized when the user registers with the OpenEdX platform.
enterprise_customer_user.enroll(
enrollment.course_id, enrollment.course_mode, cohort=enrollment.cohort_name)
enrollment.course_id,
enrollment.course_mode,
cohort=enrollment.cohort_name,
source_slug=getattr(enrollment.source, 'slug', None)
)
track_enrollment('pending-admin-enrollment', user_instance.id, enrollment.course_id)
pending_ecu.delete()
transaction.on_commit(_complete_user_enrollment)
Expand Down
21 changes: 16 additions & 5 deletions tests/test_enterprise/api/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

import ddt
import mock
from pytest import mark
from pytest import mark, raises
from rest_framework import status
from rest_framework.reverse import reverse
from rest_framework.test import APIClient
Expand Down Expand Up @@ -39,6 +39,7 @@
EnterpriseCatalogQuery,
EnterpriseCourseEnrollment,
EnterpriseCustomerUser,
EnterpriseEnrollmentSource,
EnterpriseFeatureRole,
EnterpriseFeatureUserRoleAssignment,
PendingEnrollment,
Expand Down Expand Up @@ -1750,7 +1751,9 @@ def test_enterprise_customer_course_enrollments_detail_success(
assert EnterpriseCourseEnrollment.objects.filter(
enterprise_customer_user__user_id=user.id,
course_id=payload.get('course_run_id'),
source=EnterpriseEnrollmentSource.get_source(EnterpriseEnrollmentSource.API)
).exists()

mock_enrollment_client.return_value.get_course_enrollment.assert_called_once_with(
user.username, payload.get('course_run_id')
)
Expand All @@ -1760,11 +1763,11 @@ def test_enterprise_customer_course_enrollments_detail_success(
payload.get('course_mode'),
cohort=payload.get('cohort'),
)
elif 'user_email' in post_data:
elif 'user_email' in payload and payload.get('is_active', True):
# If a new user given via for user_email, check that the appropriate objects were created.
pending_ecu = PendingEnterpriseCustomerUser.objects.get(
enterprise_customer=enterprise_customer,
user_email=payload.get('user_email')
user_email=payload.get('user_email'),
)

assert pending_ecu is not None
Expand All @@ -1774,12 +1777,20 @@ def test_enterprise_customer_course_enrollments_detail_success(
course_mode=payload.get('course_mode')
)
if payload.get('is_active', True):
assert pending_enrollment
assert pending_enrollment.cohort_name == payload.get('cohort')
assert pending_enrollment[0]
assert pending_enrollment[0].cohort_name == payload.get('cohort')
assert pending_enrollment[0].source.slug == EnterpriseEnrollmentSource.API
else:
assert not pending_enrollment
mock_enrollment_client.return_value.get_course_enrollment.assert_not_called()
mock_enrollment_client.return_value.enroll_user_in_course.assert_not_called()
elif 'user_email' in payload and not payload.get('is_active', True):
with raises(PendingEnterpriseCustomerUser.DoesNotExist):
# No Pending user should have been created in this case.
PendingEnterpriseCustomerUser.objects.get(
user_email=payload.get('user_email'),
enterprise_customer=enterprise_customer
)

if 'email_students' in payload:
mock_notify_learners.assert_called_once()
Expand Down

0 comments on commit e8fa734

Please sign in to comment.