Esempio n. 1
0
    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"]})
Esempio n. 3
0
    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")
Esempio n. 5
0
    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" />',
        )
Esempio n. 6
0
    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">')
Esempio n. 7
0
    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" />')
Esempio n. 8
0
    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" />',
        )
Esempio n. 9
0
    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,
            )
Esempio n. 11
0
    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)))
Esempio n. 14
0
    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"),
            ))
Esempio n. 15
0
    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)
Esempio n. 17
0
    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),
            )
Esempio n. 20
0
    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())
Esempio n. 21
0
    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" />',
        )
Esempio n. 22
0
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)
Esempio n. 23
0
    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)))
Esempio n. 24
0
    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())
Esempio n. 25
0
    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())
Esempio n. 26
0
    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,
            )