def test_indexers_courses_get_es_document_no_image_icon_picture(self): """ ES document is created without errors when a icon image for the course is actually a Picture instance without an image on it. """ # Create the example course to index and get hold of its course_icons placeholder course = CourseFactory(should_publish=True) course_icons_placeholder = course.extended_object.placeholders.filter( slot="course_icons").first() # Create a category and add it to the course on the icons placeholder category = CategoryFactory(should_publish=True, color="#654321") add_plugin(course_icons_placeholder, CategoryPlugin, "en", **{"page": category.extended_object}) course.extended_object.publish("en") # Make sure we associate an image-less picture with the category through # the icon placeholder category_icon_placeholder = category.extended_object.placeholders.filter( slot="icon").first() add_plugin(category_icon_placeholder, SimplePicturePlugin, "en", picture=None) category.extended_object.publish("en") indexed_courses = list( CoursesIndexer.get_es_documents(index="some_index", action="some_action")) self.assertEqual(len(indexed_courses), 1) self.assertEqual( indexed_courses[0]["_id"], str(course.extended_object.get_public_object().id), ) self.assertEqual( indexed_courses[0]["icon"], { "en": { "color": "#654321", "title": category.extended_object.get_title() } }, )
def test_cms_plugins_organizations_by_category_form_page_choices(self): """ The form to create an organizations by category plugin should only list category pages in the select box. There shouldn't be any duplicate because of published status. """ class OrganizationsByCategoryPluginModelForm(forms.ModelForm): """A form for testing the choices in the select box""" class Meta: model = OrganizationsByCategoryPluginModel fields = ["page"] category = CategoryFactory(should_publish=True) PageFactory(title__title="other page", should_publish=True) plugin_form = OrganizationsByCategoryPluginModelForm() rendered_form = plugin_form.as_table() self.assertEqual( rendered_form.count(category.extended_object.get_title()), 1) self.assertNotIn("other", plugin_form.as_table())
def test_models_category_get_children_categories(self): """ It should be possible to retrieve the list of direct children page which are Category extensions and not any other type. """ empty_category = CategoryFactory(should_publish=True) parent_category = CategoryFactory(should_publish=True) child_categories = CategoryFactory.create_batch( 2, page_parent=parent_category.extended_object, should_publish=True ) with self.assertNumQueries(2): self.assertFalse(empty_category.get_children_categories().exists()) with self.assertNumQueries(2): self.assertEqual( set(parent_category.get_children_categories()), set(child_categories) )
def test_template_category_detail_meta_description(self): """ The category meta description should show meta_description placeholder if defined """ category = CategoryFactory() page = category.extended_object title_obj = page.get_title_obj(language="en") title_obj.meta_description = "A custom description of the category" title_obj.save() page.publish("en") url = category.extended_object.get_absolute_url() response = self.client.get(url) self.assertEqual(response.status_code, 200) self.assertContains( response, '<meta name="description" content="A custom description of the category" />', )
def test_indexers_courses_related_objects_consistency(self): """ The organization and category ids in the Elasticsearch course document should be the same as the ids with which the corresponding organization and category objects are indexed. """ # Create a course with a page in both english and french organization = OrganizationFactory(should_publish=True) category = CategoryFactory(should_publish=True) course = CourseFactory( fill_organizations=[organization], fill_categories=[category], should_publish=True, ) CourseRunFactory(page_parent=course.extended_object, should_publish=True) course_document = list( CoursesIndexer.get_es_documents(index="some_index", action="some_action") )[0] self.assertEqual( course_document["organizations"], [ next( OrganizationsIndexer.get_es_documents( index="some_index", action="some_action" ) )["_id"] ], ) self.assertEqual( course_document["categories"], [ next( CategoriesIndexer.get_es_documents( index="some_index", action="some_action" ) )["_id"] ], )
def test_cms_wizards_category_submit_form_from_category_page(self): """ Submitting a valid CategoryWizardForm from a category should create a sub category of this category and its related page. """ # A parent page should pre-exist create_page( "Categories", "richie/single_column.html", "en", reverse_id=Category.PAGE["reverse_id"], ) # Create a category when visiting an existing category parent_category = CategoryFactory() # Create a user with just the required permissions user = UserFactory( is_staff=True, permissions=["courses.add_category", "cms.add_page", "cms.change_page"], ) # We can submit a form with just the title set form = CategoryWizardForm( data={"title": "My title"}, wizard_language="en", wizard_user=user, wizard_page=parent_category.extended_object, ) self.assertTrue(form.is_valid()) page = form.save() # Related page should have been created as draft Page.objects.drafts().get(id=page.id) Category.objects.get(id=page.category.id, extended_object=page) self.assertEqual(page.get_parent_page(), parent_category.extended_object) self.assertEqual(page.get_title(), "My title") # The slug should have been automatically set self.assertEqual(page.get_slug(), "my-title")
def test_cms_plugins_category_render_on_draft_page(self): """ The category plugin should render as expected on a draft page. """ staff = UserFactory(is_staff=True, is_superuser=True) self.client.login(username=staff.username, password="******") # Create a Category category = CategoryFactory(page_title="public title") category_page = category.extended_object # Create a page to add the plugin to page = create_i18n_page("A page") placeholder = page.placeholders.get(slot="maincontent") add_plugin(placeholder, CategoryPlugin, "en", **{"page": category_page}) category_page.publish("en") category_page.unpublish("en") page_url = page.get_absolute_url(language="en") url = f"{page_url:s}?edit" # The unpublished category plugin should not be visible on the draft page response = self.client.get(url) self.assertNotContains(response, "public title") # Now publish the category and modify it to have a draft different from the # public version category_page.publish("en") title_obj = category_page.get_title_obj(language="en") title_obj.title = "draft title" title_obj.save() # The draft version of the category plugin should not be visible response = self.client.get(url) self.assertNotContains(response, "draft title") self.assertContains(response, "public title")
def test_cms_wizards_category_submit_form_slug_duplicate(self): """ Trying to create a category with a slug that would lead to a duplicate path should raise a validation error. """ # A parent page should pre-exist parent_page = create_page( "Categories", "richie/single_column.html", "en", reverse_id=Category.PAGE["reverse_id"], ) # Create an existing page with a known slug CategoryFactory(page_parent=parent_page, page_title="My title") # Submit a title that will lead to the same slug data = {"title": "my title"} user = UserFactory(is_staff=True, is_superuser=True) form = CategoryWizardForm( data=data, wizard_language="en", wizard_user=user, wizard_page=parent_page ) self.assertFalse(form.is_valid()) self.assertEqual(form.errors, {"slug": ["This slug is already in use"]})
def test_templates_organization_detail_cms_published_content(self): """ Validate that the important elements are displayed on a published organization page """ # Categories published_category = CategoryFactory(should_publish=True) extra_published_category = CategoryFactory(should_publish=True) unpublished_category = CategoryFactory(should_publish=True) unpublished_category.extended_object.unpublish("en") not_published_category = CategoryFactory() # Organizations organization = OrganizationFactory( page_title="La Sorbonne", fill_categories=[ published_category, not_published_category, unpublished_category, ], ) # Courses published_course = CourseFactory(fill_organizations=[organization], should_publish=True) extra_published_course = CourseFactory(should_publish=True) unpublished_course = CourseFactory(fill_organizations=[organization]) not_published_course = CourseFactory(fill_organizations=[organization]) # Republish courses to take into account adding the organization published_course.extended_object.publish("en") unpublished_course.extended_object.publish("en") unpublished_course.extended_object.unpublish("en") page = organization.extended_object # The page should not be visible before it is published url = page.get_absolute_url() response = self.client.get(url) self.assertEqual(response.status_code, 404) # Publish the organization page.publish("en") # Modify the draft version of the published category title_obj = published_category.extended_object.title_set.get( language="en") title_obj.title = "modified category" title_obj.save() # Modify the draft version of the published course title_obj = published_course.extended_object.title_set.get( language="en") title_obj.title = "modified course" title_obj.save() # Add a new category to the draft organization page but don't publish the modification add_plugin( page.placeholders.get(slot="categories"), CategoryPlugin, "en", page=extra_published_category.extended_object, ) # Add a new organization to the draft course page but don't publish the modification add_plugin( extra_published_course.extended_object.placeholders.get( slot="course_organizations"), OrganizationPlugin, "en", page=page, ) # Ensure the published page content is correct response = self.client.get(url) self.assertContains(response, "<title>La Sorbonne</title>", html=True, status_code=200) self.assertContains( response, '<h1 class="organization-detail__title">La Sorbonne</h1>', html=True, ) # The published category should be on the page in its published version self.assertContains( response, ('<a class="category-plugin-tag" href="{:s}">' '<div class="category-plugin-tag__title">{:s}</div></a>').format( published_category.public_extension.extended_object. get_absolute_url(), published_category.public_extension.extended_object.get_title( ), ), html=True, ) # The other categories should not be leaked: # - new_category linked only on the draft organization page self.assertNotContains( response, extra_published_category.extended_object.get_title()) # - not published category self.assertNotContains( response, not_published_category.extended_object.get_title()) # - unpublished category self.assertNotContains( response, unpublished_category.extended_object.get_title()) # The published courses should be on the page in its published version self.assertContains( response, '<p class="course-glimpse__content__title">{:s}</p>'.format( published_course.public_extension.extended_object.get_title()), html=True, ) # The other courses should not be leaked: # - new course linked only on the draft organization page self.assertNotContains( response, extra_published_course.extended_object.get_title()) # - not published course self.assertNotContains( response, not_published_course.extended_object.get_title()) # - unpublished course self.assertNotContains(response, unpublished_course.extended_object.get_title()) # Modified draft category and course should not be leaked self.assertNotContains(response, "modified")
def test_templates_person_detail_cms_published_content(self): """ Validate that the important elements are displayed on a published person page """ # Categories published_category = CategoryFactory(should_publish=True) unpublished_category = CategoryFactory(should_publish=True) unpublished_category.extended_object.unpublish("en") extra_published_category = CategoryFactory(should_publish=True) not_published_category = CategoryFactory() # Modify the draft version of the published category title_obj = published_category.extended_object.title_set.get( language="en") title_obj.title = "modified title" title_obj.save() # Organizations published_organization = OrganizationFactory(should_publish=True) unpublished_organization = OrganizationFactory(should_publish=True) unpublished_organization.extended_object.unpublish("en") extra_published_organization = OrganizationFactory(should_publish=True) not_published_organization = OrganizationFactory() # Modify the draft version of the published organization title_obj = published_organization.extended_object.title_set.get( language="en") title_obj.title = "modified title" title_obj.save() person = PersonFactory( page_title="My page title", fill_portrait=True, fill_bio=True, fill_categories=[ published_category, not_published_category, unpublished_category, ], fill_organizations=[ published_organization, not_published_organization, unpublished_organization, ], ) page = person.extended_object # The page should not be visible before it is published url = page.get_absolute_url() response = self.client.get(url) self.assertEqual(response.status_code, 404) # Publish the person page.publish("en") # Add a new category to the draft person page but don't publish the modification add_plugin( page.placeholders.get(slot="categories"), CategoryPlugin, "en", page=extra_published_category.extended_object, ) # Add a new organization to the draft person page but don't publish the modification add_plugin( page.placeholders.get(slot="organizations"), OrganizationPlugin, "en", page=extra_published_organization.extended_object, ) # Ensure the published page content is correct response = self.client.get(url) self.assertContains(response, "<title>My page title</title>", html=True, status_code=200) self.assertContains( response, '<h1 class="subheader__title">{:s}</h1>'.format( person.extended_object.get_title()), html=True, ) # The published category should be on the page in its published version self.assertContains( response, ('<a class="category-badge" href="{:s}">' '<span class="category-badge__title">{:s}</span></a>').format( published_category.public_extension.extended_object. get_absolute_url(), published_category.public_extension.extended_object.get_title( ), ), html=True, ) # The other categories should not be leaked: # - new_category linked only on the draft person page self.assertNotContains( response, extra_published_category.extended_object.get_title(), html=True) # - not_published category self.assertNotContains( response, not_published_category.extended_object.get_title(), html=True) # - unpublished category self.assertNotContains( response, unpublished_category.extended_object.get_title(), html=True) # The published organization should be on the page in its published version self.assertContains( response, '<div class="organization-glimpse__title">{:s}</div>'.format( published_organization.public_extension.extended_object. get_title()), html=True, ) # The other organizations should not be leaked: # - new organization linked only on the draft person page self.assertNotContains( response, extra_published_organization.extended_object.get_title(), html=True, ) # - not published organization self.assertNotContains( response, not_published_organization.extended_object.get_title(), html=True) # - unpublished organization self.assertNotContains( response, unpublished_organization.extended_object.get_title(), html=True) # Modified draft category and organization should not be leaked self.assertNotContains(response, "modified")
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_templates_course_run_detail_cms_draft_content(self): """ A staff user should see a draft course run including its draft elements with an annotation """ user = UserFactory(is_staff=True, is_superuser=True) self.client.login(username=user.username, password="******") categories = CategoryFactory.create_batch(4) organizations = OrganizationFactory.create_batch(4) course = CourseFactory( page_title="Very interesting course", fill_organizations=organizations, fill_categories=categories, should_publish=True, ) course_run = CourseRunFactory( page_title="first session", page_parent=course.extended_object, resource_link="https://www.example.com/enroll", enrollment_start=datetime(2018, 10, 21, tzinfo=pytz.utc), enrollment_end=datetime(2019, 1, 18, tzinfo=pytz.utc), start=datetime(2018, 12, 10, tzinfo=pytz.utc), end=datetime(2019, 2, 14, tzinfo=pytz.utc), languages=["en", "fr"], ) page = course_run.extended_object # Publish only 2 out of 4 categories and 2 out of 4 organizations categories[0].extended_object.publish("en") categories[1].extended_object.publish("en") organizations[0].extended_object.publish("en") organizations[1].extended_object.publish("en") # The unpublished objects may have been published and unpublished which puts them in a # status different from objects that have never been published. # We want to test both cases. categories[2].extended_object.publish("en") categories[2].extended_object.unpublish("en") organizations[2].extended_object.publish("en") organizations[2].extended_object.unpublish("en") # The page should be visible as draft to the staff user url = page.get_absolute_url() response = self.client.get(url) self.assertEqual(response.status_code, 200) self.assertContains( response, "<title>First session - Very interesting course</title>", html=True, ) self.assertContains( response, '<h1 class="subheader__title">' "Very interesting course<br>first session</h1>", html=True, ) # Draft and public organizations should all be present on the page for organization in organizations: self.assertContains( response, '<div class="organization-glimpse__title">{title:s}</div>'. format(title=organization.extended_object.get_title()), html=True, ) # Draft organizations should be annotated for styling self.assertContains(response, "organization-glimpse--draft", count=2) # The published categories should be present on the page for category in categories[:2]: self.assertContains( response, ('<a class="category-badge" href="{:s}">' '<span class="category-badge__title">{:s}</span></a>').format( category.extended_object.get_absolute_url(), category.extended_object.get_title(), ), html=True, ) # Draft categories should also be present on the page with an annotation for styling for category in categories[-2:]: self.assertContains( response, ('<a class="{element:s} {element:s}--draft" href="{url:s}">' '<span class="category-badge__title">{title:s}</span></a>' ).format( url=category.extended_object.get_absolute_url(), element="category-badge", title=category.extended_object.get_title(), ), html=True, ) # The course run details should be on the page self.assertContains( response, "<strong>Enrollment starts</strong><span>Oct. 21, 2018</span>") self.assertContains( response, "<strong>Enrollment ends</strong><span>Jan. 18, 2019</span>") self.assertContains( response, "<strong>Course starts</strong><span>Dec. 10, 2018</span>") self.assertContains( response, "<strong>Course ends</strong><span>Feb. 14, 2019</span>") self.assertContains( response, "<strong>Languages</strong><span>English and french</span>")
def test_models_category_get_persons_descendants(self): """ Related persons should include the persons linked to the category's descendants, unless specifically deactivated by the "include_descendants" argument. """ category_page = create_page("Subjects", "courses/cms/category_detail.html", "en", published=True) category = CategoryFactory(extended_object=category_page, should_publish=True) persons = PersonFactory.create_batch(2, fill_categories=[category], should_publish=True) child_category_page = create_page( "Literature", "courses/cms/category_detail.html", "en", parent=category_page, published=True, ) child_category = CategoryFactory(extended_object=child_category_page, should_publish=True) persons_child = PersonFactory.create_batch( 2, fill_categories=[child_category], should_publish=True) grand_child_category_page = create_page( "Literature", "courses/cms/category_detail.html", "en", parent=child_category_page, published=True, ) grand_child_category = CategoryFactory( extended_object=grand_child_category_page, should_publish=True) persons_grand_child = PersonFactory.create_batch( 2, fill_categories=[grand_child_category], should_publish=True) # Check that each category gets persons from its descendants # ...unless we pass an argument to deactivate it self.assertEqual(list(category.get_persons()), persons + persons_child + persons_grand_child) self.assertEqual(list(category.get_persons(include_descendants=False)), persons) self.assertEqual(list(child_category.get_persons()), persons_child + persons_grand_child) self.assertEqual( list(child_category.get_persons(include_descendants=False)), persons_child) self.assertEqual( list(grand_child_category.get_persons(include_descendants=False)), persons_grand_child, ) self.assertEqual(list(grand_child_category.get_persons()), persons_grand_child)
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_templates_person_detail_cms_draft_content(self): """ A superuser should see a draft person including its draft elements with an annotation. """ user = UserFactory(is_staff=True, is_superuser=True) self.client.login(username=user.username, password="******") published_category = CategoryFactory(should_publish=True) not_published_category = CategoryFactory() published_organization = OrganizationFactory(should_publish=True) not_published_organization = OrganizationFactory() person = PersonFactory( page_title="My page title", fill_portrait=True, fill_bio=True, fill_maincontent=True, fill_categories=[published_category, not_published_category], fill_organizations=[ published_organization, not_published_organization ], ) # Modify the draft version of the published category title_obj = published_category.extended_object.title_set.get( language="en") title_obj.title = "modified category" title_obj.save() # Modify the draft version of the published organization title_obj = published_category.extended_object.title_set.get( language="en") title_obj.title = "modified organization" title_obj.save() page = person.extended_object # The page should be visible as draft to the superuser url = page.get_absolute_url() response = self.client.get(url) content = htmlmin.minify( response.content.decode("UTF-8"), reduce_empty_attributes=False, remove_optional_attribute_quotes=False, ) self.assertContains(response, "<title>My page title</title>", html=True, status_code=200) title = person.extended_object.get_title() self.assertContains( response, f'<h1 class="subheader__title">{title:s}</h1>', html=True, ) # Main content should be present when not empty self.assertContains(response, "person-detail__maincontent") # The published category should be on the page in its published version self.assertContains( response, ( # pylint: disable=consider-using-f-string '<a class="category-badge" href="{:s}">' '<span class="category-badge__title">{:s}</span></a>').format( published_category.public_extension.extended_object. get_absolute_url(), published_category.public_extension.extended_object. get_title(), ), html=True, ) # The not published category should not be on the page self.assertContains( response, ( # pylint: disable=consider-using-f-string '<a class="category-badge category-badge--draft" href="{:s}">' '<span class="category-badge__title">{:s}</span></a>').format( not_published_category.extended_object.get_absolute_url(), not_published_category.extended_object.get_title(), ), html=True, ) # The published organization should be on the page in its published version self.assertIn( # pylint: disable=consider-using-f-string '<div class="organization-glimpse" property="contributor" ' 'typeof="CollegeOrUniversity"><a href="{:s}" title="{:s}">'.format( published_organization.extended_object.get_absolute_url(), published_organization.extended_object.get_title(), ), content, ) self.assertContains( response, # pylint: disable=consider-using-f-string '<div class="organization-glimpse__title" property="name">{:s}</div>' .format(published_organization.public_extension.extended_object. get_title()), html=True, ) # The not published organization should not be on the page self.assertIn( # pylint: disable=consider-using-f-string '<a href="{:s}" title="{:s}">'.format( not_published_organization.extended_object.get_absolute_url(), not_published_organization.extended_object.get_title(), ), content, ) self.assertContains( response, # pylint: disable=consider-using-f-string '<div class="organization-glimpse__title" property="name">{:s}</div>' .format(not_published_organization.extended_object.get_title()), html=True, ) self.assertNotContains(response, "modified")
def test_templates_course_detail_cms_published_content(self): """ Validate that the important elements are displayed on a published course page """ categories = CategoryFactory.create_batch(4) icons = CategoryFactory.create_batch(4, fill_icon=True) organizations = OrganizationFactory.create_batch(4) course = CourseFactory( page_title="Very interesting course", fill_organizations=organizations, fill_categories=categories, fill_icons=icons, ) page = course.extended_object # Create an ongoing open course run that will be published (created before # publishing the page) now = timezone.now() CourseRunFactory( direct_course=course, start=now - timedelta(hours=1), end=now + timedelta(hours=2), enrollment_end=now + timedelta(hours=1), languages=["en", "fr"], ) # Publish only 2 out of 4 categories, icons and organizations self.assertTrue(categories[0].extended_object.publish("en")) self.assertTrue(categories[1].extended_object.publish("en")) self.assertTrue(icons[0].extended_object.publish("en")) self.assertTrue(icons[1].extended_object.publish("en")) self.assertTrue(organizations[0].extended_object.publish("en")) self.assertTrue(organizations[1].extended_object.publish("en")) # The unpublished objects may have been published and unpublished which puts them in a # status different from objects that have never been published. # We want to test both cases. self.assertTrue(categories[2].extended_object.publish("en")) self.assertTrue(categories[2].extended_object.unpublish("en")) self.assertTrue(icons[2].extended_object.publish("en")) self.assertTrue(icons[2].extended_object.unpublish("en")) self.assertTrue(organizations[2].extended_object.publish("en")) self.assertTrue(organizations[2].extended_object.unpublish("en")) # The page should not be visible before it is published url = page.get_absolute_url() response = self.client.get(url) self.assertEqual(response.status_code, 404) # Publish and ensure content is correct self.assertTrue(page.publish("en")) # Create an unpublished ongoing open course run (created after # publishing the page) CourseRunFactory( direct_course=course, start=now - timedelta(hours=1), end=now + timedelta(hours=2), enrollment_end=now + timedelta(hours=1), languages=["en", "fr"], ) response = self.client.get(url) self.assertEqual(response.status_code, 200) self.assertContains(response, "<title>Very interesting course</title>", html=True) self.assertContains( response, '<h1 class="subheader__title">Very interesting course</h1>', html=True, ) # Only published categories should be present on the page for category in categories[:2]: self.assertContains( response, ('<a class="category-badge" href="{:s}">' '<span class="category-badge__title">{:s}</span></a>').format( category.extended_object.get_absolute_url(), category.extended_object.get_title(), ), html=True, ) for category in categories[-2:]: self.assertNotContains(response, category.extended_object.get_title()) # Only published icons should be present on the page pattern = ( r'<a.*class="category-badge".*href="{link:s}".*>' r'<img src="/media/filer_public_thumbnails/filer_public/.*icon\.jpg.*alt="{title:s}">' r'<span class="category-badge__title">' r".*{title:s}.*</span>") for icon in icons[:2]: self.assertIsNotNone( re.search( pattern.format( link=icon.extended_object.get_absolute_url(), title=icon.extended_object.get_title(), ), str(response.content), )) for icon in icons[-2:]: self.assertNotContains(response, icon.extended_object.get_title()) # Public organizations should be in response content for organization in organizations[:2]: self.assertContains( response, '<div class="organization-glimpse__title">{title:s}</div>'. format(title=organization.extended_object.get_title()), html=True, ) # Draft organizations should not be in response content for organization in organizations[-2:]: self.assertNotContains(response, organization.extended_object.get_title(), html=True) # Only the published course run should be in response content self.assertEqual(CourseRun.objects.count(), 3) self.assertContains(response, "<dd>English and french</dd>", html=True, count=1)
def test_templates_organization_detail_cms_draft_content(self): """ A staff user should see a draft organization including only the related objects that are published. """ user = UserFactory(is_staff=True, is_superuser=True) self.client.login(username=user.username, password="******") published_category = CategoryFactory(should_publish=True) not_published_category = CategoryFactory() organization = OrganizationFactory( page_title="La Sorbonne", fill_categories=[published_category, not_published_category], ) published_course = CourseFactory( fill_organizations=[organization], should_publish=True ) not_published_course = CourseFactory(fill_organizations=[organization]) # Republish courses to take into account adding the organization published_course.extended_object.publish("en") # Modify the draft version of the published category title_obj = published_category.extended_object.title_set.get(language="en") title_obj.title = "modified category" title_obj.save() # Modify the draft version of the published course title_obj = published_course.extended_object.title_set.get(language="en") title_obj.title = "modified course" title_obj.save() page = organization.extended_object # The page should be visible as draft to the staff user url = page.get_absolute_url() response = self.client.get(url) self.assertContains( response, "<title>La Sorbonne</title>", html=True, status_code=200 ) self.assertContains( response, '<h1 class="organization-detail__title">La Sorbonne</h1>', html=True, ) # The published category should be on the page in its published version self.assertContains( response, ( # pylint: disable=consider-using-f-string '<a class="category-tag" href="{:s}">' '<span class="category-tag__title">{:s}</span></a>' ).format( published_category.public_extension.extended_object.get_absolute_url(), published_category.public_extension.extended_object.get_title(), ), html=True, ) # The not published category should be on the page self.assertContains( response, ( # pylint: disable=consider-using-f-string '<a class="category-tag category-tag--draft" href="{:s}">' '<span class="category-tag__title">{:s}</span></a>' ).format( not_published_category.extended_object.get_absolute_url(), not_published_category.extended_object.get_title(), ), html=True, ) # The modified draft category should not be leaked self.assertNotContains(response, "modified category") # The published course should be on the page in its draft version self.assertContains( response, '<p class="course-glimpse__title">modified course</p>', html=True, ) # The not published course should be on the page self.assertContains( response, not_published_course.extended_object.get_title(), )
def test_templates_course_detail_cms_published_content(self): """ Validate that the important elements are displayed on a published course page """ categories = CategoryFactory.create_batch(4) icons = CategoryFactory.create_batch(4, fill_icon=True) organizations = OrganizationFactory.create_batch(4) course = CourseFactory( page_title="Very interesting course", fill_organizations=organizations, fill_categories=categories, fill_icons=icons, ) page = course.extended_object course_run1, _course_run2 = CourseRunFactory.create_batch( 2, page_parent=course.extended_object, languages=["en", "fr"] ) self.assertFalse(course_run1.extended_object.publish("en")) # Publish only 2 out of 4 categories, icons and organizations categories[0].extended_object.publish("en") categories[1].extended_object.publish("en") icons[0].extended_object.publish("en") icons[1].extended_object.publish("en") organizations[0].extended_object.publish("en") organizations[1].extended_object.publish("en") # The unpublished objects may have been published and unpublished which puts them in a # status different from objects that have never been published. # We want to test both cases. categories[2].extended_object.publish("en") categories[2].extended_object.unpublish("en") icons[2].extended_object.publish("en") icons[2].extended_object.unpublish("en") organizations[2].extended_object.publish("en") organizations[2].extended_object.unpublish("en") # The page should not be visible before it is published url = page.get_absolute_url() response = self.client.get(url) self.assertEqual(response.status_code, 404) # Publish and ensure content is correct page.publish("en") # Now we can publish children course runs: publish only 1 of the 2 course_run1.extended_object.parent_page.refresh_from_db() self.assertTrue(course_run1.extended_object.publish("en")) response = self.client.get(url) self.assertEqual(response.status_code, 200) self.assertContains( response, "<title>Very interesting course</title>", html=True ) self.assertContains( response, '<h1 class="course-detail__content__title">Very interesting course</h1>', html=True, ) # Only published categories should be present on the page for category in categories[:2]: self.assertContains( response, ( '<a class="category-plugin-tag" href="{:s}">' '<div class="category-plugin-tag__title">{:s}</div></a>' ).format( category.extended_object.get_absolute_url(), category.extended_object.get_title(), ), html=True, ) for category in categories[-2:]: self.assertNotContains(response, category.extended_object.get_title()) # Only published icons should be present on the page pattern = ( r'<a.*class="category-plugin-tag".*href="{link:s}".*>' r'<figure class="category-plugin-tag__figure">' r'<figcaption.*class="category-plugin-tag__figure__caption".*>' r".*{title:s}.*</figcaption>" r'<img src="/media/filer_public_thumbnails/filer_public/.*icon\.jpg.*alt=""/>' ) for icon in icons[:2]: self.assertIsNotNone( re.search( pattern.format( link=icon.extended_object.get_absolute_url(), title=icon.extended_object.get_title(), ), str(response.content), ) ) for icon in icons[-2:]: self.assertNotContains(response, icon.extended_object.get_title()) # Public organizations should be in response content for organization in organizations[:2]: self.assertContains( response, '<div class="organization-glimpse__title">{title:s}</div>'.format( title=organization.extended_object.get_title() ), html=True, ) # Draft organizations should not be in response content for organization in organizations[-2:]: self.assertNotContains( response, organization.extended_object.get_title(), html=True ) # Only the published course run should be in response content self.assertContains(response, "<dd>English and french</dd>", html=True, count=1)
def test_indexers_courses_get_es_documents_from_models(self, _mock_picture): """ Happy path: the data is retrieved from the models properly formatted """ # Create a course with a page in both english and french published_categories = [ CategoryFactory( # L-0001 fill_icon=True, page_title={"en": "Title L-0001", "fr": "Titre L-0001"}, should_publish=True, ), CategoryFactory( # L-0002 fill_icon=True, page_title={"en": "Title L-0002", "fr": "Titre L-0002"}, should_publish=True, ), ] draft_category = CategoryFactory(fill_icon=True) # L-0003 main_organization = OrganizationFactory( # L-0004 page_title={ "en": "english main organization title", "fr": "titre organisation principale français", }, should_publish=True, ) other_draft_organization = OrganizationFactory( # L-0005 page_title={ "en": "english other organization title", "fr": "titre autre organisation français", } ) other_published_organization = OrganizationFactory( # L-0006 page_title={ "en": "english other organization title", "fr": "titre autre organisation français", }, should_publish=True, ) person1 = PersonFactory( page_title={"en": "Eugène Delacroix", "fr": "Eugène Delacroix"}, should_publish=True, ) person2 = PersonFactory( page_title={"en": "Comte de Saint-Germain", "fr": "Earl of Saint-Germain"}, should_publish=True, ) person_draft = PersonFactory( page_title={"en": "Jules de Polignac", "fr": "Jules de Polignac"} ) course = CourseFactory( duration=[3, WEEK], effort=[2, HOUR, WEEK], fill_categories=published_categories + [draft_category], fill_cover=True, fill_icons=published_categories + [draft_category], fill_organizations=[ main_organization, other_draft_organization, other_published_organization, ], fill_team=[person1, person_draft, person2], page_title={ "en": "an english course title", "fr": "un titre cours français", }, ) CourseRunFactory.create_batch(2, direct_course=course) course.extended_object.publish("en") course.extended_object.publish("fr") course.refresh_from_db() # Add a description in several languages placeholder = course.public_extension.extended_object.placeholders.get( slot="course_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="a propos français ligne 1.", language="fr", **plugin_params) add_plugin(body="a propos français ligne 2.", language="fr", **plugin_params) # The results were properly formatted and passed to the consumer expected_course = { "_id": str(course.public_extension.extended_object_id), "_index": "some_index", "_op_type": "some_action", "_type": "course", "absolute_url": { "en": "/en/an-english-course-title/", "fr": "/fr/un-titre-cours-francais/", }, "categories": ["L-0001", "L-0002"], "categories_names": { "en": ["Title L-0001", "Title L-0002"], "fr": ["Titre L-0001", "Titre L-0002"], }, "complete": { "en": [ "an english course title", "english course title", "course title", "title", ], "fr": [ "un titre cours français", "titre cours français", "cours français", "français", ], }, "course_runs": [ { "start": course_run.public_course_run.start, "end": course_run.public_course_run.end, "enrollment_start": course_run.public_course_run.enrollment_start, "enrollment_end": course_run.public_course_run.enrollment_end, "languages": course_run.public_course_run.languages, } for course_run in course.get_course_runs().order_by("-end") ], "cover_image": { "en": {"info": "picture info"}, "fr": {"info": "picture info"}, }, "description": { "en": "english description line 1. english description line 2.", "fr": "a propos français ligne 1. a propos français ligne 2.", }, "duration": {"en": "3 weeks", "fr": "3 semaines"}, "effort": {"en": "2 hours/week", "fr": "2 heures/semaine"}, "icon": { "en": { "color": published_categories[0].color, "info": "picture info", "title": "Title L-0001", }, "fr": { "color": published_categories[0].color, "info": "picture info", "title": "Titre L-0001", }, }, "is_new": False, "is_listed": True, "organization_highlighted": { "en": "english main organization title", "fr": "titre organisation principale français", }, "organizations": ["L-0004", "L-0006"], "organizations_names": { "en": [ "english main organization title", "english other organization title", ], "fr": [ "titre organisation principale français", "titre autre organisation français", ], }, "persons": [ str(person1.public_extension.extended_object_id), str(person2.public_extension.extended_object_id), ], "persons_names": { "en": ["Eugène Delacroix", "Comte de Saint-Germain"], "fr": ["Eugène Delacroix", "Earl of Saint-Germain"], }, "title": {"fr": "un titre cours français", "en": "an english course title"}, } indexed_courses = list( CoursesIndexer.get_es_documents(index="some_index", action="some_action") ) self.assertEqual(len(indexed_courses), 1) self.assertEqual(indexed_courses[0], expected_course)
def test_indexers_courses_get_es_document_for_course_related_names_course_unpublished( self, ): """ When a course is unpublished in one language, but it has related objects that are still published in this language, should or shouldn't we use this language's content for the related objects when building the course glimpse? This choice is controversial and I had to write these tests to fully understand it. So I propose to commit them to keep this memory. Note: We could argue in the future that a course glimpse should be built with content only in the same language and not with "as few fallback languages as possible, using available content in each language". """ # Create a course with related pages in both english and french but only # published in one language category = CategoryFactory( page_title={ "en": "english category title", "fr": "titre catégorie français", }, should_publish=True, ) organization_title = { "en": "english organization title", "fr": "titre organisation français", } organization = OrganizationFactory( page_title=organization_title, should_publish=True ) person = PersonFactory( page_title={"en": "Brian", "fr": "François"}, should_publish=True ) course = CourseFactory( fill_categories=[category], fill_organizations=[organization], fill_team=[person], page_title={ "en": "an english course title", "fr": "un titre cours français", }, should_publish=True, ) course.extended_object.unpublish("fr") course_document = CoursesIndexer.get_es_document_for_course(course) self.assertEqual( course_document["organizations_names"], { "en": ["english organization title"], "fr": ["titre organisation français"], }, ) self.assertEqual( course_document["organization_highlighted"], organization_title ) self.assertEqual( course_document["categories_names"], {"en": ["english category title"], "fr": ["titre catégorie français"]}, ) self.assertEqual( course_document["persons_names"], {"en": ["Brian"], "fr": ["François"]} )
def create_categories( children=None, color=None, fill_banner=True, fill_description=True, fill_icon=False, fill_logo=True, page_in_navigation=True, page_title=None, page_parent=None, page_reverse_id=None, should_publish=True, ): """ Create the category tree from the SUBJECTS dictionary. Arguments: info (List): definition of the category tree to create in the following format: { "title": "Subject", "children": [ { "title": "Computer science", "children": [ {"title": "Coding"}, {"title": "Security"}, ], }, {"title": "Languages"}, ], } page (cms.models.pagemodel.Page): Instance of a Page below which the category tree is created. Returns: generator[courses.models.Category]: yield only the leaf categories of the created tree. """ category = CategoryFactory( color=color, fill_banner=fill_banner, fill_description=fill_description, fill_icon=fill_icon, fill_logo=fill_logo, page_title=page_title, page_reverse_id=page_reverse_id, page_in_navigation=page_in_navigation, page_parent=page_parent, should_publish=should_publish, ) if children: for child_info in children: kwargs = { "fill_banner": fill_banner, "fill_description": fill_description, "fill_icon": fill_icon, "fill_logo": fill_logo, "page_parent": category.extended_object, "should_publish": should_publish, } kwargs.update(child_info) yield from create_categories(**kwargs) else: # we only return leaf categories (no children) yield category
def execute_query(self, querystring=""): """ Not a test. This method is doing the heavy lifting for the tests in this class: create and fill the index with our courses so we can run our queries and check our facet counts. It also executes the query and returns the result from the API. """ # Create the subject category page. This is necessary to link the subjects we # defined above with the "subjects" filter # As it is the only page we create, we expect it to have the path "0001" CategoryFactory(page_reverse_id="subjects", should_publish=True) # Index these 4 courses in Elasticsearch indices_client = IndicesClient(client=ES_CLIENT) # Delete any existing indexes so we get a clean slate indices_client.delete(index="_all") # Create an index we'll use to test the ES features indices_client.create(index="test_courses") indices_client.close(index="test_courses") indices_client.put_settings(body=ANALYSIS_SETTINGS, index="test_courses") indices_client.open(index="test_courses") # Use the default courses mapping from the Indexer indices_client.put_mapping(body=CoursesIndexer.mapping, doc_type="course", index="test_courses") # Add the sorting script ES_CLIENT.put_script(id="state", body=CoursesIndexer.scripts["state"]) # Actually insert our courses in the index actions = [{ "_id": course["id"], "_index": "test_courses", "_op_type": "create", "_type": "course", "absolute_url": { "en": "url" }, "cover_image": { "en": "image" }, "title": { "en": "title" }, **course, "course_runs": [{ "languages": course_run["languages"], "start": arrow.utcnow().datetime, "end": arrow.utcnow().datetime, "enrollment_start": arrow.utcnow().datetime, "enrollment_end": arrow.utcnow().datetime, } for course_run in course["course_runs"]], } for course in COURSES] bulk(actions=actions, chunk_size=500, client=ES_CLIENT) indices_client.refresh() response = self.client.get(f"/api/v1.0/courses/?{querystring:s}") self.assertEqual(response.status_code, 200) return json.loads(response.content)
def test_templates_course_run_detail_cms_published_content(self): """ Validate that the important elements are displayed on a published course run page """ categories = CategoryFactory.create_batch(4) organizations = OrganizationFactory.create_batch(4) course = CourseFactory( page_title="Very interesting course", fill_organizations=organizations, fill_categories=categories, should_publish=True, ) course_run = CourseRunFactory( page_title="first session", page_parent=course.extended_object, resource_link="https://www.example.com/enroll", enrollment_start=datetime(2018, 10, 21, tzinfo=pytz.utc), enrollment_end=datetime(2019, 1, 18, tzinfo=pytz.utc), start=datetime(2018, 12, 10, tzinfo=pytz.utc), end=datetime(2019, 2, 14, tzinfo=pytz.utc), languages=["en", "fr"], ) page = course_run.extended_object # Publish only 2 out of 4 categories and 2 out of 4 organizations categories[0].extended_object.publish("en") categories[1].extended_object.publish("en") organizations[0].extended_object.publish("en") organizations[1].extended_object.publish("en") # The unpublished objects may have been published and unpublished which puts them in a # status different from objects that have never been published. # We want to test both cases. categories[2].extended_object.publish("en") categories[2].extended_object.unpublish("en") organizations[2].extended_object.publish("en") organizations[2].extended_object.unpublish("en") # The page should not be visible before it is published url = page.get_absolute_url() response = self.client.get(url) self.assertEqual(response.status_code, 404) # Now publish the page and check its content page.publish("en") response = self.client.get(url) self.assertEqual(response.status_code, 200) self.assertContains( response, "<title>First session - Very interesting course</title>", html=True, ) self.assertContains( response, '<h1 class="subheader__title">' "Very interesting course<br>first session</h1>", html=True, ) # Only published categories should be present on the page for category in categories[:2]: self.assertContains( response, ('<a class="category-badge" href="{:s}">' '<span class="category-badge__title">{:s}</span></a>').format( category.extended_object.get_absolute_url(), category.extended_object.get_title(), ), html=True, ) for category in categories[-2:]: self.assertNotContains(response, category.extended_object.get_title()) # Public organizations should be in response content for organization in organizations[:2]: self.assertContains( response, '<div class="organization-glimpse__title">{title:s}</div>'. format(title=organization.extended_object.get_title()), html=True, ) # Draft organizations should not be in response content for organization in organizations[-2:]: self.assertNotContains(response, organization.extended_object.get_title(), html=True) # The course run details should be on the page self.assertContains( response, "<strong>Enrollment starts</strong><span>Oct. 21, 2018</span>") self.assertContains( response, "<strong>Enrollment ends</strong><span>Jan. 18, 2019</span>") self.assertContains( response, "<strong>Course starts</strong><span>Dec. 10, 2018</span>") self.assertContains( response, "<strong>Course ends</strong><span>Feb. 14, 2019</span>") self.assertContains( response, "<strong>Languages</strong><span>English and french</span>")
def test_factories_category_color(self): """The category factory should leave the color field null.""" category = CategoryFactory() self.assertIsNone(category.color)
def test_templates_person_detail_cms_draft_content(self): """ A superuser should see a draft person including its draft elements with an annotation. """ user = UserFactory(is_staff=True, is_superuser=True) self.client.login(username=user.username, password="******") published_category = CategoryFactory(should_publish=True) not_published_category = CategoryFactory() published_organization = OrganizationFactory(should_publish=True) not_published_organization = OrganizationFactory() person = PersonFactory( page_title="My page title", fill_portrait=True, fill_bio=True, fill_maincontent=True, fill_categories=[published_category, not_published_category], fill_organizations=[ published_organization, not_published_organization ], ) # Modify the draft version of the published category title_obj = published_category.extended_object.title_set.get( language="en") title_obj.title = "modified category" title_obj.save() # Modify the draft version of the published organization title_obj = published_category.extended_object.title_set.get( language="en") title_obj.title = "modified organization" title_obj.save() page = person.extended_object # The page should be visible as draft to the superuser url = page.get_absolute_url() response = self.client.get(url) self.assertContains(response, "<title>My page title</title>", html=True, status_code=200) title = person.extended_object.get_title() self.assertContains( response, f'<h1 class="subheader__title">{title:s}</h1>', html=True, ) # Main content should be present when not empty self.assertContains(response, "person-detail__maincontent") # The published category should be on the page in its published version self.assertContains( response, ('<a class="category-badge" href="{:s}">' '<span class="category-badge__title">{:s}</span></a>').format( published_category.public_extension.extended_object. get_absolute_url(), published_category.public_extension.extended_object.get_title( ), ), html=True, ) # The not published category should be on the page, mark as draft self.assertContains( response, ('<a class="category-badge category-badge--draft" ' 'href="{:s}"><span class="category-badge__title">{:s}</span></a>' ).format( not_published_category.extended_object.get_absolute_url(), not_published_category.extended_object.get_title(), ), html=True, ) # The published organization should be on the page in its published version self.assertContains( response, '<div class="organization-glimpse__title">{:s}</div>'.format( published_organization.public_extension.extended_object. get_title()), html=True, ) # The not published organization should be on the page, mark as draft self.assertContains( response, '<div class="organization-glimpse__title">{:s}</div>'.format( not_published_organization.extended_object.get_title()), html=True, ) self.assertIn( ('<a class="organization-glimpse organization-glimpse--draft" ' 'href="{url:s}" title="{title:s}">').format( url=not_published_organization.extended_object. get_absolute_url(), title=not_published_organization.extended_object.get_title(), ), re.sub(" +", " ", str(response.content).replace("\\n", "")), ) self.assertNotContains(response, "modified")
def test_views_filter_definitions(self): """ Returns an object with all the filter definitions, keyed by machine name. """ # Create pages with expected `reverse_id` to ensure the base paths are returned # as part of the filter definitions. create_i18n_page( { "en": "Organizations", "fr": "Organisations" }, reverse_id="organizations", published=True, ) create_i18n_page({ "en": "Persons", "fr": "Personnes" }, reverse_id="persons", published=True) categories_page = create_i18n_page( { "en": "Categories", "fr": "Catégories" }, published=True) CategoryFactory( page_parent=categories_page, page_reverse_id="subjects", page_title={ "en": "Subjects", "fr": "Sujets" }, should_publish=True, ) CategoryFactory( page_parent=categories_page, page_reverse_id="levels", page_title={ "en": "Levels", "fr": "Niveaux" }, should_publish=True, ) response = self.client.get("/api/v1.0/filter-definitions/") self.assertEqual( json.loads(response.content), { "new": { "base_path": None, "human_name": "New courses", "is_autocompletable": False, "is_drilldown": False, "is_searchable": False, "name": "new", "position": 0, }, "availability": { "base_path": None, "human_name": "Availability", "is_autocompletable": False, "is_drilldown": True, "is_searchable": False, "name": "availability", "position": 1, }, "subjects": { "base_path": "00030001", "human_name": "Subjects", "is_autocompletable": True, "is_drilldown": False, "is_searchable": True, "name": "subjects", "position": 2, }, "levels": { "base_path": "00030002", "human_name": "Levels", "is_autocompletable": True, "is_drilldown": False, "is_searchable": True, "name": "levels", "position": 3, }, "organizations": { "base_path": "0001", "human_name": "Organizations", "is_autocompletable": True, "is_drilldown": False, "is_searchable": True, "name": "organizations", "position": 4, }, "languages": { "base_path": None, "human_name": "Languages", "is_autocompletable": False, "is_drilldown": False, "is_searchable": False, "name": "languages", "position": 5, }, "persons": { "base_path": "0002", "human_name": "Persons", "is_autocompletable": True, "is_drilldown": False, "is_searchable": True, "name": "persons", "position": 5, }, }, )
def test_templates_organization_detail_cms_draft_content(self): """ A staff user should see a draft organization including its draft elements with an annotation. """ user = UserFactory(is_staff=True, is_superuser=True) self.client.login(username=user.username, password="******") published_category = CategoryFactory(should_publish=True) not_published_category = CategoryFactory() organization = OrganizationFactory( page_title="La Sorbonne", fill_categories=[published_category, not_published_category], ) published_course = CourseFactory(fill_organizations=[organization], should_publish=True) not_published_course = CourseFactory(fill_organizations=[organization]) # Republish courses to take into account adding the organization published_course.extended_object.publish("en") # Modify the draft version of the published category title_obj = published_category.extended_object.title_set.get( language="en") title_obj.title = "modified category" title_obj.save() # Modify the draft version of the published course title_obj = published_course.extended_object.title_set.get( language="en") title_obj.title = "modified course" title_obj.save() page = organization.extended_object # The page should be visible as draft to the staff user url = page.get_absolute_url() response = self.client.get(url) self.assertContains(response, "<title>La Sorbonne</title>", html=True, status_code=200) self.assertContains( response, '<h1 class="organization-detail__title">La Sorbonne</h1>', html=True, ) # The published category should be on the page in its published version self.assertContains( response, ('<a class="category-plugin-tag" href="{:s}">' '<div class="category-plugin-tag__title">{:s}</div></a>').format( published_category.public_extension.extended_object. get_absolute_url(), published_category.public_extension.extended_object.get_title( ), ), html=True, ) # The not published category should be on the page, mark as draft self.assertContains( response, ('<a class="category-plugin-tag category-plugin-tag--draft" ' 'href="{:s}"><div class="category-plugin-tag__title">{:s}</div></a>' ).format( not_published_category.extended_object.get_absolute_url(), not_published_category.extended_object.get_title(), ), html=True, ) # The modified draft category should not be leaked self.assertNotContains(response, "modified category") # The published course should be on the page in its draft version self.assertContains( response, '<p class="course-glimpse__content__title">modified course</p>', html=True, ) # The not published course should be on the page, mark as draft self.assertContains( response, '<p class="course-glimpse__content__title">{:s}</p>'.format( not_published_course.extended_object.get_title()), html=True, ) self.assertIn( '<a class=" course-glimpse course-glimpse--link course-glimpse--draft " ' 'href="{:s}"'.format( not_published_course.extended_object.get_absolute_url()), re.sub(" +", " ", str(response.content).replace("\\n", "")), )
def test_templates_blogpost_detail_cms_draft_content(self): """ A staff user should see a draft blogpost including only its published linked objects. """ user = UserFactory(is_staff=True, is_superuser=True) self.client.login(username=user.username, password="******") category = CategoryFactory() published_category = CategoryFactory(should_publish=True) author = PersonFactory(page_title={"en": "Comte de Saint-Germain"}, should_publish=True) blogpost = BlogPostFactory( page_title="Preums", fill_cover=True, fill_body=True, fill_categories=[category, published_category], fill_author=[author], ) page = blogpost.extended_object # The page should be visible as draft to the staff user url = page.get_absolute_url() response = self.client.get(url) self.assertContains(response, "<title>Preums - example.com</title>", html=True, status_code=200) self.assertContains(response, '<h1 class="blogpost-detail__title">Preums</h1>', html=True) self.assertContains(response, "Comte de Saint-Germain", html=True) self.assertContains( response, ( # pylint: disable=consider-using-f-string '<a class="category-tag" ' 'href="{:s}"><span class="category-tag__title">{:s}</span></a>' ).format( published_category.extended_object.get_absolute_url(), published_category.extended_object.get_title(), ), html=True, ) self.assertContains( response, ( # pylint: disable=consider-using-f-string '<a class="category-tag category-tag--draft" ' 'href="{:s}"><span class="category-tag__title">{:s}</span></a>' ).format( category.extended_object.get_absolute_url(), category.extended_object.get_title(), ), html=True, ) self.assertContains( response, '<p class="blogpost-detail__pubdate">Not published yet</p>', html=True, )
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_templates_course_detail_cms_draft_content(self): """ A staff user should see a draft course including its draft elements with an annotation """ user = UserFactory(is_staff=True, is_superuser=True) self.client.login(username=user.username, password="******") categories = CategoryFactory.create_batch(4) organizations = OrganizationFactory.create_batch(4) course = CourseFactory( page_title="Very interesting course", fill_organizations=organizations, fill_categories=categories, ) page = course.extended_object now = timezone.now() CourseRunFactory( direct_course=course, start=now - timedelta(hours=1), end=now + timedelta(hours=2), enrollment_end=now + timedelta(hours=1), languages=["en", "fr"], ) # Publish only 2 out of 4 categories and 2 out of 4 organizations self.assertTrue(categories[0].extended_object.publish("en")) self.assertTrue(categories[1].extended_object.publish("en")) self.assertTrue(organizations[0].extended_object.publish("en")) self.assertTrue(organizations[1].extended_object.publish("en")) # The unpublished objects may have been published and unpublished which puts them in a # status different from objects that have never been published. # We want to test both cases. self.assertTrue(categories[2].extended_object.publish("en")) self.assertTrue(categories[2].extended_object.unpublish("en")) self.assertTrue(organizations[2].extended_object.publish("en")) self.assertTrue(organizations[2].extended_object.unpublish("en")) # The page should be visible as draft to the staff user url = page.get_absolute_url() response = self.client.get(url) self.assertEqual(response.status_code, 200) self.assertContains(response, "<title>Very interesting course</title>", html=True) self.assertContains( response, '<h1 class="subheader__title">Very interesting course</h1>', html=True, ) # Draft and public organizations should all be present on the page for organization in organizations: self.assertContains( response, '<div class="organization-glimpse__title">{title:s}</div>'. format(title=organization.extended_object.get_title()), html=True, ) # Draft organizations should be annotated for styling self.assertContains(response, "organization-glimpse--draft", count=2) # The published categories should be present on the page for category in categories[:2]: self.assertContains( response, ('<a class="category-badge" href="{:s}">' '<span class="category-badge__title">{:s}</span></a>').format( category.extended_object.get_absolute_url(), category.extended_object.get_title(), ), html=True, ) # Draft categories should also be present on the page with an annotation for styling for category in categories[-2:]: self.assertContains( response, ('<a class="{element:s} {element:s}--draft" href="{url:s}">' '<span class="category-badge__title">{title:s}</span></a>' ).format( url=category.extended_object.get_absolute_url(), element="category-badge", title=category.extended_object.get_title(), ), html=True, ) # The course run should be in the page self.assertContains(response, "<dd>English and french</dd>", html=True, count=1)