diff --git a/enterprise_catalog/apps/academy/migrations/0002_tag_content_metadata.py b/enterprise_catalog/apps/academy/migrations/0002_tag_content_metadata.py new file mode 100644 index 000000000..3c88f2c84 --- /dev/null +++ b/enterprise_catalog/apps/academy/migrations/0002_tag_content_metadata.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.7 on 2024-01-08 20:33 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('catalog', '0037_alter_historicalcontentmetadata_options_and_more'), + ('academy', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='tag', + name='content_metadata', + field=models.ManyToManyField(related_name='tags', to='catalog.contentmetadata'), + ), + ] diff --git a/enterprise_catalog/apps/academy/models.py b/enterprise_catalog/apps/academy/models.py index 169da13f4..ac6b26332 100644 --- a/enterprise_catalog/apps/academy/models.py +++ b/enterprise_catalog/apps/academy/models.py @@ -8,7 +8,10 @@ from model_utils.models import TimeStampedModel from simple_history.models import HistoricalRecords -from enterprise_catalog.apps.catalog.models import EnterpriseCatalog +from enterprise_catalog.apps.catalog.models import ( + ContentMetadata, + EnterpriseCatalog, +) class Tag(models.Model): @@ -19,6 +22,7 @@ class Tag(models.Model): """ title = models.CharField(max_length=255, help_text=_('Tag title')) description = models.TextField(help_text=_('Tag description.')) + content_metadata = models.ManyToManyField(ContentMetadata, related_name='tags') class Meta: verbose_name = _('Tag') diff --git a/enterprise_catalog/apps/academy/tests/factories.py b/enterprise_catalog/apps/academy/tests/factories.py index 434800b54..15a2c4ba6 100644 --- a/enterprise_catalog/apps/academy/tests/factories.py +++ b/enterprise_catalog/apps/academy/tests/factories.py @@ -4,7 +4,9 @@ from factory.fuzzy import FuzzyText from enterprise_catalog.apps.academy.models import Academy, Tag +from enterprise_catalog.apps.catalog.constants import COURSE from enterprise_catalog.apps.catalog.tests.factories import ( + ContentMetadataFactory, EnterpriseCatalogFactory, ) @@ -18,6 +20,12 @@ class Meta: title = FuzzyText(length=32) + @factory.post_generation + def content_metadata(self, create, extracted, **kwargs): # pylint: disable=unused-argument + content_metadata1 = ContentMetadataFactory.create(content_type=COURSE) + content_metadata2 = ContentMetadataFactory.create(content_type=COURSE) + self.content_metadata.set([content_metadata1, content_metadata2]) # pylint: disable=no-member + class AcademyFactory(factory.django.DjangoModelFactory): """ diff --git a/enterprise_catalog/apps/api/tasks.py b/enterprise_catalog/apps/api/tasks.py index 46e002c81..e04ee914c 100644 --- a/enterprise_catalog/apps/api/tasks.py +++ b/enterprise_catalog/apps/api/tasks.py @@ -784,6 +784,7 @@ def _get_algolia_products_for_batch( 'enterprise_catalogs', 'enterprise_catalogs__academies', 'enterprise_catalogs__academies__tags', + 'enterprise_catalogs__academies__tags__content_metadata', ) # Retrieve ContentMetadata records for: @@ -831,7 +832,7 @@ def _get_algolia_products_for_batch( # First pass over the batch of content. The goal for this pass is to collect all the UUIDs directly associated with # each content. This DOES NOT capture any UUIDs indirectly related to programs or pathways via associated courses # or programs. - for metadata in content_metadata_to_process: + for metadata in content_metadata_to_process: # pylint: disable=too-many-nested-blocks if metadata.content_type in (COURSE, PROGRAM, LEARNER_PATHWAY): content_key = metadata.content_key else: @@ -856,8 +857,9 @@ def _get_algolia_products_for_batch( academy_uuids_by_key[content_key].add(str(academy.uuid)) academy_uuids_by_catalog_uuid[str(catalog.uuid)].add(str(academy.uuid)) for tag in associated_academy_tags: - academy_tags_by_key[content_key].add(str(tag.title)) - academy_tags_by_catalog_uuid[str(catalog.uuid)].add(str(tag.title)) + if tag.content_metadata.filter(content_key=content_key): + academy_tags_by_key[content_key].add(str(tag.title)) + academy_tags_by_catalog_uuid[str(catalog.uuid)].add(str(tag.title)) # Second pass. This time the goal is to capture indirect relationships on programs: # * For each program: diff --git a/enterprise_catalog/apps/api/tests/test_tasks.py b/enterprise_catalog/apps/api/tests/test_tasks.py index 73f5fb6c9..1e8645908 100644 --- a/enterprise_catalog/apps/api/tests/test_tasks.py +++ b/enterprise_catalog/apps/api/tests/test_tasks.py @@ -681,11 +681,13 @@ def setUp(self): # Set up a catalog, query, and metadata for a course and course associated program self.academy = AcademyFactory() + self.tag1 = self.academy.tags.all()[0] self.enterprise_catalog_query = CatalogQueryFactory(uuid=SORTED_QUERY_UUID_LIST[0]) self.enterprise_catalog_courses = EnterpriseCatalogFactory(catalog_query=self.enterprise_catalog_query) self.enterprise_catalog_courses.academies.add(self.academy) self.course_metadata_published = ContentMetadataFactory(content_type=COURSE, content_key='course-1') self.course_metadata_published.catalog_queries.set([self.enterprise_catalog_query]) + self.course_metadata_published.tags.set([self.tag1]) self.course_metadata_unpublished = ContentMetadataFactory(content_type=COURSE, content_key='course-2') self.course_metadata_unpublished.json_metadata.get('course_runs')[0].update({ 'status': 'unpublished', @@ -723,7 +725,7 @@ def _set_up_factory_data_for_algolia(self): str(self.enterprise_catalog_course_runs.enterprise_uuid), ]) expected_academy_uuids = [str(self.academy.uuid)] - expected_academy_tags = sorted([tag.title for tag in self.academy.tags.all()]) + expected_academy_tags = sorted([self.tag1.title]) expected_queries = sorted([( str(self.enterprise_catalog_courses.catalog_query.uuid), self.enterprise_catalog_courses.catalog_query.title, @@ -1118,7 +1120,7 @@ def mock_replace_all_objects(products_iterable): tasks.index_enterprise_catalog_in_algolia_task() # pylint: disable=no-value-for-parameter products_found_log_records = [record for record in info_logs.output if ' products found.' in record] - assert ' 10 products found.' in products_found_log_records[0] + assert ' 9 products found.' in products_found_log_records[0] # create expected data to be added/updated in the Algolia index. expected_course_1_objects_to_index = [] @@ -1164,10 +1166,6 @@ def mock_replace_all_objects(products_iterable): 'objectID': f'program-{program_uuid}-academy-uuids-0', 'academy_uuids': [str(self.enterprise_catalog_courses.academies.first().uuid)], }) - expected_program_1_objects_to_index.append({ - 'objectID': f'program-{program_uuid}-academy-tags-0', - 'academy_tags': sorted([tag.title for tag in self.enterprise_catalog_courses.academies.first().tags.all()]), - }) expected_program_1_objects_to_index.append({ 'objectID': f'program-{program_uuid}-catalog-query-uuids-0', 'enterprise_catalog_query_uuids': [str(self.enterprise_catalog_courses.catalog_query.uuid)], @@ -1331,7 +1329,7 @@ def mock_replace_all_objects(products_iterable): tasks.index_enterprise_catalog_in_algolia_task() # pylint: disable=no-value-for-parameter products_found_log_records = [record for record in info_logs.output if ' products found.' in record] - assert ' 10 products found.' in products_found_log_records[0] + assert ' 9 products found.' in products_found_log_records[0] # create expected data to be added/updated in the Algolia index. expected_course_1_objects_to_index = [] @@ -1380,11 +1378,6 @@ def mock_replace_all_objects(products_iterable): 'objectID': f'learnerpathway-{pathway_uuid}-academy-uuids-0', 'academy_uuids': [str(self.enterprise_catalog_courses.academies.first().uuid)], }) - expected_pathway_1_objects_to_index.append({ - 'key': pathway_1.content_key, - 'objectID': f'learnerpathway-{pathway_uuid}-academy-tags-0', - 'academy_tags': sorted([tag.title for tag in self.enterprise_catalog_courses.academies.first().tags.all()]), - }) expected_pathway_1_objects_to_index.append({ 'key': pathway_1.content_key, 'objectID': f'learnerpathway-{pathway_uuid}-catalog-query-uuids-0', @@ -1452,7 +1445,7 @@ def mock_replace_all_objects(products_iterable): tasks.index_enterprise_catalog_in_algolia_task() # pylint: disable=no-value-for-parameter products_found_log_records = [record for record in info_logs.output if ' products found.' in record] - assert ' 15 products found.' in products_found_log_records[0] + assert ' 13 products found.' in products_found_log_records[0] # create expected data to be added/updated in the Algolia index. expected_course_1_objects_to_index = [] @@ -1498,10 +1491,6 @@ def mock_replace_all_objects(products_iterable): 'objectID': f'program-{program_uuid}-academy-uuids-0', 'academy_uuids': [str(self.enterprise_catalog_courses.academies.first().uuid)], }) - expected_program_1_objects_to_index.append({ - 'objectID': f'program-{program_uuid}-academy-tags-0', - 'academy_tags': sorted([tag.title for tag in self.enterprise_catalog_courses.academies.first().tags.all()]), - }) expected_program_1_objects_to_index.append({ 'objectID': f'program-{program_uuid}-catalog-query-uuids-0', 'enterprise_catalog_query_uuids': [str(self.enterprise_catalog_courses.catalog_query.uuid)], @@ -1525,11 +1514,6 @@ def mock_replace_all_objects(products_iterable): 'objectID': f'learnerpathway-{pathway_uuid}-academy-uuids-0', 'academy_uuids': [str(self.enterprise_catalog_courses.academies.first().uuid)], }) - expected_pathway_1_objects_to_index.append({ - 'key': pathway_1.content_key, - 'objectID': f'learnerpathway-{pathway_uuid}-academy-tags-0', - 'academy_tags': sorted([tag.title for tag in self.enterprise_catalog_courses.academies.first().tags.all()]), - }) expected_pathway_1_objects_to_index.append({ 'key': pathway_1.content_key, 'objectID': f'learnerpathway-{pathway_uuid}-catalog-query-uuids-0', @@ -1633,7 +1617,7 @@ def mock_replace_all_objects(products_iterable): tasks.index_enterprise_catalog_in_algolia_task() # pylint: disable=no-value-for-parameter products_found_log_records = [record for record in info_logs.output if ' products found.' in record] - assert ' 25 products found.' in products_found_log_records[0] + assert ' 23 products found.' in products_found_log_records[0] # create expected data to be added/updated in the Algolia index. expected_algolia_objects_to_index = [] @@ -1679,10 +1663,6 @@ def mock_replace_all_objects(products_iterable): 'objectID': f'program-{program_uuid}-academy-uuids-0', 'academy_uuids': [str(self.enterprise_catalog_courses.academies.first().uuid)], }) - expected_algolia_program_objects3.append({ - 'objectID': f'program-{program_uuid}-academy-tags-0', - 'academy_tags': sorted([tag.title for tag in self.enterprise_catalog_courses.academies.first().tags.all()]), - }) expected_algolia_program_objects3.append({ 'objectID': f'program-{program_uuid}-catalog-query-uuids-0', 'enterprise_catalog_query_uuids': [str(self.enterprise_catalog_courses.catalog_query.uuid)], @@ -1735,11 +1715,6 @@ def mock_replace_all_objects(products_iterable): 'objectID': f'learnerpathway-{pathway_uuid}-academy-uuids-0', 'academy_uuids': [str(self.enterprise_catalog_courses.academies.first().uuid)], }) - expected_algolia_pathway_objects2.append({ - 'key': pathway_for_courserun.content_key, - 'objectID': f'learnerpathway-{pathway_uuid}-academy-tags-0', - 'academy_tags': sorted([tag.title for tag in self.enterprise_catalog_courses.academies.first().tags.all()]), - }) expected_algolia_pathway_objects2.append({ 'key': pathway_for_courserun.content_key, 'objectID': f'learnerpathway-{pathway_uuid}-catalog-query-uuids-0', @@ -1813,7 +1788,7 @@ def mock_replace_all_objects(products_iterable): with self.assertLogs(level='INFO') as info_logs: tasks.index_enterprise_catalog_in_algolia_task() # pylint: disable=no-value-for-parameter - assert ' 9 products found.' in info_logs.output[-1] + assert ' 8 products found.' in info_logs.output[-1] # create expected data to be added/updated in the Algolia index. expected_algolia_objects_to_index = [] @@ -1848,11 +1823,6 @@ def mock_replace_all_objects(products_iterable): 'objectID': f'course-{published_course_uuid}-academy-tags-0', 'academy_tags': [algolia_data['academy_tags'][0]], }) - expected_algolia_objects_to_index.append({ - 'key': algolia_data['course_metadata_published'].content_key, - 'objectID': f'course-{published_course_uuid}-academy-tags-1', - 'academy_tags': [algolia_data['academy_tags'][1]], - }) expected_algolia_objects_to_index.append({ 'key': algolia_data['course_metadata_published'].content_key, 'objectID': f'course-{published_course_uuid}-catalog-query-uuids-0', @@ -1893,7 +1863,7 @@ def mock_replace_all_objects(products_iterable): with self.assertLogs(level='INFO') as info_logs: tasks.index_enterprise_catalog_in_algolia_task() # pylint: disable=no-value-for-parameter - assert ' 9 products found.' in info_logs.output[-1] + assert ' 8 products found.' in info_logs.output[-1] # create expected data to be added/updated in the Algolia index. expected_algolia_objects_to_index = [] @@ -1928,11 +1898,6 @@ def mock_replace_all_objects(products_iterable): 'objectID': f'course-{published_course_uuid}-academy-tags-0', 'academy_tags': [algolia_data['academy_tags'][0]], }) - expected_algolia_objects_to_index.append({ - 'key': algolia_data['course_metadata_published'].content_key, - 'objectID': f'course-{published_course_uuid}-academy-tags-1', - 'academy_tags': [algolia_data['academy_tags'][1]], - }) expected_algolia_objects_to_index.append({ 'key': algolia_data['course_metadata_published'].content_key, 'objectID': f'course-{published_course_uuid}-catalog-query-uuids-0', @@ -2004,7 +1969,7 @@ def test_index_algolia_dry_run(self, mock_search_client): tasks.index_enterprise_catalog_in_algolia_task(force, dry_run) mock_search_client().replace_all_objects.assert_not_called() - assert '[ENTERPRISE_CATALOG_ALGOLIA_REINDEX] [DRY RUN] 9 products found.' in info_logs.output[-1] + assert '[ENTERPRISE_CATALOG_ALGOLIA_REINDEX] [DRY RUN] 8 products found.' in info_logs.output[-1] assert any( '[ENTERPRISE_CATALOG_ALGOLIA_REINDEX] [DRY RUN] skipping algolia_client.replace_all_objects().' in record for record in info_logs.output