def test_templates_program_detail_open_graph_description_program_excerpt_exceeds_max_length( self, ): """ The open graph description should be cut if it exceeds more than 200 caracters """ program = ProgramFactory() page = program.extended_object placeholder_value = ( "Long description that describes the page with a summary. " * 8) # Add a excerpt to a program placeholder = program.extended_object.placeholders.get( slot="program_excerpt") add_plugin( language="en", placeholder=placeholder, plugin_type="PlainTextPlugin", body=placeholder_value, ) page.publish("en") url = program.extended_object.get_absolute_url() response = self.client.get(url) self.assertEqual(response.status_code, 200) cut = placeholder_value[0:200] self.assertContains( response, f'<meta property="og:description" content="{cut}" />', )
def test_cms_wizards_program_submit_form_slug_duplicate(self): """ Trying to create a program 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( "News", "richie/single_column.html", "en", reverse_id=Program.PAGE["reverse_id"], ) # Create an existing page with a known slug ProgramFactory(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 = ProgramWizardForm(data=data, wizard_language="en", wizard_user=user) self.assertFalse(form.is_valid()) self.assertEqual(form.errors, {"slug": ["This slug is already in use"]})
def test_templates_program_detail_meta_description_program_excerpt_exceeds_max_length( self, ): """ The program meta description should show the program_excerpt if no meta_description is specified """ program = ProgramFactory() page = program.extended_object placeholder_value = ( "Long description that describes the page with a summary. " "Long description that describes the page with a summary. " "Long description that describes the page with a summary. ") # Add a excerpt to a program placeholder = program.extended_object.placeholders.get( slot="program_excerpt") add_plugin( language="en", placeholder=placeholder, plugin_type="PlainTextPlugin", body=placeholder_value, ) page.publish("en") url = program.extended_object.get_absolute_url() response = self.client.get(url) self.assertEqual(response.status_code, 200) cut = placeholder_value[0:160] self.assertContains( response, f'<meta name="description" content="{cut}" />', )
def test_cms_plugins_program_fallback_when_published_unpublished(self): """ The program plugin should not render when the program was voluntarily unpublished in the current language. """ # Create a program program = ProgramFactory( page_title={ "en": "public title", "fr": "titre public" }, fill_cover={ "original_filename": "cover.jpg", "default_alt_text": "my cover", }, ) program_page = program.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, ProgramPlugin, "en", **{"page": program_page}) add_plugin(placeholder, ProgramPlugin, "fr", **{"page": program_page}) # Publish only the French version of the program program_page.publish("fr") program_page.publish("en") program_page.unpublish("en") # Check the page content in English page.publish("en") url = page.get_absolute_url(language="en") response = self.client.get(url) self.assertNotContains(response, "glimpse")
def test_templates_program_detail_meta_description_program_excerpt(self): """ The program meta description should show the program_excerpt if no meta_description is specified """ program = ProgramFactory() page = program.extended_object # Add a excerpt to a program placeholder = program.extended_object.placeholders.get( slot="program_excerpt") add_plugin( language="en", placeholder=placeholder, plugin_type="PlainTextPlugin", body="A program excerpt description", ) page.publish("en") url = program.extended_object.get_absolute_url() response = self.client.get(url) self.assertEqual(response.status_code, 200) self.assertContains( response, '<meta name="description" content="A program excerpt description" />', )
def test_templates_program_detail_cms_no_course(self): """ Validate that a program without course doesn't show the course section on a published program page but does on the draft program page """ program = ProgramFactory( page_title="Preums", fill_cover=True, fill_excerpt=True, fill_body=True, ) page = program.extended_object # Publish the program and ensure the content is absent page.publish("en") url = page.get_absolute_url() response = self.client.get(url) self.assertNotContains( response, '<div class="program-detail__courses program-detail__block">') # The content should be visible as draft to the staff user user = UserFactory(is_staff=True, is_superuser=True) self.client.login(username=user.username, password="******") response = self.client.get(url) self.assertContains( response, '<div class="program-detail__courses program-detail__block">')
def test_templates_program_detail_cms_published_content_opengraph(self): """The program logo should be used as opengraph image.""" program = ProgramFactory( fill_cover={ "original_filename": "cover.jpg", "default_alt_text": "my cover", }, should_publish=True, ) url = program.extended_object.get_absolute_url() response = self.client.get(url) self.assertEqual(response.status_code, 200) self.assertContains(response, '<meta property="og:type" content="website" />') self.assertContains( response, f'<meta property="og:url" content="http://example.com{url:s}" />') pattern = ( r'<meta property="og:image" content="http://example.com' r"/media/filer_public_thumbnails/filer_public/.*cover\.jpg__1200x630" ) self.assertIsNotNone(re.search(pattern, str(response.content))) self.assertContains( response, '<meta property="og:image:width" content="1200" />') self.assertContains( response, '<meta property="og:image:height" content="630" />')
def test_templates_program_detail_open_graph_description_program_excerpt( self): """ An opengraph description meta should be present if the program_excerpt placeholder is set. """ program = ProgramFactory() page = program.extended_object # Add a excerpt to a program placeholder = program.extended_object.placeholders.get( slot="program_excerpt") add_plugin( language="en", placeholder=placeholder, plugin_type="PlainTextPlugin", body="A program excerpt description", ) page.publish("en") url = program.extended_object.get_absolute_url() response = self.client.get(url) self.assertEqual(response.status_code, 200) self.assertContains( response, '<meta property="og:description" content="A program excerpt description" />', )
def test_cms_plugins_program_render_on_draft_page(self): """ The program 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 Program program = ProgramFactory(page_title="public title") program_page = program.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, ProgramPlugin, "en", **{"page": program_page}) program_page.publish("en") program_page.unpublish("en") program_page.refresh_from_db() url = "{:s}?edit".format(page.get_absolute_url(language="en")) # The program plugin should still be visible on the draft page response = self.client.get(url) self.assertContains(response, "public title") # Now modify the program to have a draft different from the public version title_obj = program_page.get_title_obj(language="en") title_obj.title = "draft title" title_obj.save() # The draft version of the program plugin should now be visible response = self.client.get(url) self.assertContains(response, "draft title") self.assertNotContains(response, "public title")
def test_templates_program_detail_cms_draft_content(self): """ A staff user should see a draft program including only its public elements. """ user = UserFactory(is_staff=True, is_superuser=True) self.client.login(username=user.username, password="******") courses = CourseFactory.create_batch(4) program = ProgramFactory( page_title="Preums", fill_cover=True, fill_excerpt=True, fill_body=True, fill_courses=courses, ) page = program.extended_object # Publish only 2 out of 4 courses courses[0].extended_object.publish("en") courses[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. courses[2].extended_object.publish("en") courses[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.assertContains(response, "<title>Preums</title>", html=True, status_code=200) self.assertContains(response, '<h1 class="subheader__title">Preums</h1>', html=True) # The published courses should be present on the page for course in courses[:2]: self.assertContains( response, '<p class="course-glimpse__title">{:s}</p>'.format( course.extended_object.get_title()), html=True, ) # Draft courses should not be present on the page for course in courses[-2:]: self.assertNotIn( course.extended_object.get_absolute_url(), re.sub(" +", " ", str(response.content).replace("\\n", "")), ) self.assertNotContains( response, course.extended_object.get_title(), html=True, )
def test_templates_program_detail_cms_published_content(self): """ Validate that the important elements are displayed on a published program page """ courses = CourseFactory.create_batch(4) program = ProgramFactory( page_title="Preums", fill_cover=True, fill_excerpt=True, fill_body=True, fill_courses=courses, ) page = program.extended_object # Publish only 2 out of 4 courses courses[0].extended_object.publish("en") courses[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. courses[2].extended_object.publish("en") courses[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 the program and ensure the content is correct page.publish("en") response = self.client.get(url) self.assertContains( response, "<title>Preums - Program - example.com</title>", html=True, status_code=200, ) self.assertContains(response, '<h1 class="subheader__title">Preums</h1>', html=True) # Only published courses should be present on the page for course in courses[:2]: self.assertContains( response, '<span class="course-glimpse__title-text">{0:s}</span>'.format( # noqa pylint: disable=consider-using-f-string,line-too-long course.extended_object.get_title()), html=True, ) for course in courses[-2:]: self.assertNotContains(response, course.extended_object.get_title())
def test_templates_program_list_cms_content(self): """ Validate that the public website only displays programs that are currently published, while staff users can see draft and unpublished programs. """ page = PageFactory( template="courses/cms/program_list.html", title__language="en", should_publish=True, ) ProgramFactory(page_parent=page, page_title="First program") ProgramFactory( page_parent=page, page_title="Second program", should_publish=True ) # Publish with a publication date in the future future = timezone.now() + timedelta(hours=1) with mock.patch("cms.models.pagemodel.now", return_value=future): ProgramFactory( page_parent=page, page_title="Third program", should_publish=True ) # Anonymous users should only see published programs response = self.client.get(page.get_absolute_url()) self.assertEqual(response.status_code, 200) self.assertNotContains(response, "First") self.assertContains(response, "Second") self.assertNotContains(response, "Third") # Staff users can see draft and unpublished programs user = UserFactory(is_staff=True, is_superuser=True) self.client.login(username=user.username, password="******") response = self.client.get(page.get_absolute_url()) self.assertEqual(response.status_code, 200) for title in ["First", "Second", "Third"]: self.assertContains(response, title)
def test_cms_plugins_program_fallback_when_never_published(self): """ The program plugin should render in the fallback language when the program page has never been published in the current language. """ # Create a program program = ProgramFactory( page_title={ "en": "public program", "fr": "programme publique" }, fill_cover={ "original_filename": "cover.jpg", "default_alt_text": "my cover", }, ) program_page = program.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, ProgramPlugin, "en", **{"page": program_page}) add_plugin(placeholder, ProgramPlugin, "fr", **{"page": program_page}) # Publish only the French version of the program program_page.publish("fr") # Check the page content in English page.publish("en") url = page.get_absolute_url(language="en") response = self.client.get(url) # Program's name should be present as a link to the cms page self.assertContains( response, '<a href="/en/programme-publique/" class="program-glimpse program-glimpse--link" >', status_code=200, ) # The program's full name should be wrapped in a h2 self.assertContains( response, '<p class="program-glimpse__title">programme publique</p>', html=True, ) self.assertNotContains(response, "public program") # Program's cover should be present pattern = ( r'<div class="program-glimpse__media">' r'<img src="/media/filer_public_thumbnails/filer_public/.*cover\.jpg__300x170' r'.*alt=""') self.assertIsNotNone(re.search(pattern, str(response.content)))
def test_cms_plugins_program_fallback_when_never_published(self): """ The program plugin should render in the fallback language when the program page has never been published in the current language. """ # Create a program program = ProgramFactory( page_title={ "en": "public program", "fr": "programme publique" }, fill_cover={ "original_filename": "cover.jpg", "default_alt_text": "my cover", }, ) program_page = program.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, ProgramPlugin, "en", **{"page": program_page}) add_plugin(placeholder, ProgramPlugin, "fr", **{"page": program_page}) # Publish only the French version of the program program_page.publish("fr") # Check the page content in English page.publish("en") url = page.get_absolute_url(language="en") response = self.client.get(url) html = lxml.html.fromstring(response.content) # The program's full name should be wrapped in a link within an h2 title = html.cssselect(".program-glimpse__title")[0] link = title.cssselect(".program-glimpse__link")[0] self.assertEqual(link.text_content().strip(), "programme publique") self.assertNotContains(response, "public program") # Program's cover should be present cover = html.cssselect(".program-glimpse__media")[0] self.assertEqual(cover.get("aria-hidden"), "true") img = cover.cssselect("img")[0] self.assertIsNotNone( re.search( r"/media/filer_public_thumbnails/filer_public/.*cover\.jpg__300x170", img.get("src"), ))
def test_templates_program_detail_meta_description_empty(self): """ The program meta description should not be present if neither the meta_description field on the page, nor the `program_excerpt` placeholder are filled """ program = ProgramFactory() page = program.extended_object page.publish("en") url = program.extended_object.get_absolute_url() response = self.client.get(url) self.assertEqual(response.status_code, 200) self.assertNotContains( response, '<meta name="description"', )
def test_factories_program_body(self): """ ProgramFactory should be able to generate plugins with a realistic body for several languages. """ program = ProgramFactory(page_languages=["fr", "en"], fill_body=True) # Check that the body plugins were created as expected body = program.extended_object.placeholders.get(slot="program_body") self.assertEqual(body.cmsplugin_set.count(), 2) # The body plugins should contain paragraphs for language in ["fr", "en"]: description_plugin = body.cmsplugin_set.get( plugin_type="TextPlugin", language=language) self.assertIn("<p>", description_plugin.djangocms_text_ckeditor_text.body)
def test_templates_program_detail_meta_description_empty_program_excerpt( self): """ The opengraph description meta should be missing if the program_excerpt placeholder is not set. """ program = ProgramFactory() page = program.extended_object page.publish("en") url = program.extended_object.get_absolute_url() response = self.client.get(url) self.assertEqual(response.status_code, 200) self.assertNotContains( response, "og:description", )
def test_factories_program_excerpt(self): """ ProgramFactory should be able to generate plugins with a realistic excerpt for several languages. """ program = ProgramFactory(page_languages=["fr", "en"], fill_excerpt=True) # Check that the excerpt plugins were created as expected excerpt = program.extended_object.placeholders.get( slot="program_excerpt") self.assertEqual(excerpt.cmsplugin_set.count(), 2) # The excerpt plugins should contain paragraphs for language in ["fr", "en"]: excerpt_plugin = excerpt.cmsplugin_set.get( plugin_type="PlainTextPlugin", language=language) self.assertTrue(len(excerpt_plugin.plain_text_plaintext.body) > 0)
def test_factories_program_cover(self): """ ProgramFactory should be able to generate plugins with a realistic cover for several languages. """ program = ProgramFactory(page_languages=["fr", "en"], fill_cover=True) # Check that the cover plugins were created as expected cover = program.extended_object.placeholders.get(slot="program_cover") self.assertEqual(cover.cmsplugin_set.count(), 2) # The cover plugins should point to one of our fixtures images for language in ["fr", "en"]: cover_plugin = cover.cmsplugin_set.get( plugin_type="SimplePicturePlugin", language=language) self.assertIn( "cover", os.path.basename( cover_plugin.djangocms_picture_picture.picture.file.name), )
def test_cms_plugins_program_form_page_choices(self): """ The form to create a program plugin should only list program pages in the select box. """ class ProgramPluginModelForm(forms.ModelForm): """A form for testing the choices in the select box""" class Meta: model = ProgramPluginModel fields = ["page"] program_page = create_i18n_page("my title", published=True) program = ProgramFactory(page_parent=program_page) other_page_title = "other page" create_page(other_page_title, "richie/single_column.html", settings.LANGUAGE_CODE) plugin_form = ProgramPluginModelForm() rendered_form = plugin_form.as_table() self.assertEqual( rendered_form.count(program.extended_object.get_title()), 1) self.assertNotIn(other_page_title, plugin_form.as_table())
def test_templates_program_detail_meta_description(self): """ The program meta description should show meta_description placeholder if defined """ program = ProgramFactory() page = program.extended_object title_obj = page.get_title_obj(language="en") title_obj.meta_description = "A custom description of the program" title_obj.save() page.publish("en") url = program.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 program" />', )
def create_demo_site(): """ Create a simple site tree structure for developpers to work in realistic environment. We create multilingual pages, add organizations under the related page and add plugins to each page. """ site = Site.objects.get(id=1) # Create pages as described in PAGES_INFOS pages_created = recursive_page_creation(site, PAGES_INFO) # Create the footer links footer_static_ph = StaticPlaceholder.objects.get_or_create(code="footer")[0] for footer_placeholder in [footer_static_ph.draft, footer_static_ph.public]: for language, content in FOOTER_CONTENT.items(): # Create the <ul> section to carry the list of links section_plugin = add_plugin( footer_placeholder, plugin_type="SectionPlugin", language=language, template="richie/section/section_list.html", ) # One column per content object for footer_info in content: column_plugin = add_plugin( footer_placeholder, plugin_type="SectionPlugin", language=language, target=section_plugin, template="richie/section/section_list.html", title=footer_info.get("title"), ) for item_info in footer_info.get("items", []): if "internal_link" in item_info: item_info = item_info.copy() item_info["internal_link"] = pages_created[ item_info["internal_link"] ] add_plugin( footer_placeholder, plugin_type="LinkPlugin", language=language, target=column_plugin, **item_info, ) # Create some licences licences = LicenceFactory.create_batch( NB_OBJECTS["licences"], logo__file__from_path=pick_image("licence")() ) # Generate each category tree and return a list of the leaf categories icons = list( create_categories( **ICONS_INFO, fill_banner=pick_image("banner"), fill_logo=pick_image("logo"), page_parent=pages_created["categories"], ) ) levels = list( create_categories( **LEVELS_INFO, fill_banner=pick_image("banner"), fill_logo=pick_image("logo"), page_parent=pages_created["categories"], ) ) subjects = list( create_categories( **SUBJECTS_INFO, fill_banner=pick_image("banner"), fill_logo=pick_image("logo"), page_parent=pages_created["categories"], ) ) partnerships = list( create_categories( **PARTNERSHIPS_INFO, fill_banner=pick_image("banner"), fill_logo=pick_image("logo"), page_parent=pages_created["categories"], ) ) # Create organizations under the `Organizations` page organizations = [] for i in range(NB_OBJECTS["organizations"]): # Randomly assign each organization to a partnership level category organizations.append( OrganizationFactory( page_in_navigation=True, page_languages=["en", "fr"], page_parent=pages_created["organizations"], fill_banner=pick_image("banner"), fill_categories=[random.choice(partnerships)] # nosec if (i % 2 == 0) else [], fill_description=True, fill_logo=pick_image("logo"), should_publish=True, with_permissions=True, ) ) # Create persons under the `persons` page persons = [] persons_for_organization = defaultdict(list) for _ in range(NB_OBJECTS["persons"]): # Randomly assign each person to a set of organizations person_organizations = random.sample( organizations, random.randint(1, NB_OBJECTS["person_organizations"]), # nosec ) person = PersonFactory( page_in_navigation=True, page_languages=["en", "fr"], page_parent=pages_created["persons"], fill_categories=random.sample( subjects, random.randint(1, NB_OBJECTS["person_subjects"]) # nosec ), fill_organizations=person_organizations, fill_portrait=pick_image("portrait"), fill_bio=True, should_publish=True, ) persons.append(person) for organization in person_organizations: persons_for_organization[organization.id].append(person) # Assign each person randomly to an organization so that our course are tagged realistically # If organizations and persons are tagged randomly on courses, each organizations will # in the end be related to most persons... not what we want. # Create courses under the `Course` page with categories and organizations # relations courses = [] for _ in range(NB_OBJECTS["courses"]): video_sample = random.choice(VIDEO_SAMPLE_LINKS) # nosec # Randomly assign each course to a set of organizations course_organizations = random.sample( organizations, NB_OBJECTS["course_organizations"] ) # Only the persons members of these organizations are eligible to be part # of the course team eligible_persons = set( person for o in course_organizations for person in persons_for_organization[o.id] ) course = CourseFactory( page_in_navigation=True, page_languages=["en", "fr"], page_parent=pages_created["courses"], fill_licences=[ ("course_license_content", random.choice(licences)), # nosec ("course_license_participation", random.choice(licences)), # nosec ], fill_team=random.sample( eligible_persons, min( random.randint(1, NB_OBJECTS["course_persons"]), # nosec len(eligible_persons), ), ), fill_teaser=video_sample, fill_cover=pick_image("cover")(video_sample.image), fill_categories=[ *random.sample( subjects, random.randint(1, NB_OBJECTS["course_subjects"]) # nosec ), random.choice(levels), # nosec ], fill_icons=random.sample(icons, get_number_of_icons()), fill_organizations=course_organizations, fill_texts=[ "course_description", "course_format", "course_prerequisites", "course_plan", # "course_license_content", # "course_license_participation", ], should_publish=True, ) course.create_permissions_for_organization(course_organizations[0]) courses.append(course) # Add a random number of course runs to the course nb_course_runs = get_number_of_course_runs() # pick a subset of languages for this course (otherwise all courses will have more or # less all the languages across their course runs!) languages_subset = random.sample( ["de", "en", "es", "fr", "it", "nl"], random.randint(1, 4) # nosec ) for i in range(nb_course_runs): CourseRunFactory( __sequence=i, languages=random.sample( languages_subset, random.randint(1, len(languages_subset)) # nosec ), page_in_navigation=False, page_languages=["en", "fr"], page_parent=course.extended_object, should_publish=True, ) # Create blog posts under the `News` page blogposts = [] for _ in range(NB_OBJECTS["blogposts"]): post = BlogPostFactory.create( page_in_navigation=True, page_languages=["en", "fr"], page_parent=pages_created["blogposts"], fill_cover=pick_image("cover"), fill_excerpt=True, fill_body=True, fill_categories=[ *random.sample(subjects, NB_OBJECTS["blogpost_categories"]), random.choice(levels), # nosec ], fill_author=random.sample(persons, 1), should_publish=True, ) blogposts.append(post) # Create programs under the `Programs` page programs = [] for _ in range(NB_OBJECTS["programs"]): program = ProgramFactory.create( page_in_navigation=True, page_languages=["en", "fr"], page_parent=pages_created["programs"], fill_cover=pick_image("cover"), fill_excerpt=True, fill_body=True, fill_courses=[*random.sample(courses, NB_OBJECTS["programs_courses"])], should_publish=True, ) programs.append(program) # Once everything has been created, use some content to create a homepage placeholder = pages_created["home"].placeholders.get(slot="maincontent") # - Get a banner image banner = image_getter(pick_image("banner")()) # - Get a logo image logo = image_getter(pick_image("logo")()) # - Create the home page in each language for language, content in HOMEPAGE_CONTENT.items(): # Add a banner add_plugin( language=language, placeholder=placeholder, plugin_type="LargeBannerPlugin", title=content["banner_title"], background_image=banner, logo=logo, logo_alt_text="logo", content=content["banner_content"], template=content["banner_template"], ) # Add highlighted courses with a button courses_section = add_plugin( language=language, placeholder=placeholder, plugin_type="SectionPlugin", title=content["courses_title"], template=content["section_template"], ) for course in random.sample(courses, NB_OBJECTS["home_courses"]): add_plugin( language=language, placeholder=placeholder, plugin_type="CoursePlugin", target=courses_section, page=course.extended_object, ) add_plugin( language=language, placeholder=placeholder, plugin_type="LinkPlugin", target=courses_section, name=content["courses_button_title"], template=content["button_template_name"], internal_link=pages_created["courses"], ) # Add highlighted blogposts blogposts_section = add_plugin( language=language, placeholder=placeholder, plugin_type="SectionPlugin", title=content["blogposts_title"], template=content["section_template"], ) for blogpost in random.sample(blogposts, NB_OBJECTS["home_blogposts"]): add_plugin( language=language, placeholder=placeholder, plugin_type="BlogPostPlugin", target=blogposts_section, page=blogpost.extended_object, ) add_plugin( language=language, placeholder=placeholder, plugin_type="LinkPlugin", target=blogposts_section, name=content["blogposts_button_title"], template=content["button_template_name"], internal_link=pages_created["blogposts"], ) # Add highlighted programs programs_section = add_plugin( language=language, placeholder=placeholder, plugin_type="SectionPlugin", title=content["programs_title"], template=content["section_template"], ) for program in random.sample(programs, NB_OBJECTS["home_programs"]): add_plugin( language=language, placeholder=placeholder, plugin_type="ProgramPlugin", target=programs_section, page=program.extended_object, ) add_plugin( language=language, placeholder=placeholder, plugin_type="LinkPlugin", target=programs_section, name=content["programs_button_title"], template=content["button_template_name"], internal_link=pages_created["programs"], ) # Add highlighted organizations organizations_section = add_plugin( language=language, placeholder=placeholder, plugin_type="SectionPlugin", title=content["organizations_title"], template=content["section_template"], ) for organization in random.sample( organizations, NB_OBJECTS["home_organizations"] ): add_plugin( language=language, placeholder=placeholder, plugin_type="OrganizationPlugin", target=organizations_section, page=organization.extended_object, ) add_plugin( language=language, placeholder=placeholder, plugin_type="LinkPlugin", target=organizations_section, name=content["organizations_button_title"], template=content["button_template_name"], internal_link=pages_created["organizations"], ) # Add highlighted subjects subjects_section = add_plugin( language=language, placeholder=placeholder, plugin_type="SectionPlugin", title=content["subjects_title"], template=content["section_template"], ) for subject in random.sample(subjects, NB_OBJECTS["home_subjects"]): add_plugin( language=language, placeholder=placeholder, plugin_type="CategoryPlugin", target=subjects_section, page=subject.extended_object, ) add_plugin( language=language, placeholder=placeholder, plugin_type="LinkPlugin", target=subjects_section, name=content["subjects_button_title"], template=content["button_template_name"], internal_link=pages_created["categories"], ) # Add highlighted persons persons_section = add_plugin( language=language, placeholder=placeholder, plugin_type="SectionPlugin", title=content["persons_title"], template=content["section_template"], ) for person in random.sample(persons, NB_OBJECTS["home_persons"]): add_plugin( language=language, placeholder=placeholder, plugin_type="PersonPlugin", target=persons_section, page=person.extended_object, ) add_plugin( language=language, placeholder=placeholder, plugin_type="LinkPlugin", target=persons_section, name=content["persons_button_title"], template=content["button_template_name"], internal_link=pages_created["persons"], ) # Once content has been added we must publish again homepage pages_created["home"].publish(language) # Fill the single column sample page placeholder = pages_created["annex__about"].placeholders.get(slot="maincontent") # - Get a banner image banner = image_getter(pick_image("banner")()) # - Get a logo image logo = image_getter(pick_image("logo")()) # - Get a video video_sample = random.choice(VIDEO_SAMPLE_LINKS) # nosec # - Create sample page in each language for language, content in SINGLECOLUMN_CONTENT.items(): # Add a banner add_plugin( language=language, placeholder=placeholder, plugin_type="LargeBannerPlugin", title=content["banner_title"], background_image=banner, content=content["banner_content"], template=content["banner_template"], ) # HTML paragraphs create_text_plugin( pages_created["annex__about"], placeholder, nb_paragraphs=random.randint(3, 4), # nosec languages=[language], plugin_type="TextPlugin", ) # A large video sample add_plugin( language=language, placeholder=placeholder, plugin_type="VideoPlayerPlugin", label=video_sample.label, embed_link=video_sample.url, template="full-width", ) # Section with some various plugins sample_section = add_plugin( language=language, placeholder=placeholder, plugin_type="SectionPlugin", title=content["section_sample_title"], template=content["section_sample_template"], ) add_plugin( language=language, placeholder=placeholder, plugin_type="OrganizationPlugin", target=sample_section, page=random.choice(organizations).extended_object, # nosec ) add_plugin( language=language, placeholder=placeholder, plugin_type="CoursePlugin", target=sample_section, page=random.choice(courses).extended_object, # nosec ) add_plugin( language=language, placeholder=placeholder, plugin_type="OrganizationPlugin", target=sample_section, page=random.choice(organizations).extended_object, # nosec ) add_plugin( language=language, placeholder=placeholder, plugin_type="BlogPostPlugin", target=sample_section, page=random.choice(blogposts).extended_object, # nosec ) add_plugin( language=language, placeholder=placeholder, plugin_type="LinkPlugin", target=sample_section, name=content["section_sample_button_title"], template=content["button_template_name"], internal_link=pages_created["home"], ) # Add a licence add_plugin( language=language, placeholder=placeholder, plugin_type="LicencePlugin", licence=random.choice(licences), # nosec ) # Add a simple picture entry add_plugin( language=language, placeholder=placeholder, plugin_type="SimplePicturePlugin", picture=logo, ) # Add a plain text text = factory.Faker( "text", max_nb_chars=random.randint(150, 250), locale=language # nosec ).generate({}) add_plugin( language=language, placeholder=placeholder, plugin_type="PlainTextPlugin", body=text, ) # Once content has been added we must publish again the about page pages_created["annex__about"].publish(language) # Create a sitemap page placeholder = pages_created["annex__sitemap"].placeholders.get(slot="maincontent") for language in pages_created["annex__sitemap"].get_languages(): parent_instance = add_plugin( language=language, placeholder=placeholder, plugin_type="HTMLSitemapPlugin" ) for name, params in SITEMAP_PAGE_PARAMS.items(): add_plugin( language=language, placeholder=placeholder, plugin_type="HTMLSitemapPagePlugin", target=parent_instance, root_page=pages_created[name], **params, ) # Once content has been added we must publish again the sitemap pages_created["annex__sitemap"].publish(language)
def test_cms_plugins_program_render_on_public_page(self): """ The program plugin should render as expected on a public page. """ # Create an program program = ProgramFactory( page_title={ "en": "public title", "fr": "titre public" }, fill_cover={ "original_filename": "cover.jpg", "default_alt_text": "my cover", }, ) program_page = program.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, ProgramPlugin, "en", **{"page": program_page}) add_plugin(placeholder, ProgramPlugin, "fr", **{"page": program_page}) program_page.publish("en") program_page.publish("fr") program.refresh_from_db() page.publish("en") page.publish("fr") # Check the page content in English url = page.get_absolute_url(language="en") # The program plugin should not be visible on the public page before it is published program_page.unpublish("en") response = self.client.get(url) self.assertNotContains(response, "public title") # # Republish the plugin program_page.publish("en") # Now modify the program to have a draft different from the public version title_obj = program_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) # The program's name should be present as a link to the cms page self.assertContains( response, '<a href="/en/public-title/" class="program-glimpse program-glimpse--link', status_code=200, ) # The program's title should be wrapped in a p self.assertContains( response, '<p class="program-glimpse__title">{:s}</p>'.format( program.public_extension.extended_object.get_title()), html=True, ) self.assertNotContains(response, "draft title") # Program's cover should be present pattern = ( r'<div class="program-glimpse__media">' r'<img src="/media/filer_public_thumbnails/filer_public/.*cover\.jpg__300x170' 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 href="/fr/titre-public/" class="program-glimpse program-glimpse--link', status_code=200, ) # pylint: disable=no-member pattern = ( r'<div class="program-glimpse__media">' r'<img src="/media/filer_public_thumbnails/filer_public/.*cover\.jpg__300x170' r'.*alt=""') self.assertIsNotNone(re.search(pattern, str(response.content)))
def test_templates_program_detail_cms_draft_content(self): """ A staff user should see a draft program including draft elements. """ user = UserFactory(is_staff=True, is_superuser=True) self.client.login(username=user.username, password="******") courses = CourseFactory.create_batch(4) program = ProgramFactory( page_title="Preums", fill_cover=True, fill_excerpt=True, fill_body=True, fill_courses=courses, ) page = program.extended_object # Publish only 2 out of 4 courses courses[0].extended_object.publish("en") courses[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. courses[3].extended_object.publish("en") courses[3].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.assertContains( response, "<title>Preums - Program - example.com</title>", html=True, status_code=200, ) self.assertContains(response, '<h1 class="subheader__title">Preums</h1>', html=True) # Draft and published courses should be present on the page for course in courses[:2]: self.assertContains( response, '<a class="course-glimpse__link" ' f'href="{course.extended_object.get_absolute_url():s}"', ) self.assertContains( response, '<span class="course-glimpse__title-text">{0:s}</span>'.format( # noqa pylint: disable=consider-using-f-string,line-too-long course.extended_object.get_title()), html=True, ) self.assertContains( response, '<div class="course-glimpse course-glimpse--draft">' '<div aria-hidden="true" class="course-glimpse__media">' f'<a tabindex="-1" href="{courses[2].extended_object.get_absolute_url():s}"', ) self.assertContains( response, '<span class="course-glimpse__title-text">{0:s}</span>'.format( # noqa pylint: disable=consider-using-f-string,line-too-long courses[2].extended_object.get_title()), html=True, ) # The unpublished course should not be present on the page self.assertNotContains(response, courses[3].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 # 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"], ) program_published, program_unpublished = ProgramFactory.create_batch( 2, fill_courses=[course], should_publish=True) program_unpublished.extended_object.unpublish("en") # 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, f'<div class="subheader__code">Ref. {course.code:s}</div>', 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) # Only the published program should be in response content self.assertContains(response, "course-detail__programs") self.assertContains(response, "This course is part of a program") self.assertContains(response, program_published.extended_object.get_title(), html=True, count=1) self.assertNotContains(response, program_unpublished.extended_object.get_title())
def test_templates_course_detail_cms_draft_content(self): """ A staff user should see a draft course including only the related objects that are published. """ user = UserFactory(is_staff=True, is_superuser=True) self.client.login(username=user.username, password="******") categories = CategoryFactory.create_batch(4) organizations = OrganizationFactory.create_batch(2) published_organizations = OrganizationFactory.create_batch(2) course = CourseFactory( page_title="Very interesting course", fill_organizations=organizations + published_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"], ) program_published, program_unpublished = ProgramFactory.create_batch( 2, fill_courses=[course], should_publish=True) program_unpublished.extended_object.unpublish("en") # 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( published_organizations[0].extended_object.publish("en")) self.assertTrue( published_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[0].extended_object.publish("en")) self.assertTrue(organizations[0].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, ) self.assertContains( response, f'<div class="subheader__code">Ref. {course.code:s}</div>', html=True, ) # only public organizations should be present on the page for organization in organizations: self.assertNotContains(response, organization.extended_object.get_title()) for organization in published_organizations: self.assertContains( response, '<div class="organization-glimpse__title">{title:s}</div>'. format(title=organization.extended_object.get_title()), html=True, ) # 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 not be present on the page for category in categories[-2:]: self.assertNotContains( response, 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) # Both programs should be in response content self.assertContains(response, "course-detail__programs") self.assertContains(response, "This course is part of programs") self.assertContains(response, program_published.extended_object.get_title(), html=True, count=1) self.assertContains( response, program_unpublished.extended_object.get_title(), html=True, count=1, )
def test_templates_program_detail_cms_draft_content(self): """ A staff user should see a draft program including its draft elements with an annotation. """ user = UserFactory(is_staff=True, is_superuser=True) self.client.login(username=user.username, password="******") courses = CourseFactory.create_batch(4) program = ProgramFactory( page_title="Preums", fill_cover=True, fill_excerpt=True, fill_body=True, fill_courses=courses, ) page = program.extended_object # Publish only 2 out of 4 courses courses[0].extended_object.publish("en") courses[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. courses[2].extended_object.publish("en") courses[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.assertContains( response, "<title>Preums</title>", html=True, status_code=200 ) self.assertContains( response, '<h1 class="program-detail__title">Preums</h1>', html=True ) # The published courses should be present on the page for course in courses[:2]: self.assertContains( response, '<p class="course-glimpse__content__title">{:s}</p>'.format( course.extended_object.get_title() ), html=True, ) # Draft courses should also be present on the page with an annotation for styling for course in courses[-2:]: self.assertContains( response, '<a href="{link:s}" class="{name:s} {name:s}--link {name:s}--draft">'.format( name="course-glimpse", link=course.extended_object.get_absolute_url(), ), ) self.assertContains( response, '<p class="course-glimpse__content__title">{title:s}</p>'.format( title=course.extended_object.get_title() ), html=True, )