Skip to content

Commit

Permalink
ENT-2310 | Adding a post_save receiver to listen to CourseEnrollments…
Browse files Browse the repository at this point in the history
… table (#589)

Adding in a test file I forgot. Updating a few test doctrings

Actually adding in the tasks file too

Updating task to prevent duplicate recreation of EnterpriseCourseEnrollment

Appeasing pylint

bumping version
  • Loading branch information
christopappas authored Oct 2, 2019
1 parent e0e5600 commit efe35de
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 2 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
----------

[1.11.0] - 2019-10-02
---------------------

* Adding post-save receiver to spin off EnterpriseCourseEnrollment creation tasks on CourseEnrollment creation signals

[1.10.8] - 2019-10-01
---------------------

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__ = "1.10.8"
__version__ = "1.11.0"

default_app_config = "enterprise.apps.EnterpriseConfig" # pylint: disable=invalid-name
36 changes: 36 additions & 0 deletions enterprise/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from logging import getLogger

from django.core.exceptions import ObjectDoesNotExist
from django.db import transaction
from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver
Expand All @@ -19,8 +20,14 @@
SystemWideEnterpriseRole,
SystemWideEnterpriseUserRoleAssignment,
)
from enterprise.tasks import create_enterprise_enrollment
from enterprise.utils import get_default_catalog_content_filter, track_enrollment

try:
from student.models import CourseEnrollment
except ImportError:
CourseEnrollment = None

logger = getLogger(__name__) # pylint: disable=invalid-name


Expand Down Expand Up @@ -116,3 +123,32 @@ def delete_enterprise_learner_role_assignment(sender, instance, **kwargs): #
except SystemWideEnterpriseUserRoleAssignment.DoesNotExist:
# Do nothing if no role assignment is present for the enterprise customer user.
pass


def create_enterprise_enrollment_receiver(sender, instance, **kwargs): # pylint: disable=unused-argument
"""
Watches for post_save signal for creates on the CourseEnrollment table.
Spin off an async task to generate an EnterpriseCourseEnrollment if appropriate.
"""
if kwargs.get('created') and instance.user:
user_id = instance.user.id
try:
ecu = EnterpriseCustomerUser.objects.get(user_id=user_id)
except ObjectDoesNotExist:
return
logger.info((
"User %s is an EnterpriseCustomerUser. "
"Spinning off task to check if course is within User's "
"Enterprise's EnterpriseCustomerCatalog."
), user_id)

create_enterprise_enrollment.delay(
instance.course_id,
ecu,
)


# Don't connect this receiver if we dont have access to CourseEnrollment model
if CourseEnrollment is not None:
post_save.connect(create_enterprise_enrollment_receiver, sender=CourseEnrollment)
42 changes: 42 additions & 0 deletions enterprise/tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
"""
Django tasks.
"""
from __future__ import absolute_import, unicode_literals

from logging import getLogger

from celery import shared_task

from enterprise.models import EnterpriseCourseEnrollment

LOGGER = getLogger(__name__)


@shared_task
def create_enterprise_enrollment(course_id, enterprise_customer_user):
"""
Create enterprise enrollment for user if course_id part of catalog for the ENT customer.
"""
# Prevent duplicate records from being created if possible
# before we need to make a call to discovery
if EnterpriseCourseEnrollment.objects.filter(
enterprise_customer_user=enterprise_customer_user,
course_id=course_id,
).exists():
LOGGER.info((
"EnterpriseCourseEnrollment record exists for user %s "
"on course %s. Exiting task."
), enterprise_customer_user.user_id, course_id)
return

enterprise_customer = enterprise_customer_user.enterprise_customer
if enterprise_customer.catalog_contains_course(course_id):
LOGGER.info((
"Creating EnterpriseCourseEnrollment for user %s "
"on course %s for enterprise_customer %s"
), enterprise_customer_user.user_id, course_id, enterprise_customer)
EnterpriseCourseEnrollment.objects.create(
course_id=course_id,
enterprise_customer_user=enterprise_customer_user,
)
69 changes: 68 additions & 1 deletion tests/test_enterprise/test_signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
SystemWideEnterpriseRole,
SystemWideEnterpriseUserRoleAssignment,
)
from enterprise.signals import handle_user_post_save
from enterprise.signals import create_enterprise_enrollment_receiver, handle_user_post_save
from test_utils.factories import (
EnterpriseCustomerCatalogFactory,
EnterpriseCustomerFactory,
Expand Down Expand Up @@ -465,3 +465,70 @@ def test_delete_enterprise_learner_role_assignment_no_user_associated(self):
role=self.enterprise_learner_role
)
self.assertFalse(learner_role_assignment.exists())


@mark.django_db
class TestCourseEnrollmentSignals(unittest.TestCase):
"""
Tests signals associated with CourseEnrollments (that are found in edx-platform).
"""
def setUp(self):
"""
Setup for `TestCourseEnrollmentSignals` test.
"""
self.user = UserFactory(id=2, email='user@example.com')
self.enterprise_customer = EnterpriseCustomerFactory(
name='Team Titans',
)
self.enterprise_customer_user = EnterpriseCustomerUserFactory(
user_id=self.user.id,
enterprise_customer=self.enterprise_customer,
)
self.non_enterprise_user = UserFactory(id=999, email='user999@example.com')
super(TestCourseEnrollmentSignals, self).setUp()

@mock.patch('enterprise.tasks.create_enterprise_enrollment.delay')
def test_receiver_calls_task_if_ecu_exists(self, mock_task):
"""
Receiver should call a task
if user tied to the CourseEnrollment that is handed into the function
is an EnterpriseCustomerUser
"""
sender = mock.Mock() # This would be a CourseEnrollment class
instance = mock.Mock() # This would be a CourseEnrollment instance
instance.user = self.user
instance.course_id = "fake:course_id"
# Signal metadata (note: 'signal' would be an actual object, but we dont need it here)
kwargs = {
'update_fields': None,
'raw': False,
'signal': '<django.db.models.signals.ModelSignal object at 0x7fcfc38b5e90>',
'using': 'default',
'created': True,
}

create_enterprise_enrollment_receiver(sender, instance, **kwargs)
mock_task.assert_called_once_with(instance.course_id, self.enterprise_customer_user)

@mock.patch('enterprise.tasks.create_enterprise_enrollment.delay')
def test_receiver_does_not_call_task_if_ecu_not_exists(self, mock_task):
"""
Receiver should NOT call a task
if user tied to the CourseEnrollment that is handed into the function
is NOT an EnterpriseCustomerUser
"""
sender = mock.Mock() # This would be a CourseEnrollment class
instance = mock.Mock() # This would be a CourseEnrollment instance
instance.user = self.non_enterprise_user
instance.course_id = "fake:course_id"
# Signal metadata (note: 'signal' would be an actual object, but we dont need it here)
kwargs = {
'update_fields': None,
'raw': False,
'signal': '<django.db.models.signals.ModelSignal object at 0x7fcfc38b5e90>',
'using': 'default',
'created': True,
}

create_enterprise_enrollment_receiver(sender, instance, **kwargs)
mock_task.assert_not_called()
82 changes: 82 additions & 0 deletions tests/test_enterprise/test_tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# -*- coding: utf-8 -*-
"""
Tests for the `edx-enterprise` tasks module.
"""
from __future__ import absolute_import, unicode_literals, with_statement

import unittest

import mock
from pytest import mark

from enterprise.models import EnterpriseCourseEnrollment
from enterprise.tasks import create_enterprise_enrollment
from test_utils.factories import EnterpriseCustomerFactory, EnterpriseCustomerUserFactory, UserFactory


@mark.django_db
class TestEnterpriseTasks(unittest.TestCase):
"""
Tests tasks associated with Enterprise.
"""
def setUp(self):
"""
Setup for `TestEnterpriseTasks` test.
"""
self.user = UserFactory(id=2, email='user@example.com')
self.enterprise_customer = EnterpriseCustomerFactory(
name='Team Titans',
)
self.enterprise_customer_user = EnterpriseCustomerUserFactory(
user_id=self.user.id,
enterprise_customer=self.enterprise_customer,
)
super(TestEnterpriseTasks, self).setUp()

@mock.patch('enterprise.models.EnterpriseCustomer.catalog_contains_course')
def test_create_enrollment_task_course_in_catalog(self, mock_contains_course):
"""
Task should create an enterprise enrollment if the course_id handed to
the function is part of the EnterpriseCustomer's catalogs
"""
mock_contains_course.return_value = True

assert EnterpriseCourseEnrollment.objects.count() == 0
create_enterprise_enrollment(
'fake:course',
self.enterprise_customer_user
)
assert EnterpriseCourseEnrollment.objects.count() == 1

@mock.patch('enterprise.models.EnterpriseCustomer.catalog_contains_course')
def test_create_enrollment_task_course_not_in_catalog(self, mock_contains_course):
"""
Task should NOT create an enterprise enrollment if the course_id handed
to the function is NOT part of the EnterpriseCustomer's catalogs
"""
mock_contains_course.return_value = False

assert EnterpriseCourseEnrollment.objects.count() == 0
create_enterprise_enrollment(
'fake:course',
self.enterprise_customer_user
)
assert EnterpriseCourseEnrollment.objects.count() == 0

def test_create_enrollment_task_no_create_duplicates(self):
"""
Task should return without creating a new EnterpriseCourseEnrollment
if one with the course_id and enterprise_customer_user specified
already exists.
"""
EnterpriseCourseEnrollment.objects.create(
course_id='fake:course',
enterprise_customer_user=self.enterprise_customer_user,
)

assert EnterpriseCourseEnrollment.objects.count() == 1
create_enterprise_enrollment(
'fake:course',
self.enterprise_customer_user
)
assert EnterpriseCourseEnrollment.objects.count() == 1

0 comments on commit efe35de

Please sign in to comment.