def test_signals_categories_publish(self, mock_bulk, *_): """ Publishing a category should update its document in the Elasticsearch categories index, and the documents for published courses to which it is related, excluding snapshots. """ parent = CategoryFactory(should_publish=True) category = CategoryFactory(page_parent=parent.extended_object) published_course, _unpublished_course = CourseFactory.create_batch( 2, fill_categories=[category]) self.assertTrue(published_course.extended_object.publish("en")) published_course.refresh_from_db() self.run_commit_hooks() mock_bulk.reset_mock() self.assertTrue(category.extended_object.publish("en")) category.refresh_from_db() # Elasticsearch should not be called before the db transaction is successful self.assertFalse(mock_bulk.called) self.run_commit_hooks() self.assertEqual(mock_bulk.call_count, 1) self.assertEqual(len(mock_bulk.call_args[1]["actions"]), 3) actions = list(mock_bulk.call_args[1]["actions"]) self.assertEqual(actions[0]["_id"], published_course.get_es_id()) self.assertEqual(actions[0]["_op_type"], "index") self.assertEqual(actions[0]["_index"], "test_courses") self.assertEqual(actions[1]["_id"], category.get_es_id()) self.assertEqual(actions[1]["_op_type"], "index") self.assertEqual(actions[1]["_index"], "richie_categories") self.assertEqual(actions[2]["_id"], parent.get_es_id()) self.assertEqual(actions[2]["_op_type"], "index") self.assertEqual(actions[2]["_index"], "richie_categories")
def test_signals_categories_unpublish(self, mock_bulk, *_): """ Unpublishing a category in one language should update its document in the Elasticsearch categories index or delete it if there is no language published anymore. It should also update the documents for published courses to which it is related excluding snapshots. """ category = CategoryFactory(page_languages=["en", "fr"], should_publish=True) published_course, _unpublished_course = CourseFactory.create_batch( 2, fill_categories=[category]) self.assertTrue(published_course.extended_object.publish("en")) published_course.refresh_from_db() self.run_commit_hooks() mock_bulk.reset_mock() # - Unpublish the first language self.assertTrue(category.extended_object.unpublish("en")) category.refresh_from_db() # Elasticsearch should not be called before the db transaction is successful self.assertFalse(mock_bulk.called) self.run_commit_hooks() self.assertEqual(mock_bulk.call_count, 1) self.assertEqual(len(mock_bulk.call_args[1]["actions"]), 2) actions = list(mock_bulk.call_args[1]["actions"]) self.assertEqual(actions[0]["_id"], published_course.get_es_id()) self.assertEqual(actions[0]["_op_type"], "index") self.assertEqual(actions[0]["_index"], "test_courses") self.assertEqual(actions[1]["_id"], category.get_es_id()) self.assertEqual(actions[1]["_op_type"], "index") self.assertEqual(actions[1]["_index"], "richie_categories") mock_bulk.reset_mock() # - Unpublish the second language self.assertTrue(category.extended_object.unpublish("fr")) category.refresh_from_db() # Elasticsearch should not be called before the db transaction is successful self.assertFalse(mock_bulk.called) self.run_commit_hooks() self.assertEqual(mock_bulk.call_count, 1) self.assertEqual(len(mock_bulk.call_args[1]["actions"]), 2) actions = list(mock_bulk.call_args[1]["actions"]) self.assertEqual(actions[0]["_id"], published_course.get_es_id()) self.assertEqual(actions[0]["_op_type"], "index") self.assertEqual(actions[0]["_index"], "test_courses") self.assertEqual(actions[1]["_id"], category.get_es_id()) self.assertEqual(actions[1]["_op_type"], "delete") self.assertEqual(actions[1]["_index"], "richie_categories")
def test_indexers_categories_get_es_documents_unpublished(self): """ Unpublished categories and children of unpublished categories should not be indexed """ # Our meta category and its page meta = CategoryFactory( page_parent=create_i18n_page("Categories", published=True), page_reverse_id="subjects", page_title="Subjects", should_publish=True, ) parent = CategoryFactory(page_parent=meta.extended_object, should_publish=True) CategoryFactory( page_parent=parent.extended_object, page_title="my second subject", should_publish=True, ) # Unpublish the parent category self.assertTrue(parent.extended_object.unpublish("en")) # The unpublished parent category and its child should not get indexed self.assertEqual( list( CategoriesIndexer.get_es_documents(index="some_index", action="some_action")), [ { "_id": meta.get_es_id(), "_index": "some_index", "_op_type": "some_action", "absolute_url": { "en": "/en/categories/subjects/", "fr": "/fr/categories/subjects/", }, "complete": { "en": ["Subjects"] }, "description": {}, "icon": {}, "is_meta": True, "kind": "meta", "logo": {}, "nb_children": 1, "path": "00010001", "title": { "en": "Subjects" }, "title_raw": { "en": "Subjects" }, }, ], )
def test_filter_definitions_indexable_filter_aggs_include_published_page( self): """ The indexable filters (subjects, levels and organizations) should return a list of objects that are direct children of the filter pages if those exist and are published. """ for filter_name in ["levels", "subjects", "organizations"]: filter_page = CategoryFactory(page_reverse_id=filter_name, should_publish=True) item = CategoryFactory(page_parent=filter_page.extended_object, should_publish=True) expected_include = [str(item.get_es_id())] with self.assertNumQueries(2): self.assertEqual( FILTERS[filter_name].aggs_include, expected_include, ) with self.assertNumQueries(0): self.assertEqual( FILTERS[filter_name].aggs_include, expected_include, ) # Reset cache for subsequent tests... # pylint: disable=protected-access FILTERS[filter_name]._base_page = None caches["search"].clear() with self.assertNumQueries(2): self.assertEqual( FILTERS[filter_name].aggs_include, expected_include, ) with self.assertNumQueries(0): self.assertEqual( FILTERS[filter_name].aggs_include, expected_include, ) # Reset cache for subsequent tests... # pylint: disable=protected-access FILTERS[filter_name]._base_page = None
def test_indexable_filters_internationalization(self): """ Indexable filters (such as categories and organizations by default) should have their names localized in the filter definitions in course search responses. """ # Create the meta categories, each with a child category that should appear in facets subjects_meta = CategoryFactory(page_reverse_id="subjects", should_publish=True) subject = CategoryFactory(page_parent=subjects_meta.extended_object, should_publish=True) levels_meta = CategoryFactory(page_reverse_id="levels", should_publish=True) level = CategoryFactory(page_parent=levels_meta.extended_object, should_publish=True) # Create 2 organizations that should appear in facets org_meta = OrganizationFactory(page_reverse_id="organizations", should_publish=True) org_1 = OrganizationFactory( page_parent=org_meta.extended_object, page_title="First organization", should_publish=True, ) org_2 = OrganizationFactory( page_parent=org_meta.extended_object, page_title="Second organization", should_publish=True, ) # Create a course linked to our categories and organizations CourseFactory( fill_categories=[subject, level], fill_organizations=[org_1, org_2], should_publish=True, ) # Index our objects into ES bulk_compat( actions=[ *ES_INDICES.categories.get_es_documents(), *ES_INDICES.organizations.get_es_documents(), *ES_INDICES.courses.get_es_documents(), ], chunk_size=500, client=ES_CLIENT, ) ES_INDICES_CLIENT.refresh() response = self.client.get("/api/v1.0/courses/?scope=filters") self.assertEqual(response.status_code, 200) self.assertEqual( response.json()["filters"]["subjects"], { "base_path": "0001", "human_name": "Subjects", "is_autocompletable": True, "is_drilldown": False, "is_searchable": True, "name": "subjects", "position": 2, "has_more_values": False, "values": [{ "count": 1, "human_name": subject.extended_object.get_title(), "key": subject.get_es_id(), }], }, ) self.assertEqual( response.json()["filters"]["levels"], { "base_path": "0002", "human_name": "Levels", "is_autocompletable": True, "is_drilldown": False, "is_searchable": True, "name": "levels", "position": 3, "has_more_values": False, "values": [{ "count": 1, "human_name": level.extended_object.get_title(), "key": level.get_es_id(), }], }, ) self.assertEqual( response.json()["filters"]["organizations"], { "base_path": "0003", "human_name": "Organizations", "is_autocompletable": True, "is_drilldown": False, "is_searchable": True, "name": "organizations", "position": 4, "has_more_values": False, "values": [ { "count": 1, "human_name": org_1.extended_object.get_title(), "key": org_1.get_es_id(), }, { "count": 1, "human_name": org_2.extended_object.get_title(), "key": org_2.get_es_id(), }, ], }, )
def test_indexers_categories_get_es_documents(self, _mock_picture): """ Happy path: the data is fetched from the models properly formatted """ # Our meta category and its page meta = CategoryFactory( page_parent=create_i18n_page( { "en": "Categories", "fr": "Catégories" }, published=True), page_reverse_id="subjects", page_title={ "en": "Subjects", "fr": "Sujets" }, fill_icon=True, fill_logo=True, should_publish=True, ) category1 = CategoryFactory( page_parent=meta.extended_object, page_title={ "en": "my first subject", "fr": "ma première thématique" }, fill_icon=True, fill_logo=True, should_publish=True, ) category2 = CategoryFactory( page_parent=category1.extended_object, page_title={ "en": "my second subject", "fr": "ma deuxième thématic" }, should_publish=True, ) # Add a description in several languages to the first category placeholder = category1.public_extension.extended_object.placeholders.get( slot="description") plugin_params = { "placeholder": placeholder, "plugin_type": "CKEditorPlugin" } add_plugin(body="english description line 1.", language="en", **plugin_params) add_plugin(body="english description line 2.", language="en", **plugin_params) add_plugin(body="description français ligne 1.", language="fr", **plugin_params) add_plugin(body="description français ligne 2.", language="fr", **plugin_params) # The results were properly formatted and passed to the consumer self.assertEqual( list( CategoriesIndexer.get_es_documents(index="some_index", action="some_action")), [ { "_id": category2.get_es_id(), "_index": "some_index", "_op_type": "some_action", "absolute_url": { "en": "/en/categories/subjects/my-first-subject/my-second-subject/", "fr": "/fr/categories/sujets/ma-premiere-thematique/ma-deuxieme-thematic/", }, "complete": { "en": ["my second subject", "second subject", "subject"], "fr": [ "ma deuxième thématic", "deuxième thématic", "thématic" ], }, "description": {}, "icon": {}, "is_meta": False, "kind": "subjects", "logo": {}, "nb_children": 0, "path": "0001000100010001", "title": { "en": "my second subject", "fr": "ma deuxième thématic" }, "title_raw": { "en": "my second subject", "fr": "ma deuxième thématic", }, }, { "_id": category1.get_es_id(), "_index": "some_index", "_op_type": "some_action", "absolute_url": { "en": "/en/categories/subjects/my-first-subject/", "fr": "/fr/categories/sujets/ma-premiere-thematique/", }, "complete": { "en": ["my first subject", "first subject", "subject"], "fr": [ "ma première thématique", "première thématique", "thématique", ], }, "description": { "en": "english description line 1. english description line 2.", "fr": "description français ligne 1. description français ligne 2.", }, "icon": { "en": "picture info", "fr": "picture info" }, "is_meta": False, "kind": "subjects", "logo": { "en": "picture info", "fr": "picture info" }, "nb_children": 1, "path": "000100010001", "title": { "en": "my first subject", "fr": "ma première thématique" }, "title_raw": { "en": "my first subject", "fr": "ma première thématique", }, }, { "_id": meta.get_es_id(), "_index": "some_index", "_op_type": "some_action", "absolute_url": { "en": "/en/categories/subjects/", "fr": "/fr/categories/sujets/", }, "complete": { "en": ["Subjects"], "fr": ["Sujets"] }, "description": {}, "icon": { "en": "picture info", "fr": "picture info" }, "is_meta": True, "kind": "meta", "logo": { "en": "picture info", "fr": "picture info" }, "nb_children": 1, "path": "00010001", "title": { "en": "Subjects", "fr": "Sujets" }, "title_raw": { "en": "Subjects", "fr": "Sujets" }, }, ], )