Skip to content

Commit

Permalink
refactor: added sync_daily in platform and refactored courses/task.py…
Browse files Browse the repository at this point in the history
… to be more generic
  • Loading branch information
Anas12091101 committed Dec 4, 2024
1 parent 4177d33 commit d7d38f0
Show file tree
Hide file tree
Showing 8 changed files with 77 additions and 39 deletions.
11 changes: 4 additions & 7 deletions courses/management/commands/sync_external_course_runs.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
from django.core.management.base import BaseCommand

from courses.sync_external_courses.external_course_sync_api import (
EMERITUS_PLATFORM_NAME,
GLOBAL_ALUMNI_PLATFORM_NAME,
VENDOR_KEYMAPS,
EmeritusKeyMap,
GlobalAlumniKeyMap,
fetch_external_courses,
Expand Down Expand Up @@ -39,13 +38,11 @@ def handle(self, *args, **options): # noqa: ARG002
return

vendor_name = options["vendor_name"]
if vendor_name.lower() == EMERITUS_PLATFORM_NAME.lower():
keymap = EmeritusKeyMap()
elif vendor_name.lower() == GLOBAL_ALUMNI_PLATFORM_NAME.lower():
keymap = GlobalAlumniKeyMap()
else:
keymap = VENDOR_KEYMAPS.get(vendor_name.lower())
if not keymap:
self.stdout.write(self.style.ERROR(f"Unknown vendor name {vendor_name}."))
return

self.stdout.write(f"Starting course sync for {vendor_name}.")
emeritus_course_runs = fetch_external_courses(keymap)
stats = update_external_course_runs(emeritus_course_runs, keymap)
Expand Down
18 changes: 18 additions & 0 deletions courses/migrations/0041_platform_sync_daily.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.16 on 2024-12-04 07:39

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('courses', '0040_alter_courserun_courseware_id'),
]

operations = [
migrations.AddField(
model_name='platform',
name='sync_daily',
field=models.BooleanField(default=False),
),
]
1 change: 1 addition & 0 deletions courses/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ class Platform(TimestampedModel, ValidateOnSaveMixin):
"""

name = models.CharField(max_length=255, unique=True)
sync_daily = models.BooleanField(default=False)

def __str__(self):
return self.name
Expand Down
19 changes: 10 additions & 9 deletions courses/sync_external_courses/external_course_sync_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,6 @@
log = logging.getLogger(__name__)


EMERITUS_PLATFORM_NAME = "Emeritus"
GLOBAL_ALUMNI_PLATFORM_NAME = "Global Alumni"

class BaseKeyMap:
"""
Base class for course sync keys with common attributes.
Expand All @@ -52,11 +49,11 @@ def __init__(self, platform_name, report_names):
self.report_names = report_names

@property
def COURSE_PAGE_SUBHEAD(self):
def course_page_subhead(self):
return f"Delivered in collaboration with {self.platform_name}."

@property
def LEARNING_OUTCOMES_PAGE_SUBHEAD(self):
def learning_outcomes_page_subhead(self):
return (
f"MIT xPRO is collaborating with online education provider {self.platform_name} to "
"deliver this online course. By clicking LEARN MORE, you will be taken to "
Expand All @@ -70,16 +67,20 @@ class EmeritusKeyMap(BaseKeyMap):
Emeritus course sync keys.
"""
def __init__(self):
super().__init__(platform_name=EMERITUS_PLATFORM_NAME, report_names=["Batch"])
super().__init__(platform_name="Emeritus", report_names=["Batch"])


class GlobalAlumniKeyMap(BaseKeyMap):
"""
Global Alumni course sync keys.
"""
def __init__(self):
super().__init__(platform_name=GLOBAL_ALUMNI_PLATFORM_NAME, report_names=["GA - Batch"])
super().__init__(platform_name="Global Alumni", report_names=["GA - Batch"])

VENDOR_KEYMAPS = {
"emeritus": EmeritusKeyMap(),
"global alumni": GlobalAlumniKeyMap(),
}

class ExternalCourseSyncAPIJobStatus(Enum):
"""
Expand Down Expand Up @@ -543,7 +544,7 @@ def create_or_update_external_course_page(course_index_page, course, external_co
course=course,
title=external_course.course_title,
external_marketing_url=external_course.marketing_url,
subhead=keymap.COURSE_PAGE_SUBHEAD,
subhead=keymap.course_page_subhead,
duration=external_course.duration,
format=external_course.format,
description=external_course.description,
Expand Down Expand Up @@ -684,7 +685,7 @@ def create_learning_outcomes_page(course_page, outcomes_list, keymap):

learning_outcome_page = LearningOutcomesPage(
heading=keymap.LEARNING_OUTCOMES_PAGE_HEADING,
sub_heading=keymap.LEARNING_OUTCOMES_PAGE_SUBHEAD,
sub_heading=keymap.learning_outcomes_page_subhead,
outcome_items=outcome_items,
)
course_page.add_child(instance=learning_outcome_page)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
from courses.sync_external_courses.external_course_sync_api import (
ExternalCourse,
EmeritusKeyMap,
EMERITUS_PLATFORM_NAME,
create_learning_outcomes_page,
create_or_update_certificate_page,
create_or_update_external_course_page,
Expand Down Expand Up @@ -452,7 +451,7 @@ def test_update_external_course_runs( # noqa: PLR0915
).open() as test_data_file:
external_course_runs = json.load(test_data_file)["rows"]

platform = PlatformFactory.create(name=EMERITUS_PLATFORM_NAME)
platform = PlatformFactory.create(name="Emeritus")

if create_existing_data:
for run in random.sample(external_course_runs, len(external_course_runs) // 2):
Expand Down Expand Up @@ -677,7 +676,7 @@ def test_create_or_update_product_and_product_version( # noqa: PLR0913
"""
external_course_data["list_price"] = new_price
external_course = ExternalCourse(external_course_data, keymap=EmeritusKeyMap())
platform = PlatformFactory.create(name=EMERITUS_PLATFORM_NAME)
platform = PlatformFactory.create(name="Emeritus")
course = CourseFactory.create(
external_course_id=external_course.course_code,
platform=platform,
Expand Down
20 changes: 15 additions & 5 deletions courses/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
from django.db.models import Q
from requests.exceptions import HTTPError

from courses.models import CourseRun, CourseRunCertificate
from courses.sync_external_courses.emeritus_api import (
from courses.models import CourseRun, CourseRunCertificate, Platform
from courses.sync_external_courses.external_course_sync_api import (
VENDOR_KEYMAPS,
fetch_external_courses,
update_external_course_runs,
)
Expand Down Expand Up @@ -114,11 +115,20 @@ def sync_courseruns_data():


@app.task
def task_sync_emeritus_course_runs():
def task_sync_external_course_runs():
"""Task to sync Emeritus course runs"""
if not settings.FEATURES.get("ENABLE_EXTERNAL_COURSE_SYNC", False):
log.info("External Course sync is disabled.")
return

emeritus_course_runs = fetch_external_courses()
update_external_course_runs(emeritus_course_runs)
platforms = Platform.objects.filter(sync_daily=True)
for platform in platforms:
keymap = VENDOR_KEYMAPS.get(platform.name.lower())
if not keymap:
log.exception(
"The platform '%s' does not have a sync API configured. Please disable the 'sync_daily' setting for this platform.",
platform.name,
)
continue
emeritus_course_runs = fetch_external_courses(keymap)
update_external_course_runs(emeritus_course_runs, keymap)
22 changes: 17 additions & 5 deletions courses/tasks_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

import pytest

from courses.factories import CourseRunFactory
from courses.tasks import sync_courseruns_data, task_sync_emeritus_course_runs
from courses.factories import CourseRunFactory, PlatformFactory
from courses.tasks import sync_courseruns_data, task_sync_external_course_runs

pytestmark = [pytest.mark.django_db]

Expand All @@ -25,13 +25,25 @@ def test_sync_courseruns_data(mocker):
assert Counter(actual_course_runs) == Counter(course_runs)


def test_task_sync_emeritus_course_runs(mocker, settings):
"""Test task_sync_emeritus_course_runs calls the right api functionality"""
def test_task_sync_external_course_runs(mocker, settings):
"""Test task_sync_external_course_runs skips platforms not in VENDOR_KEYMAPS"""
settings.FEATURES["ENABLE_EXTERNAL_COURSE_SYNC"] = True

mock_fetch_external_courses = mocker.patch("courses.tasks.fetch_external_courses")
mock_update_external_course_runs = mocker.patch(
"courses.tasks.update_external_course_runs"
)
task_sync_emeritus_course_runs.delay()
log_mock = mocker.patch("courses.tasks.log")

PlatformFactory.create(name="Emeritus", sync_daily=True)
PlatformFactory.create(name="UnknownPlatform", sync_daily=True)

task_sync_external_course_runs.delay()

mock_fetch_external_courses.assert_called_once()
mock_update_external_course_runs.assert_called_once()

log_mock.exception.assert_called_once_with(
"The platform '%s' does not have a sync API configured. Please disable the 'sync_daily' setting for this platform.",
"UnknownPlatform",
)
20 changes: 10 additions & 10 deletions mitxpro/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -761,14 +761,14 @@
)

CRON_EXTERNAL_COURSERUN_SYNC_HOURS = get_string(
name="CRON_EMERITUS_COURSERUN_SYNC_HOURS",
name="CRON_EXTERNAL_COURSERUN_SYNC_HOURS",
default="0",
description="'hours' value for the 'sync-emeritus-course-runs' scheduled task (defaults to midnight)",
description="'hours' value for the 'sync-external-course-runs' scheduled task (defaults to midnight)",
)
CRON_EXTERNAL_COURSERUN_SYNC_DAYS = get_string(
name="CRON_EMERITUS_COURSERUN_SYNC_DAYS",
name="CRON_EXTERNAL_COURSERUN_SYNC_DAYS",
default=None,
description="'day_of_week' value for 'sync-emeritus-course-runs' scheduled task (default will run once a day).",
description="'day_of_week' value for 'sync-external-course-runs' scheduled task (default will run once a day).",
)

CRON_BASKET_DELETE_HOURS = get_string(
Expand Down Expand Up @@ -885,8 +885,8 @@
month_of_year="*",
),
},
"sync-emeritus-course-runs": {
"task": "courses.tasks.task_sync_emeritus_course_runs",
"sync-external-course-runs": {
"task": "courses.tasks.task_sync_external_course_runs",
"schedule": crontab(
minute="0",
hour=CRON_EXTERNAL_COURSERUN_SYNC_HOURS,
Expand Down Expand Up @@ -1131,18 +1131,18 @@
EXTERNAL_COURSE_SYNC_API_KEY = get_string(
name="EXTERNAL_COURSE_SYNC_API_KEY",
default=None,
description="The API Key for Emeritus API",
description="The API Key for external course sync API",
required=True,
)
EXTERNAL_COURSE_SYNC_API_BASE_URL = get_string(
name="EXTERNAL_COURSE_SYNC_API_BASE_URL",
default="https://mit-xpro.emeritus-analytics.io/",
description="Base API URL for Emeritus API",
description="Base API URL for external course sync API",
)
EXTERNAL_COURSE_SYNC_API_REQUEST_TIMEOUT = get_int(
name="EMERITUS_API_TIMEOUT",
name="EXTERNAL_COURSE_SYNC_API_REQUEST_TIMEOUT",
default=60,
description="API request timeout for Emeritus APIs in seconds",
description="API request timeout for external course sync APIs in seconds",
)

# django debug toolbar only in debug mode
Expand Down

0 comments on commit d7d38f0

Please sign in to comment.