def test_signals_categories_no_parent(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. """ category = CategoryFactory() published_course, _unpublished_course = CourseFactory.create_batch( 2, fill_categories=[category]) published_course.extended_object.publish("en") published_course.refresh_from_db() self.run_commit_hooks() mock_bulk.reset_mock() 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"]), 2) actions = list(mock_bulk.call_args[1]["actions"]) self.assertEqual( actions[0]["_id"], str(published_course.public_extension.extended_object_id)) self.assertEqual(actions[0]["_type"], "course") self.assertEqual(actions[1]["_id"], "L-0001") self.assertEqual(actions[1]["_type"], "category")
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_move(self, mock_bulk, *_): """ Make sure all categories are re-indexed when a category page is moved in the CMS page tree. """ user = UserFactory(is_staff=True, is_superuser=True) # Create a hierarchy of courses, with 2 parents to move the child between them grandparent = CategoryFactory(page_reverse_id="subjects", should_publish=True) parent_1 = CategoryFactory(page_parent=grandparent.extended_object, should_publish=True) parent_2 = CategoryFactory(page_parent=grandparent.extended_object, should_publish=True) child = CategoryFactory(page_parent=parent_1.extended_object, should_publish=True) # Create a course so we can verify it's not re-indexed when the linked category is moved CourseFactory(fill_categories=[child]) self.assertEqual(child.extended_object.parent_page.id, parent_1.extended_object.id) self.client.force_login(user) self.client.post( f"/en/admin/cms/page/{child.extended_object.id}/move-page/", { "position": "0", "id": str(child.extended_object.id), "target": str(parent_2.extended_object.id), "site": "1", }, ) child.refresh_from_db() self.assertEqual(child.extended_object.parent_page.id, parent_2.extended_object.id) self.assertEqual(mock_bulk.call_count, 1) actions = [*mock_bulk.call_args[1]["actions"]] self.assertEqual(len(actions), 4) self.assertEqual(actions[0]["_id"], str(child.public_extension.extended_object_id)) self.assertEqual(actions[0]["_index"], "richie_categories") self.assertEqual(actions[0]["_op_type"], "index") self.assertEqual(actions[1]["_id"], str(parent_2.public_extension.extended_object_id)) self.assertEqual(actions[1]["_index"], "richie_categories") self.assertEqual(actions[1]["_op_type"], "index") self.assertEqual(actions[2]["_id"], str(parent_1.public_extension.extended_object_id)) self.assertEqual(actions[2]["_index"], "richie_categories") self.assertEqual(actions[2]["_op_type"], "index") self.assertEqual(actions[3]["_id"], str(grandparent.public_extension.extended_object_id)) self.assertEqual(actions[3]["_index"], "richie_categories") self.assertEqual(actions[3]["_op_type"], "index")
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_cms_plugins_category_fallback_when_never_published(self): """ The category plugin should render in the fallback language when the category page has never been published in the current language. """ # Create a category category = CategoryFactory( page_title={"en": "public title", "fr": "titre public"}, fill_logo={"original_filename": "logo.jpg", "default_alt_text": "my logo"}, ) category_page = category.extended_object # Create a page to add the plugin to page = create_i18n_page({"en": "A page", "fr": "Une page"}) placeholder = page.placeholders.get(slot="maincontent") add_plugin(placeholder, CategoryPlugin, "en", **{"page": category_page}) add_plugin(placeholder, CategoryPlugin, "fr", **{"page": category_page}) # Publish only the French version of the category category_page.publish("fr") # Check the page content in English page.publish("en") url = page.get_absolute_url(language="en") response = self.client.get(url) # The english category's name should be present as a link to the cms page # But the locale in the url should remain "en" self.assertIn( '<a class="category-glimpse" href="/en/titre-public/"', re.sub(" +", " ", str(response.content).replace("\\n", "")), ) # The category's title should be wrapped in a p category.refresh_from_db() self.assertContains( response, '<h2 class="category-glimpse__title">titre public</h2>', html=True, ) self.assertNotContains(response, "public title") # category's cover should be present pattern = ( r'<div class="category-glimpse__logo">' r'<img src="/media/filer_public_thumbnails/filer_public/.*logo\.jpg__200x200' r'.*alt=""' ) self.assertIsNotNone(re.search(pattern, str(response.content)))
def test_admin_category_change_view_post(self): """Validate that the category can be updated via the admin.""" user = UserFactory(is_staff=True, is_superuser=True) self.client.login(username=user.username, password="******") # Create a category category = CategoryFactory() # Get the admin change view url = reverse("admin:courses_category_change", args=[category.id]) data = {"color": "#c71414"} response = self.client.post(url, data) self.assertEqual(response.status_code, 302) # Check that the category was updated as expected category.refresh_from_db() self.assertEqual(category.color, "#c71414")
def test_cms_plugins_organizations_by_category_render_on_public_page(self): """ The organizations by category plugin should render as expected on a public page. """ # Create a category category = CategoryFactory( page_title={"en": "category title", "fr": "titre catégorie"} ) category_page = category.extended_object # Create organizations published_organization = OrganizationFactory( page_title={"en": "public title", "fr": "titre public"}, fill_categories=[category], fill_logo={"original_filename": "logo.jpg"}, should_publish=True, ) OrganizationFactory( page_title={"en": "private title", "fr": "titre privé"}, fill_categories=[category], fill_logo={"original_filename": "logo.jpg"}, ) # Create a page to add the plugin to page = create_i18n_page({"en": "A page", "fr": "Une page"}) placeholder = page.placeholders.get(slot="maincontent") add_plugin( placeholder, OrganizationsByCategoryPlugin, "en", **{"page": category_page} ) add_plugin( placeholder, OrganizationsByCategoryPlugin, "fr", **{"page": category_page} ) category_page.publish("en") category_page.publish("fr") category.refresh_from_db() page.publish("en") page.publish("fr") # Check the page content in English url = page.get_absolute_url(language="en") # The plugin should not be visible on the public page before it is published category_page.unpublish("en") response = self.client.get(url) self.assertNotContains(response, "public title") # # Republish the plugin category_page.publish("en") # Now modify the organization to have a draft different from the public version title_obj = published_organization.extended_object.get_title_obj(language="en") title_obj.title = "draft title" title_obj.save() # Publishing the page again should make the plugin public page.publish("en") # Check the page content in English response = self.client.get(url) # The organization's name should be present as a link to the cms page # And CMS page title should be in title attribute of the link self.assertIn( '<a class="organization-glimpse" ' 'href="/en/public-title/" title="public title"', re.sub(" +", " ", str(response.content).replace("\\n", "")), ) # The organization's title should be wrapped in a div self.assertContains( response, '<div class="organization-glimpse__title">{:s}</div>'.format( published_organization.public_extension.extended_object.get_title() ), html=True, ) self.assertNotContains(response, "draft") self.assertNotContains(response, "private") # Organization's logo should be present pattern = ( r'<div class="organization-glimpse__logo">' r'<img src="/media/filer_public_thumbnails/filer_public/.*logo\.jpg__200x113' r'.*alt=""' ) self.assertIsNotNone(re.search(pattern, str(response.content))) # Same checks in French url = page.get_absolute_url(language="fr") response = self.client.get(url) self.assertIn( '<a class="organization-glimpse" ' 'href="/fr/titre-public/" title="titre public"', re.sub(" +", " ", str(response.content).replace("\\n", "")), ) pattern = ( r'<div class="organization-glimpse__logo">' r'<img src="/media/filer_public_thumbnails/filer_public/.*logo\.jpg__200x113' r'.*alt=""' ) self.assertIsNotNone(re.search(pattern, str(response.content)))
def test_cms_plugins_category_render_on_public_page(self): """ The category plugin should render as expected on a public page. """ # Create a Category category = CategoryFactory( page_title={"en": "public title", "fr": "titre publique"}, fill_logo={"original_filename": "logo.jpg", "default_alt_text": "my logo"}, ) category_page = category.extended_object # Create a page to add the plugin to page = create_i18n_page({"en": "A page", "fr": "Une page"}) placeholder = page.placeholders.get(slot="maincontent") add_plugin(placeholder, CategoryPlugin, "en", **{"page": category_page}) add_plugin(placeholder, CategoryPlugin, "fr", **{"page": category_page}) category_page.publish("en") category_page.publish("fr") category.refresh_from_db() page.publish("en") page.publish("fr") url = page.get_absolute_url(language="en") # The category plugin should not be visible on the public page before it is published category_page.unpublish("en") response = self.client.get(url) self.assertNotContains(response, "public title") # Republish the plugin category_page.publish("en") # Now modify the category to have a draft different from the public version title_obj = category_page.get_title_obj(language="en") title_obj.title = "draft title" title_obj.save() # Publishing the page again should make the plugin public page.publish("en") # Check the page content in English response = self.client.get(url) # Category's title should be present as a link to the cms page # And CMS page title should be in title attribute of the link self.assertContains( response, '<a class="category-glimpse" href="/en/public-title/"', status_code=200, ) # The category's title should be wrapped in a div self.assertContains( response, '<h2 class="category-glimpse__title">{:s}</h2>'.format( category.public_extension.extended_object.get_title() ), html=True, ) self.assertNotContains(response, "draft title") # Category's logo should be present pattern = ( r'<div class="category-glimpse__logo">' r'<img src="/media/filer_public_thumbnails/filer_public/.*logo\.jpg__200x200' r'.*alt=""' ) self.assertIsNotNone(re.search(pattern, str(response.content))) # Same checks in French url = page.get_absolute_url(language="fr") response = self.client.get(url) self.assertContains( response, '<a class="category-glimpse" href="/fr/titre-publique/"', status_code=200, ) pattern = ( r'<div class="category-glimpse__logo">' r'<img src="/media/filer_public_thumbnails/filer_public/.*logo\.jpg__200x200' r'.*alt=""' ) self.assertIsNotNone(re.search(pattern, str(response.content)))
def test_cms_plugins_category_render_on_public_page(self): """ The category plugin should render as expected on a public page. """ # Create a filer fake image image = FilerImageFactory() # Create a Category category = CategoryFactory(page_title={ "en": "public title", "fr": "titre publique" }) category_page = category.extended_object # Add logo to related placeholder logo_placeholder = category_page.placeholders.get(slot="logo") add_plugin( logo_placeholder, PicturePlugin, "en", **{ "picture": image, "attributes": { "alt": "logo description" } }) add_plugin( logo_placeholder, PicturePlugin, "fr", **{ "picture": image, "attributes": { "alt": "description du logo" } }) # Create a page to add the plugin to page = create_i18n_page({"en": "A page", "fr": "Une page"}) placeholder = page.placeholders.get(slot="maincontent") add_plugin(placeholder, CategoryPlugin, "en", **{"page": category_page}) add_plugin(placeholder, CategoryPlugin, "fr", **{"page": category_page}) category_page.publish("en") category_page.publish("fr") category.refresh_from_db() page.publish("en") page.publish("fr") url = page.get_absolute_url(language="en") # The category plugin should not be visible on the public page before it is published category_page.unpublish("en") response = self.client.get(url) self.assertNotContains(response, "public title") # Republish the plugin category_page.publish("en") # Now modify the category to have a draft different from the public version title_obj = category_page.get_title_obj(language="en") title_obj.title = "draft title" title_obj.save() # Publishing the page again should make the plugin public page.publish("en") # Check the page content in English response = self.client.get(url) # Category's title should be present as a link to the cms page # And CMS page title should be in title attribute of the link self.assertContains( response, '<a class="category-plugin__body" href="/en/public-title/" title="{title:s}"' .format( title=category.public_extension.extended_object.get_title()), status_code=200, ) # The category's title should be wrapped in a div self.assertContains( response, '<div class="category-plugin__title">{:s}</div>'.format( category.public_extension.extended_object.get_title()), html=True, ) self.assertNotContains(response, "draft title") # Category's logo should be present # pylint: disable=no-member self.assertContains(response, image.file.name) # Same checks in French url = page.get_absolute_url(language="fr") response = self.client.get(url) self.assertContains( response, '<a class="category-plugin__body" href="/fr/titre-publique/" title="{title:s}"' .format( title=category.public_extension.extended_object.get_title()), status_code=200, ) # pylint: disable=no-member self.assertContains(response, image.file.name)