|
9 | 9 | from opaque_keys import InvalidKeyError |
10 | 10 | from opaque_keys.edx.keys import UsageKey |
11 | 11 | from opaque_keys.edx.locator import LibraryCollectionLocator, LibraryContainerLocator |
| 12 | +from openedx_content import api as content_api |
| 13 | +from openedx_content.api import signals as content_signals |
| 14 | +from openedx_content.models_api import LearningPackage, PublishableEntity |
12 | 15 | from openedx_events.content_authoring.data import ( |
13 | 16 | ContentLibraryData, |
14 | 17 | ContentObjectChangedData, |
@@ -388,10 +391,74 @@ def library_container_deleted(**kwargs) -> None: |
388 | 391 | # See https://github.com/openedx/edx-platform/pull/36640 discussion. |
389 | 392 |
|
390 | 393 |
|
| 394 | +@receiver(content_signals.ENTITIES_DRAFT_CHANGED) |
| 395 | +def entities_updated( |
| 396 | + learning_package: content_signals.LearningPackageEventData, |
| 397 | + change_log: content_signals.DraftChangeLogEventData, |
| 398 | + **kwargs, |
| 399 | +) -> None: |
| 400 | + """ |
| 401 | + When entities are deleted or un-deleted (as drafts), update any associated |
| 402 | + collections, so their "# of draft entities in collection" count is correct. |
| 403 | +
|
| 404 | + 💾 This event is only received after the transaction has committed. |
| 405 | + ⏳ This event is emitted synchronously and this handler is called |
| 406 | + synchronously, so we want to be as efficient as possible. |
| 407 | + """ |
| 408 | + deleted_or_undeleted_entity_ids = [ |
| 409 | + r.entity_id for r in change_log.changes if r.new_version is None or (r.old_version is None and r.restored) |
| 410 | + ] |
| 411 | + # Note: we only care about deleted or un-deleted, not newly created drafts, because it's currently impossible for a |
| 412 | + # newly-created draft to be part of a collection. |
| 413 | + if not deleted_or_undeleted_entity_ids: |
| 414 | + return # No need to do anything more; if nothing was deleted or un-deleted, it won't affect collection counts. |
| 415 | + notify_affected_collections(learning_package.id, deleted_or_undeleted_entity_ids) |
| 416 | + |
| 417 | + |
| 418 | +@receiver(content_signals.ENTITIES_PUBLISHED) |
| 419 | +def entities_published( |
| 420 | + learning_package: content_signals.LearningPackageEventData, |
| 421 | + change_log: content_signals.PublishLogEventData, |
| 422 | + **kwargs, |
| 423 | +) -> None: |
| 424 | + """ |
| 425 | + When entities get newly published or their published version is deleted, |
| 426 | + update the "# of published entities in collection" count of any associated |
| 427 | + collections. |
| 428 | + """ |
| 429 | + newly_published_or_unpublished_entity_ids = [ |
| 430 | + r.entity_id for r in change_log.changes if r.new_version is None or r.old_version is None |
| 431 | + ] |
| 432 | + if not newly_published_or_unpublished_entity_ids: |
| 433 | + return # No need to do anything more; if nothing was deleted or un-deleted, it won't affect collection counts. |
| 434 | + notify_affected_collections(learning_package.id, newly_published_or_unpublished_entity_ids) |
| 435 | + |
| 436 | + |
| 437 | +def notify_affected_collections(learning_package_id: LearningPackage.ID, entity_ids: PublishableEntity.ID): |
| 438 | + """Helper for updating collections' "# of entities" count when draft/published entities affect it""" |
| 439 | + # Check if any collections are affected: |
| 440 | + affected_collections = ( |
| 441 | + content_api.get_collections(learning_package_id, enabled=True).filter(entities__id__in=entity_ids) |
| 442 | + ) |
| 443 | + # If any collections were affected, update them asynchronously: |
| 444 | + if not affected_collections: |
| 445 | + return |
| 446 | + # Collections are only used in libraries at the moment. Get the library key so we can form opaque keys for each |
| 447 | + # collection too. |
| 448 | + try: |
| 449 | + library_key = lib_api.get_library_key(learning_package_id) |
| 450 | + except lib_api.ContentLibraryNotFound: |
| 451 | + return |
| 452 | + |
| 453 | + for collection in affected_collections: |
| 454 | + collection_key = lib_api.library_collection_locator(library_key, collection.collection_code) |
| 455 | + update_library_collection_index_doc.delay(str(collection_key)) # Async - no need to wait for this ever. |
| 456 | + |
| 457 | + |
391 | 458 | @receiver([COURSE_IMPORT_COMPLETED, COURSE_RERUN_COMPLETED]) |
392 | 459 | def handle_reindex_on_signal(**kwargs): |
393 | 460 | """ |
394 | | - Automatically update Meiliesearch index for course in database on new import or rerun. |
| 461 | + Automatically update Meilisearch index for course in database on new import or rerun. |
395 | 462 | """ |
396 | 463 | course_data = kwargs.get("course", None) |
397 | 464 | if not course_data or not isinstance(course_data, CourseData): |
|
0 commit comments