Skip to content

Commit 1d32f2d

Browse files
fix: info added about courses which are not published (#3974)
* fix: info added about courses which are not published * test: external course sync test fixed * fix: text updated
1 parent ffc4a7f commit 1d32f2d

5 files changed

Lines changed: 127 additions & 19 deletions

File tree

cms/api.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,8 +296,13 @@ def save_page_revision(page, updated_revision):
296296
Args:
297297
page(Page): A page object.
298298
updated_revision(Page): Updated Page object using the `latest_revision_as_object`
299+
300+
Returns:
301+
bool: True if the revision was published, False if it was kept as a draft
302+
because the page had unpublished changes.
299303
"""
300304
is_draft = page.has_unpublished_changes
301305
revision = updated_revision.save_revision(user=None, log_action=True)
302306
if not is_draft:
303307
revision.publish()
308+
return not is_draft

courses/sync_external_courses/external_course_sync_api.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -450,10 +450,13 @@ def update_external_course_runs(external_courses, keymap): # noqa: C901, PLR091
450450
log.info(
451451
f"Creating or Updating course page, title: {external_course.course_title}, course_code: {external_course.course_run_code}" # noqa: G004
452452
)
453-
course_page, course_page_created, course_page_updated = (
454-
create_or_update_external_course_page(
455-
course_index_page, course, external_course, keymap
456-
)
453+
(
454+
course_page,
455+
course_page_created,
456+
course_page_updated,
457+
course_page_published,
458+
) = create_or_update_external_course_page(
459+
course_index_page, course, external_course, keymap
457460
)
458461

459462
if course_page_created:
@@ -465,7 +468,7 @@ def update_external_course_runs(external_courses, keymap): # noqa: C901, PLR091
465468
log.info(
466469
f"Created external course page for course title: {external_course.course_title}" # noqa: G004
467470
)
468-
elif course_page_updated:
471+
elif course_page_updated and course_page_published:
469472
stats_collector.add_stat(
470473
"course_pages_updated",
471474
external_course.course_code,
@@ -474,6 +477,15 @@ def update_external_course_runs(external_courses, keymap): # noqa: C901, PLR091
474477
log.info(
475478
f"Updated external course page for course title: {external_course.course_title}" # noqa: G004
476479
)
480+
elif course_page_updated:
481+
stats_collector.add_stat(
482+
"course_pages_kept_as_draft",
483+
external_course.course_code,
484+
external_course.course_title,
485+
)
486+
log.info(
487+
"Updated external course page kept as draft (page has unpublished changes)"
488+
)
477489

478490
if external_course.category:
479491
topic = CourseTopic.objects.filter(
@@ -532,6 +544,9 @@ def update_external_course_runs(external_courses, keymap): # noqa: C901, PLR091
532544
# so, we are removing the courses created from the updated courses list.
533545
stats_collector.remove_duplicates("existing_courses", "courses_created")
534546
stats_collector.remove_duplicates("course_pages_updated", "course_pages_created")
547+
stats_collector.remove_duplicates(
548+
"course_pages_kept_as_draft", "course_pages_created"
549+
)
535550

536551
return stats_collector
537552

@@ -606,7 +621,11 @@ def create_or_update_external_course_page( # noqa: C901
606621
external_course(ExternalCourse): A ExternalCourse object.
607622
608623
Returns:
609-
tuple(ExternalCoursePage, is_created, is_updated): ExternalCoursePage object, is_created, is_updated
624+
tuple(ExternalCoursePage, is_created, is_updated, is_published): ExternalCoursePage
625+
object, is_created, is_updated, and is_published. `is_published` indicates whether
626+
an update was published (True) or kept as a draft (False) because the page had
627+
unpublished changes. It is only meaningful when `is_updated` is True; for created
628+
pages and unchanged pages it defaults to True.
610629
"""
611630
course_page = (
612631
ExternalCoursePage.objects.select_for_update().filter(course=course).first()
@@ -630,6 +649,7 @@ def create_or_update_external_course_page( # noqa: C901
630649
)
631650

632651
is_created = is_updated = False
652+
is_published = True
633653
if not course_page:
634654
course_page = ExternalCoursePage(
635655
course=course,
@@ -686,9 +706,9 @@ def create_or_update_external_course_page( # noqa: C901
686706
is_updated = True
687707

688708
if is_updated:
689-
save_page_revision(course_page, latest_revision)
709+
is_published = save_page_revision(course_page, latest_revision)
690710

691-
return course_page, is_created, is_updated
711+
return course_page, is_created, is_updated, is_published
692712

693713

694714
def create_or_update_external_course_run(course, external_course):

courses/sync_external_courses/external_course_sync_api_test.py

Lines changed: 70 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ def test_create_or_update_external_course_page( # noqa: PLR0913, C901
249249
if not has_language:
250250
external_course_data.pop("language")
251251

252-
external_course_page, course_page_created, course_page_updated = (
252+
external_course_page, course_page_created, course_page_updated, _ = (
253253
create_or_update_external_course_page(
254254
course_index_page,
255255
course,
@@ -908,9 +908,13 @@ def test_save_page_revision(is_draft_page, has_unpublished_changes):
908908

909909
latest_revision = external_course_page.get_latest_revision_as_object()
910910
latest_revision.external_marketing_url = "https://test-external-course-sync-api.io/Internet-of-things-iot-design-and-applications"
911-
save_page_revision(external_course_page, latest_revision)
911+
# The revision is published only when the page had no unpublished changes
912+
# before saving; otherwise it is kept as a draft.
913+
expected_draft = external_course_page.has_unpublished_changes
914+
published = save_page_revision(external_course_page, latest_revision)
912915

913916
assert external_course_page.live == (not is_draft_page)
917+
assert published is (not expected_draft)
914918

915919
if has_unpublished_changes:
916920
assert external_course_page.has_unpublished_changes
@@ -1214,13 +1218,16 @@ def test_create_or_update_external_course_page_field_override(
12141218

12151219
keymap = get_keymap(external_course_data["course_run_code"])
12161220

1217-
external_course_page, course_page_created, course_page_updated = (
1218-
create_or_update_external_course_page(
1219-
course_index_page,
1220-
course,
1221-
ExternalCourse(external_course_data, keymap=keymap),
1222-
keymap=keymap,
1223-
)
1221+
(
1222+
external_course_page,
1223+
course_page_created,
1224+
course_page_updated,
1225+
course_page_published,
1226+
) = create_or_update_external_course_page(
1227+
course_index_page,
1228+
course,
1229+
ExternalCourse(external_course_data, keymap=keymap),
1230+
keymap=keymap,
12241231
)
12251232

12261233
latest_revision = external_course_page.get_latest_revision_as_object()
@@ -1231,6 +1238,8 @@ def test_create_or_update_external_course_page_field_override(
12311238
)
12321239
assert course_page_updated is True
12331240
assert course_page_created is False
1241+
# The page has no unpublished changes here, so the update is published.
1242+
assert course_page_published is True
12341243

12351244
# Check if fields were overridden based on whether they had existing values
12361245
if expected_fields_overridden:
@@ -1255,3 +1264,55 @@ def test_create_or_update_external_course_page_field_override(
12551264
assert latest_revision.description == existing_description
12561265
assert latest_revision.background_image == existing_image
12571266
assert latest_revision.thumbnail_image == existing_image
1267+
1268+
1269+
@pytest.mark.parametrize(
1270+
"external_course_data",
1271+
[{"platform": EMERITUS_PLATFORM_NAME}],
1272+
indirect=True,
1273+
)
1274+
@pytest.mark.django_db
1275+
def test_create_or_update_external_course_page_kept_as_draft(external_course_data):
1276+
"""
1277+
Test that when a course page already has unpublished (manual) changes, an API
1278+
update is saved as a draft and NOT published, and `is_published` is returned as False.
1279+
"""
1280+
home_page = HomePageFactory.create(title="Home Page", subhead="<p>subhead</p>")
1281+
course_index_page = CourseIndexPageFactory.create(parent=home_page, title="Courses")
1282+
course = CourseFactory.create(is_external=True, page=None)
1283+
ImageFactory.create(title=external_course_data["image_name"])
1284+
1285+
external_course_page = ExternalCoursePageFactory.create(
1286+
parent=course_index_page,
1287+
course=course,
1288+
title=external_course_data["program_name"],
1289+
external_marketing_url="https://old-url.com",
1290+
)
1291+
# Publish a baseline revision, then leave an unpublished (draft) manual change on top.
1292+
external_course_page.save_revision().publish()
1293+
external_course_page.refresh_from_db()
1294+
external_course_page.title = "Manually edited title"
1295+
external_course_page.save_revision() # draft, not published
1296+
external_course_page.refresh_from_db()
1297+
assert external_course_page.has_unpublished_changes
1298+
1299+
keymap = get_keymap(external_course_data["course_run_code"])
1300+
1301+
_, course_page_created, course_page_updated, course_page_published = (
1302+
create_or_update_external_course_page(
1303+
course_index_page,
1304+
course,
1305+
ExternalCourse(external_course_data, keymap=keymap),
1306+
keymap=keymap,
1307+
)
1308+
)
1309+
1310+
assert course_page_created is False
1311+
assert course_page_updated is True
1312+
# The page had unpublished changes, so the API update is kept as a draft.
1313+
assert course_page_published is False
1314+
1315+
external_course_page.refresh_from_db()
1316+
# The live (published) version still has the old marketing URL.
1317+
assert external_course_page.external_marketing_url == "https://old-url.com"
1318+
assert external_course_page.has_unpublished_changes

courses/sync_external_courses/utils.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,12 @@ def __init__(self):
131131
"course_pages_updated": StatItemsCollection(
132132
"course_pages_updated",
133133
"External Course Codes",
134+
display_name="Course Pages Updated and Published",
135+
),
136+
"course_pages_kept_as_draft": StatItemsCollection(
137+
"course_pages_kept_as_draft",
138+
"External Course Codes",
139+
display_name="Course Pages Updated but set as Draft",
134140
),
135141
"products_created": StatItemsCollection(
136142
"products_created",

mail/templates/external_data_sync/body.html

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@
175175
</li>
176176
<li>
177177
<span style="color: #008000"
178-
>Course Pages Updated ({{ stats.course_pages_updated|length }}):</span
178+
>Course Pages Updated and Published ({{ stats.course_pages_updated|length }}):</span
179179
>
180180
{% if stats.course_pages_updated %}
181181
<ul>
@@ -186,7 +186,23 @@
186186
</li>
187187
{% endfor %}
188188
</ul>
189-
{% else %}No course pages updated during this sync.{% endif %}
189+
{% else %}No course pages updated and published during this sync.{% endif %}
190+
</li>
191+
<li>
192+
<span style="color: #ff0000"
193+
>Course Pages Updated but set as Draft ({{ stats.course_pages_kept_as_draft|length }}):</span
194+
>
195+
{% if stats.course_pages_kept_as_draft %}
196+
<ul>
197+
{% for page in stats.course_pages_kept_as_draft %}
198+
<li>
199+
{{ page.code }}
200+
({{ page.title }}) - API changes were saved as a draft and NOT
201+
published because the page has unpublished manual changes.
202+
</li>
203+
{% endfor %}
204+
</ul>
205+
{% else %}No course pages kept as draft during this sync.{% endif %}
190206
</li>
191207
</ul>
192208

0 commit comments

Comments
 (0)