Skip to content

Commit

Permalink
Sync catalog admin to enterprise-catalog ida (#679)
Browse files Browse the repository at this point in the history
  • Loading branch information
bbaker6225 authored Jan 17, 2020
1 parent 7e0c709 commit 30a3a93
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 7 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ Change Log

.. There should always be an "Unreleased" section for changes pending release.
[2.1.0] - 2020-01-16
--------------------

* Added hooks to sync EnterpriseCustomerCatalog creation, deletion, and model updates in Django Admin to the new enterprise-catalog service

[2.0.50] - 2020-01-16
---------------------

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.50"
__version__ = "2.1.00"

default_app_config = "enterprise.apps.EnterpriseConfig" # pylint: disable=invalid-name
54 changes: 54 additions & 0 deletions enterprise/admin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
EnterpriseCustomerTransmitCoursesView,
TemplatePreviewView,
)
from enterprise.api_client.enterprise_catalog import EnterpriseCatalogApiClient
from enterprise.api_client.lms import CourseApiClient, EnrollmentApiClient
from enterprise.models import (
EnrollmentNotificationEmailTemplate,
Expand Down Expand Up @@ -633,6 +634,59 @@ def get_form(self, request, obj=None, **kwargs):
form.base_fields['content_filter'].initial = json.dumps(get_default_catalog_content_filter())
return form

def get_actions(self, request):
"""
Disallow the delete selected action as that does not send a DELETE request to enterprise-catalog
"""
actions = super(EnterpriseCustomerCatalogAdmin, self).get_actions(request)
if 'delete_selected' in actions:
del actions['delete_selected']
return actions

def save_model(self, request, obj, form, change):
"""
On save, creates or updates the corresponding catalog in the enterprise-catalog IDA.
`change` indicates whether or not the object is being updated (as opposed to created).
"""
catalog_uuid = obj.uuid
catalog_client = EnterpriseCatalogApiClient(user=request.user)

if change:
update_fields = {
'enterprise_customer': str(obj.enterprise_customer.uuid),
'title': obj.title,
'content_filter': obj.content_filter,
'enabled_course_modes': obj.enabled_course_modes,
'publish_audit_enrollment_urls': obj.publish_audit_enrollment_urls,
}
catalog_client.update_enterprise_catalog(catalog_uuid, **update_fields)
else:
catalog_client.create_enterprise_catalog(
str(catalog_uuid),
str(obj.enterprise_customer.uuid),
obj.title,
obj.content_filter,
obj.enabled_course_modes,
obj.publish_audit_enrollment_urls,
)

super(EnterpriseCustomerCatalogAdmin, self).save_model(request, obj, form, change)

def delete_model(self, request, obj):
"""
Deletes the corresponding catalog in the enterprise-catalog IDA
From the warning in https://docs.djangoproject.com/en/1.11/ref/contrib/admin/#modeladmin-methods, we don't
prevent the deletion of the catalog if the response fails, but periodically check the logs to clean up any
catalog that did not get deleted from the new service while we transition.
"""
catalog_uuid = obj.uuid
catalog_client = EnterpriseCatalogApiClient(user=request.user)
catalog_client.delete_enterprise_catalog(catalog_uuid)

super(EnterpriseCustomerCatalogAdmin, self).delete_model(request, obj)


@admin.register(EnterpriseCustomerReportingConfiguration)
class EnterpriseCustomerReportingConfigurationAdmin(admin.ModelAdmin):
Expand Down
45 changes: 41 additions & 4 deletions enterprise/api_client/enterprise_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
import json
from logging import getLogger

from requests.exceptions import ConnectionError, Timeout # pylint: disable=redefined-builtin
from slumber.exceptions import HttpNotFoundError, SlumberBaseException

from django.conf import settings

from enterprise.api_client.lms import JwtLmsApiClient
Expand Down Expand Up @@ -43,22 +46,56 @@ def create_enterprise_catalog(
'enabled_course_modes': enabled_course_modes,
'publish_audit_enrollment_urls': json.dumps(publish_audit_enrollment_urls),
}
return endpoint.post(post_data)
try:
return endpoint.post(post_data)
except (SlumberBaseException, ConnectionError, Timeout) as exc:
LOGGER.exception(
'Failed to create EnterpriseCustomer Catalog [%s] in enterprise-catalog due to: [%s]',
catalog_uuid, str(exc)
)
return {}

@JwtLmsApiClient.refresh_token
def get_enterprise_catalog(self, catalog_uuid):
"""Gets an enterprise catalog."""
endpoint = getattr(self.client, self.ENTERPRISE_CATALOG_ENDPOINT)(catalog_uuid)
return endpoint.get()
try:
return endpoint.get()
except (SlumberBaseException, ConnectionError, Timeout) as exc:
LOGGER.exception(
'Failed to get EnterpriseCustomer Catalog [%s] in enterprise-catalog due to: [%s]',
catalog_uuid, str(exc)
)
return {}

@JwtLmsApiClient.refresh_token
def update_enterprise_catalog(self, catalog_uuid, **kwargs):
"""Updates an enterprise catalog."""
endpoint = getattr(self.client, self.ENTERPRISE_CATALOG_ENDPOINT)(catalog_uuid)
return endpoint.put(kwargs)
try:
return endpoint.put(kwargs)
except (SlumberBaseException, ConnectionError, Timeout) as exc:
LOGGER.exception(
'Failed to update EnterpriseCustomer Catalog [%s] in enterprise-catalog due to: [%s]',
catalog_uuid, str(exc)
)
return {}

@JwtLmsApiClient.refresh_token
def delete_enterprise_catalog(self, catalog_uuid):
"""Deletes an enterprise catalog."""
endpoint = getattr(self.client, self.ENTERPRISE_CATALOG_ENDPOINT)(catalog_uuid)
return endpoint.delete()
try:
return endpoint.delete()
except HttpNotFoundError:
LOGGER.warning(
'Deleted EnterpriseCustomerCatalog [%s] that was not in enterprise-catalog',
catalog_uuid
)
return {}
except (SlumberBaseException, ConnectionError, Timeout) as exc:
LOGGER.exception(
'Failed to delete EnterpriseCustomer Catalog [%s] in enterprise-catalog due to: [%s]',
catalog_uuid, str(exc)
)
return {}
71 changes: 71 additions & 0 deletions tests/test_admin/test_model_admins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
"""
Tests for the `edx-enterprise` admin actions module.
"""
from __future__ import absolute_import, unicode_literals

import mock

from django.contrib.admin.sites import AdminSite
from django.test import TestCase
from django.test.client import RequestFactory

from enterprise.admin import EnterpriseCustomerCatalogAdmin
from enterprise.models import EnterpriseCustomerCatalog
from test_utils.factories import EnterpriseCustomerCatalogFactory, UserFactory


class EnterpriseCustomerCatalogAdminTests(TestCase):
"""
Tests the EnterpriseCustomerCatalogAdmin
"""

def setUp(self):
super(EnterpriseCustomerCatalogAdminTests, self).setUp()
self.catalog_admin = EnterpriseCustomerCatalogAdmin(EnterpriseCustomerCatalog, AdminSite)
self.request = RequestFactory()
self.request.user = UserFactory(is_staff=True)
self.enterprise_catalog = EnterpriseCustomerCatalogFactory()
self.enterprise_catalog_uuid = self.enterprise_catalog.uuid
self.form = None # The form isn't important for the current tests as we don't change any logic there

@mock.patch('enterprise.admin.EnterpriseCatalogApiClient')
def test_delete_catalog(self, api_client_mock):
api_client_mock.return_value = mock.MagicMock()
api_client_mock.return_value.delete_enterprise_catalog = mock.MagicMock()
self.catalog_admin.delete_model(self.request, self.enterprise_catalog)

# Verify the API was called correctly and the catalog was deleted
api_client_mock.return_value.delete_enterprise_catalog.assert_called_with(self.enterprise_catalog_uuid)
self.assertFalse(EnterpriseCustomerCatalog.objects.exists())

@mock.patch('enterprise.admin.EnterpriseCatalogApiClient')
def test_create_catalog(self, api_client_mock):
api_client_mock.return_value = mock.MagicMock()
api_client_mock.return_value.create_enterprise_catalog = mock.MagicMock()

change = False # False for creation
self.catalog_admin.save_model(self.request, self.enterprise_catalog, self.form, change)

# Verify the API was called and the catalog "was created" (even though it already was)
# This method is a little weird in that the object is sort of created / not-created at the same time
api_client_mock.return_value.create_enterprise_catalog.assert_called()
self.assertEqual(
EnterpriseCustomerCatalog.objects.get(uuid=self.enterprise_catalog_uuid),
self.enterprise_catalog
)

@mock.patch('enterprise.admin.EnterpriseCatalogApiClient')
def test_update_catalog(self, api_client_mock):
api_client_mock.return_value = mock.MagicMock()
api_client_mock.return_value.update_enterprise_catalog = mock.MagicMock()

change = True # True for updating
self.catalog_admin.save_model(self.request, self.enterprise_catalog, self.form, change)

# Verify the API was called and the catalog is the same as there were no real updates
api_client_mock.return_value.update_enterprise_catalog.assert_called()
self.assertEqual(
EnterpriseCustomerCatalog.objects.get(uuid=self.enterprise_catalog_uuid),
self.enterprise_catalog
)
4 changes: 2 additions & 2 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ commands =
deps =
-r{toxinidir}/requirements/dev.txt
commands =
isort --check-only --recursive tests test_utils enterprise enterprise_learner_portal consent integrated_channels manage.py setup.py
isort --check-only --diff --recursive tests test_utils enterprise enterprise_learner_portal consent integrated_channels manage.py setup.py

[testenv:quality]
whitelist_externals =
Expand All @@ -86,7 +86,7 @@ commands =
rm tests/__init__.py
pycodestyle enterprise enterprise_learner_portal consent integrated_channels tests test_utils
pydocstyle enterprise enterprise_learner_portal consent integrated_channels tests test_utils
isort --check-only --recursive tests test_utils enterprise enterprise_learner_portal consent integrated_channels manage.py setup.py
isort --check-only --diff --recursive tests test_utils enterprise enterprise_learner_portal consent integrated_channels manage.py setup.py

[testenv:jasmine]
passenv = JASMINE_BROWSER DISPLAY
Expand Down

0 comments on commit 30a3a93

Please sign in to comment.