示例#1
0
    def test_indexers_courses_get_es_document_no_image_icon_picture(self):
        """
        ES document is created without errors when a icon image for the course is
        actually a Picture instance without an image on it.
        """
        # Create the example course to index and get hold of its course_icons placeholder
        course = CourseFactory(should_publish=True)
        course_icons_placeholder = course.extended_object.placeholders.filter(
            slot="course_icons").first()
        # Create a category and add it to the course on the icons placeholder
        category = CategoryFactory(should_publish=True, color="#654321")
        add_plugin(course_icons_placeholder, CategoryPlugin, "en",
                   **{"page": category.extended_object})
        course.extended_object.publish("en")
        # Make sure we associate an image-less picture with the category through
        # the icon placeholder
        category_icon_placeholder = category.extended_object.placeholders.filter(
            slot="icon").first()
        add_plugin(category_icon_placeholder,
                   SimplePicturePlugin,
                   "en",
                   picture=None)
        category.extended_object.publish("en")

        indexed_courses = list(
            CoursesIndexer.get_es_documents(index="some_index",
                                            action="some_action"))

        self.assertEqual(len(indexed_courses), 1)
        self.assertEqual(
            indexed_courses[0]["_id"],
            str(course.extended_object.get_public_object().id),
        )
        self.assertEqual(
            indexed_courses[0]["icon"],
            {
                "en": {
                    "color": "#654321",
                    "title": category.extended_object.get_title()
                }
            },
        )
示例#2
0
    def test_cms_plugins_organizations_by_category_form_page_choices(self):
        """
        The form to create an organizations by category plugin should only list category pages
        in the select box. There shouldn't be any duplicate because of published status.
        """
        class OrganizationsByCategoryPluginModelForm(forms.ModelForm):
            """A form for testing the choices in the select box"""
            class Meta:
                model = OrganizationsByCategoryPluginModel
                fields = ["page"]

        category = CategoryFactory(should_publish=True)
        PageFactory(title__title="other page", should_publish=True)

        plugin_form = OrganizationsByCategoryPluginModelForm()
        rendered_form = plugin_form.as_table()

        self.assertEqual(
            rendered_form.count(category.extended_object.get_title()), 1)
        self.assertNotIn("other", plugin_form.as_table())
示例#3
0
    def test_models_category_get_children_categories(self):
        """
        It should be possible to retrieve the list of direct children page which
        are Category extensions and not any other type.
        """
        empty_category = CategoryFactory(should_publish=True)

        parent_category = CategoryFactory(should_publish=True)
        child_categories = CategoryFactory.create_batch(
            2, page_parent=parent_category.extended_object, should_publish=True
        )

        with self.assertNumQueries(2):
            self.assertFalse(empty_category.get_children_categories().exists())

        with self.assertNumQueries(2):
            self.assertEqual(
                set(parent_category.get_children_categories()), set(child_categories)
            )
示例#4
0
    def test_template_category_detail_meta_description(self):
        """
        The category meta description should show meta_description placeholder if defined
        """
        category = CategoryFactory()
        page = category.extended_object

        title_obj = page.get_title_obj(language="en")
        title_obj.meta_description = "A custom description of the category"
        title_obj.save()

        page.publish("en")

        url = category.extended_object.get_absolute_url()
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)

        self.assertContains(
            response,
            '<meta name="description" content="A custom description of the category" />',
        )
示例#5
0
    def test_indexers_courses_related_objects_consistency(self):
        """
        The organization and category ids in the Elasticsearch course document should be
        the same as the ids with which the corresponding organization and category objects
        are indexed.
        """
        # Create a course with a page in both english and french
        organization = OrganizationFactory(should_publish=True)
        category = CategoryFactory(should_publish=True)
        course = CourseFactory(
            fill_organizations=[organization],
            fill_categories=[category],
            should_publish=True,
        )
        CourseRunFactory(page_parent=course.extended_object, should_publish=True)

        course_document = list(
            CoursesIndexer.get_es_documents(index="some_index", action="some_action")
        )[0]
        self.assertEqual(
            course_document["organizations"],
            [
                next(
                    OrganizationsIndexer.get_es_documents(
                        index="some_index", action="some_action"
                    )
                )["_id"]
            ],
        )
        self.assertEqual(
            course_document["categories"],
            [
                next(
                    CategoriesIndexer.get_es_documents(
                        index="some_index", action="some_action"
                    )
                )["_id"]
            ],
        )
    def test_cms_wizards_category_submit_form_from_category_page(self):
        """
        Submitting a valid CategoryWizardForm from a category should create a sub category of this
        category and its related page.
        """
        # A parent page should pre-exist
        create_page(
            "Categories",
            "richie/single_column.html",
            "en",
            reverse_id=Category.PAGE["reverse_id"],
        )
        # Create a category when visiting an existing category
        parent_category = CategoryFactory()

        # Create a user with just the required permissions
        user = UserFactory(
            is_staff=True,
            permissions=["courses.add_category", "cms.add_page", "cms.change_page"],
        )

        # We can submit a form with just the title set
        form = CategoryWizardForm(
            data={"title": "My title"},
            wizard_language="en",
            wizard_user=user,
            wizard_page=parent_category.extended_object,
        )
        self.assertTrue(form.is_valid())
        page = form.save()

        # Related page should have been created as draft
        Page.objects.drafts().get(id=page.id)
        Category.objects.get(id=page.category.id, extended_object=page)
        self.assertEqual(page.get_parent_page(), parent_category.extended_object)

        self.assertEqual(page.get_title(), "My title")
        # The slug should have been automatically set
        self.assertEqual(page.get_slug(), "my-title")
    def test_cms_plugins_category_render_on_draft_page(self):
        """
        The category plugin should render as expected on a draft page.
        """
        staff = UserFactory(is_staff=True, is_superuser=True)
        self.client.login(username=staff.username, password="******")

        # Create a Category
        category = CategoryFactory(page_title="public title")
        category_page = category.extended_object

        # Create a page to add the plugin to
        page = create_i18n_page("A page")
        placeholder = page.placeholders.get(slot="maincontent")
        add_plugin(placeholder, CategoryPlugin, "en", **{"page": category_page})

        category_page.publish("en")
        category_page.unpublish("en")

        page_url = page.get_absolute_url(language="en")
        url = f"{page_url:s}?edit"

        # The unpublished category plugin should not be visible on the draft page
        response = self.client.get(url)
        self.assertNotContains(response, "public title")

        # Now publish the category and modify it to have a draft different from the
        # public version
        category_page.publish("en")
        title_obj = category_page.get_title_obj(language="en")
        title_obj.title = "draft title"
        title_obj.save()

        # The draft version of the category plugin should not be visible
        response = self.client.get(url)
        self.assertNotContains(response, "draft title")
        self.assertContains(response, "public title")
    def test_cms_wizards_category_submit_form_slug_duplicate(self):
        """
        Trying to create a category with a slug that would lead to a duplicate path should
        raise a validation error.
        """
        # A parent page should pre-exist
        parent_page = create_page(
            "Categories",
            "richie/single_column.html",
            "en",
            reverse_id=Category.PAGE["reverse_id"],
        )
        # Create an existing page with a known slug
        CategoryFactory(page_parent=parent_page, page_title="My title")

        # Submit a title that will lead to the same slug
        data = {"title": "my title"}
        user = UserFactory(is_staff=True, is_superuser=True)
        form = CategoryWizardForm(
            data=data, wizard_language="en", wizard_user=user, wizard_page=parent_page
        )

        self.assertFalse(form.is_valid())
        self.assertEqual(form.errors, {"slug": ["This slug is already in use"]})
    def test_templates_organization_detail_cms_published_content(self):
        """
        Validate that the important elements are displayed on a published organization page
        """
        # Categories
        published_category = CategoryFactory(should_publish=True)
        extra_published_category = CategoryFactory(should_publish=True)
        unpublished_category = CategoryFactory(should_publish=True)
        unpublished_category.extended_object.unpublish("en")
        not_published_category = CategoryFactory()

        # Organizations
        organization = OrganizationFactory(
            page_title="La Sorbonne",
            fill_categories=[
                published_category,
                not_published_category,
                unpublished_category,
            ],
        )

        # Courses
        published_course = CourseFactory(fill_organizations=[organization],
                                         should_publish=True)
        extra_published_course = CourseFactory(should_publish=True)
        unpublished_course = CourseFactory(fill_organizations=[organization])
        not_published_course = CourseFactory(fill_organizations=[organization])

        # Republish courses to take into account adding the organization
        published_course.extended_object.publish("en")
        unpublished_course.extended_object.publish("en")
        unpublished_course.extended_object.unpublish("en")

        page = organization.extended_object

        # The page should not be visible before it is published
        url = page.get_absolute_url()
        response = self.client.get(url)
        self.assertEqual(response.status_code, 404)

        # Publish the organization
        page.publish("en")

        # Modify the draft version of the published category
        title_obj = published_category.extended_object.title_set.get(
            language="en")
        title_obj.title = "modified category"
        title_obj.save()

        # Modify the draft version of the published course
        title_obj = published_course.extended_object.title_set.get(
            language="en")
        title_obj.title = "modified course"
        title_obj.save()

        # Add a new category to the draft organization page but don't publish the modification
        add_plugin(
            page.placeholders.get(slot="categories"),
            CategoryPlugin,
            "en",
            page=extra_published_category.extended_object,
        )

        # Add a new organization to the draft course page but don't publish the modification
        add_plugin(
            extra_published_course.extended_object.placeholders.get(
                slot="course_organizations"),
            OrganizationPlugin,
            "en",
            page=page,
        )

        # Ensure the published page content is correct
        response = self.client.get(url)
        self.assertContains(response,
                            "<title>La Sorbonne</title>",
                            html=True,
                            status_code=200)
        self.assertContains(
            response,
            '<h1 class="organization-detail__title">La Sorbonne</h1>',
            html=True,
        )

        # The published category should be on the page in its published version
        self.assertContains(
            response,
            ('<a class="category-plugin-tag" href="{:s}">'
             '<div class="category-plugin-tag__title">{:s}</div></a>').format(
                 published_category.public_extension.extended_object.
                 get_absolute_url(),
                 published_category.public_extension.extended_object.get_title(
                 ),
             ),
            html=True,
        )
        # The other categories should not be leaked:
        # - new_category linked only on the draft organization page
        self.assertNotContains(
            response, extra_published_category.extended_object.get_title())
        # - not published category
        self.assertNotContains(
            response, not_published_category.extended_object.get_title())
        # - unpublished category
        self.assertNotContains(
            response, unpublished_category.extended_object.get_title())

        # The published courses should be on the page in its published version
        self.assertContains(
            response,
            '<p class="course-glimpse__content__title">{:s}</p>'.format(
                published_course.public_extension.extended_object.get_title()),
            html=True,
        )
        # The other courses should not be leaked:
        # - new course linked only on the draft organization page
        self.assertNotContains(
            response, extra_published_course.extended_object.get_title())
        # - not published course
        self.assertNotContains(
            response, not_published_course.extended_object.get_title())
        # - unpublished course
        self.assertNotContains(response,
                               unpublished_course.extended_object.get_title())

        # Modified draft category and course should not be leaked
        self.assertNotContains(response, "modified")
示例#10
0
    def test_templates_person_detail_cms_published_content(self):
        """
        Validate that the important elements are displayed on a published person page
        """
        # Categories
        published_category = CategoryFactory(should_publish=True)
        unpublished_category = CategoryFactory(should_publish=True)
        unpublished_category.extended_object.unpublish("en")
        extra_published_category = CategoryFactory(should_publish=True)
        not_published_category = CategoryFactory()

        # Modify the draft version of the published category
        title_obj = published_category.extended_object.title_set.get(
            language="en")
        title_obj.title = "modified title"
        title_obj.save()

        # Organizations
        published_organization = OrganizationFactory(should_publish=True)
        unpublished_organization = OrganizationFactory(should_publish=True)
        unpublished_organization.extended_object.unpublish("en")
        extra_published_organization = OrganizationFactory(should_publish=True)
        not_published_organization = OrganizationFactory()

        # Modify the draft version of the published organization
        title_obj = published_organization.extended_object.title_set.get(
            language="en")
        title_obj.title = "modified title"
        title_obj.save()

        person = PersonFactory(
            page_title="My page title",
            fill_portrait=True,
            fill_bio=True,
            fill_categories=[
                published_category,
                not_published_category,
                unpublished_category,
            ],
            fill_organizations=[
                published_organization,
                not_published_organization,
                unpublished_organization,
            ],
        )
        page = person.extended_object

        # The page should not be visible before it is published
        url = page.get_absolute_url()
        response = self.client.get(url)
        self.assertEqual(response.status_code, 404)

        # Publish the person
        page.publish("en")

        # Add a new category to the draft person page but don't publish the modification
        add_plugin(
            page.placeholders.get(slot="categories"),
            CategoryPlugin,
            "en",
            page=extra_published_category.extended_object,
        )

        # Add a new organization to the draft person page but don't publish the modification
        add_plugin(
            page.placeholders.get(slot="organizations"),
            OrganizationPlugin,
            "en",
            page=extra_published_organization.extended_object,
        )

        # Ensure the published page content is correct
        response = self.client.get(url)
        self.assertContains(response,
                            "<title>My page title</title>",
                            html=True,
                            status_code=200)
        self.assertContains(
            response,
            '<h1 class="subheader__title">{:s}</h1>'.format(
                person.extended_object.get_title()),
            html=True,
        )
        # The published category should be on the page in its published version
        self.assertContains(
            response,
            ('<a class="category-badge" href="{:s}">'
             '<span class="category-badge__title">{:s}</span></a>').format(
                 published_category.public_extension.extended_object.
                 get_absolute_url(),
                 published_category.public_extension.extended_object.get_title(
                 ),
             ),
            html=True,
        )
        # The other categories should not be leaked:
        # - new_category linked only on the draft person page
        self.assertNotContains(
            response,
            extra_published_category.extended_object.get_title(),
            html=True)
        # - not_published category
        self.assertNotContains(
            response,
            not_published_category.extended_object.get_title(),
            html=True)
        # - unpublished category
        self.assertNotContains(
            response,
            unpublished_category.extended_object.get_title(),
            html=True)

        # The published organization should be on the page in its published version
        self.assertContains(
            response,
            '<div class="organization-glimpse__title">{:s}</div>'.format(
                published_organization.public_extension.extended_object.
                get_title()),
            html=True,
        )

        # The other organizations should not be leaked:
        # - new organization linked only on the draft person page
        self.assertNotContains(
            response,
            extra_published_organization.extended_object.get_title(),
            html=True,
        )
        # - not published organization
        self.assertNotContains(
            response,
            not_published_organization.extended_object.get_title(),
            html=True)
        # - unpublished organization
        self.assertNotContains(
            response,
            unpublished_organization.extended_object.get_title(),
            html=True)

        # Modified draft category and organization should not be leaked
        self.assertNotContains(response, "modified")
    def test_cms_plugins_category_render_on_public_page(self):
        """
        The category plugin should render as expected on a public page.
        """
        # Create a Category
        category = CategoryFactory(
            page_title={"en": "public title", "fr": "titre publique"},
            fill_logo={"original_filename": "logo.jpg", "default_alt_text": "my logo"},
        )
        category_page = category.extended_object

        # Create a page to add the plugin to
        page = create_i18n_page({"en": "A page", "fr": "Une page"})
        placeholder = page.placeholders.get(slot="maincontent")
        add_plugin(placeholder, CategoryPlugin, "en", **{"page": category_page})
        add_plugin(placeholder, CategoryPlugin, "fr", **{"page": category_page})

        category_page.publish("en")
        category_page.publish("fr")
        category.refresh_from_db()

        page.publish("en")
        page.publish("fr")

        url = page.get_absolute_url(language="en")

        # The category plugin should not be visible on the public page before it is published
        category_page.unpublish("en")
        response = self.client.get(url)
        self.assertNotContains(response, "public title")

        # Republish the plugin
        category_page.publish("en")

        # Now modify the category to have a draft different from the public version
        title_obj = category_page.get_title_obj(language="en")
        title_obj.title = "draft title"
        title_obj.save()

        # Publishing the page again should make the plugin public
        page.publish("en")

        # Check the page content in English
        response = self.client.get(url)

        # Category's title should be present as a link to the cms page
        # And CMS page title should be in title attribute of the link
        self.assertContains(
            response,
            '<a class="category-glimpse" href="/en/public-title/"',
            status_code=200,
        )
        # The category's title should be wrapped in a div
        self.assertContains(
            response,
            '<h2 class="category-glimpse__title">{:s}</h2>'.format(
                category.public_extension.extended_object.get_title()
            ),
            html=True,
        )
        self.assertNotContains(response, "draft title")

        # Category's logo should be present
        pattern = (
            r'<div class="category-glimpse__logo">'
            r'<img src="/media/filer_public_thumbnails/filer_public/.*logo\.jpg__200x200'
            r'.*alt=""'
        )
        self.assertIsNotNone(re.search(pattern, str(response.content)))

        # Same checks in French
        url = page.get_absolute_url(language="fr")
        response = self.client.get(url)
        self.assertContains(
            response,
            '<a class="category-glimpse" href="/fr/titre-publique/"',
            status_code=200,
        )
        pattern = (
            r'<div class="category-glimpse__logo">'
            r'<img src="/media/filer_public_thumbnails/filer_public/.*logo\.jpg__200x200'
            r'.*alt=""'
        )
        self.assertIsNotNone(re.search(pattern, str(response.content)))
示例#12
0
    def test_templates_course_run_detail_cms_draft_content(self):
        """
        A staff user should see a draft course run including its draft elements with
        an annotation
        """
        user = UserFactory(is_staff=True, is_superuser=True)
        self.client.login(username=user.username, password="******")

        categories = CategoryFactory.create_batch(4)
        organizations = OrganizationFactory.create_batch(4)

        course = CourseFactory(
            page_title="Very interesting course",
            fill_organizations=organizations,
            fill_categories=categories,
            should_publish=True,
        )
        course_run = CourseRunFactory(
            page_title="first session",
            page_parent=course.extended_object,
            resource_link="https://www.example.com/enroll",
            enrollment_start=datetime(2018, 10, 21, tzinfo=pytz.utc),
            enrollment_end=datetime(2019, 1, 18, tzinfo=pytz.utc),
            start=datetime(2018, 12, 10, tzinfo=pytz.utc),
            end=datetime(2019, 2, 14, tzinfo=pytz.utc),
            languages=["en", "fr"],
        )
        page = course_run.extended_object

        # Publish only 2 out of 4 categories and 2 out of 4 organizations
        categories[0].extended_object.publish("en")
        categories[1].extended_object.publish("en")
        organizations[0].extended_object.publish("en")
        organizations[1].extended_object.publish("en")

        # The unpublished objects may have been published and unpublished which puts them in a
        # status different from objects that have never been published.
        # We want to test both cases.
        categories[2].extended_object.publish("en")
        categories[2].extended_object.unpublish("en")
        organizations[2].extended_object.publish("en")
        organizations[2].extended_object.unpublish("en")

        # The page should be visible as draft to the staff user
        url = page.get_absolute_url()
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)

        self.assertContains(
            response,
            "<title>First session - Very interesting course</title>",
            html=True,
        )
        self.assertContains(
            response,
            '<h1 class="subheader__title">'
            "Very interesting course<br>first session</h1>",
            html=True,
        )

        # Draft and public organizations should all be present on the page
        for organization in organizations:
            self.assertContains(
                response,
                '<div class="organization-glimpse__title">{title:s}</div>'.
                format(title=organization.extended_object.get_title()),
                html=True,
            )

        # Draft organizations should be annotated for styling
        self.assertContains(response, "organization-glimpse--draft", count=2)

        # The published categories should be present on the page
        for category in categories[:2]:
            self.assertContains(
                response,
                ('<a class="category-badge" href="{:s}">'
                 '<span class="category-badge__title">{:s}</span></a>').format(
                     category.extended_object.get_absolute_url(),
                     category.extended_object.get_title(),
                 ),
                html=True,
            )

        # Draft categories should also be present on the page with an annotation for styling
        for category in categories[-2:]:
            self.assertContains(
                response,
                ('<a class="{element:s} {element:s}--draft" href="{url:s}">'
                 '<span class="category-badge__title">{title:s}</span></a>'
                 ).format(
                     url=category.extended_object.get_absolute_url(),
                     element="category-badge",
                     title=category.extended_object.get_title(),
                 ),
                html=True,
            )

        # The course run details should be on the page
        self.assertContains(
            response,
            "<strong>Enrollment starts</strong><span>Oct. 21, 2018</span>")
        self.assertContains(
            response,
            "<strong>Enrollment ends</strong><span>Jan. 18, 2019</span>")
        self.assertContains(
            response,
            "<strong>Course starts</strong><span>Dec. 10, 2018</span>")
        self.assertContains(
            response, "<strong>Course ends</strong><span>Feb. 14, 2019</span>")
        self.assertContains(
            response,
            "<strong>Languages</strong><span>English and french</span>")
示例#13
0
    def test_models_category_get_persons_descendants(self):
        """
        Related persons should include the persons linked to the category's descendants,
        unless specifically deactivated by the "include_descendants" argument.
        """
        category_page = create_page("Subjects",
                                    "courses/cms/category_detail.html",
                                    "en",
                                    published=True)
        category = CategoryFactory(extended_object=category_page,
                                   should_publish=True)
        persons = PersonFactory.create_batch(2,
                                             fill_categories=[category],
                                             should_publish=True)

        child_category_page = create_page(
            "Literature",
            "courses/cms/category_detail.html",
            "en",
            parent=category_page,
            published=True,
        )
        child_category = CategoryFactory(extended_object=child_category_page,
                                         should_publish=True)
        persons_child = PersonFactory.create_batch(
            2, fill_categories=[child_category], should_publish=True)

        grand_child_category_page = create_page(
            "Literature",
            "courses/cms/category_detail.html",
            "en",
            parent=child_category_page,
            published=True,
        )
        grand_child_category = CategoryFactory(
            extended_object=grand_child_category_page, should_publish=True)
        persons_grand_child = PersonFactory.create_batch(
            2, fill_categories=[grand_child_category], should_publish=True)

        # Check that each category gets persons from its descendants
        # ...unless we pass an argument to deactivate it
        self.assertEqual(list(category.get_persons()),
                         persons + persons_child + persons_grand_child)
        self.assertEqual(list(category.get_persons(include_descendants=False)),
                         persons)

        self.assertEqual(list(child_category.get_persons()),
                         persons_child + persons_grand_child)
        self.assertEqual(
            list(child_category.get_persons(include_descendants=False)),
            persons_child)

        self.assertEqual(
            list(grand_child_category.get_persons(include_descendants=False)),
            persons_grand_child,
        )
        self.assertEqual(list(grand_child_category.get_persons()),
                         persons_grand_child)
示例#14
0
    def test_indexable_filters_internationalization(self):
        """
        Indexable filters (such as categories and organizations by default) should have
        their names localized in the filter definitions in course search responses.
        """
        # Create the meta categories, each with a child category that should appear in facets
        subjects_meta = CategoryFactory(page_reverse_id="subjects",
                                        should_publish=True)
        subject = CategoryFactory(page_parent=subjects_meta.extended_object,
                                  should_publish=True)
        levels_meta = CategoryFactory(page_reverse_id="levels",
                                      should_publish=True)
        level = CategoryFactory(page_parent=levels_meta.extended_object,
                                should_publish=True)
        # Create 2 organizations that should appear in facets
        org_meta = OrganizationFactory(page_reverse_id="organizations",
                                       should_publish=True)
        org_1 = OrganizationFactory(
            page_parent=org_meta.extended_object,
            page_title="First organization",
            should_publish=True,
        )
        org_2 = OrganizationFactory(
            page_parent=org_meta.extended_object,
            page_title="Second organization",
            should_publish=True,
        )
        # Create a course linked to our categories and organizations
        CourseFactory(
            fill_categories=[subject, level],
            fill_organizations=[org_1, org_2],
            should_publish=True,
        )
        # Index our objects into ES
        bulk_compat(
            actions=[
                *ES_INDICES.categories.get_es_documents(),
                *ES_INDICES.organizations.get_es_documents(),
                *ES_INDICES.courses.get_es_documents(),
            ],
            chunk_size=500,
            client=ES_CLIENT,
        )
        ES_INDICES_CLIENT.refresh()

        response = self.client.get("/api/v1.0/courses/?scope=filters")
        self.assertEqual(response.status_code, 200)

        self.assertEqual(
            response.json()["filters"]["subjects"],
            {
                "base_path":
                "0001",
                "human_name":
                "Subjects",
                "is_autocompletable":
                True,
                "is_drilldown":
                False,
                "is_searchable":
                True,
                "name":
                "subjects",
                "position":
                2,
                "has_more_values":
                False,
                "values": [{
                    "count": 1,
                    "human_name": subject.extended_object.get_title(),
                    "key": subject.get_es_id(),
                }],
            },
        )
        self.assertEqual(
            response.json()["filters"]["levels"],
            {
                "base_path":
                "0002",
                "human_name":
                "Levels",
                "is_autocompletable":
                True,
                "is_drilldown":
                False,
                "is_searchable":
                True,
                "name":
                "levels",
                "position":
                3,
                "has_more_values":
                False,
                "values": [{
                    "count": 1,
                    "human_name": level.extended_object.get_title(),
                    "key": level.get_es_id(),
                }],
            },
        )
        self.assertEqual(
            response.json()["filters"]["organizations"],
            {
                "base_path":
                "0003",
                "human_name":
                "Organizations",
                "is_autocompletable":
                True,
                "is_drilldown":
                False,
                "is_searchable":
                True,
                "name":
                "organizations",
                "position":
                4,
                "has_more_values":
                False,
                "values": [
                    {
                        "count": 1,
                        "human_name": org_1.extended_object.get_title(),
                        "key": org_1.get_es_id(),
                    },
                    {
                        "count": 1,
                        "human_name": org_2.extended_object.get_title(),
                        "key": org_2.get_es_id(),
                    },
                ],
            },
        )
    def test_templates_person_detail_cms_draft_content(self):
        """
        A superuser should see a draft person including its draft elements with an
        annotation.
        """
        user = UserFactory(is_staff=True, is_superuser=True)
        self.client.login(username=user.username, password="******")

        published_category = CategoryFactory(should_publish=True)
        not_published_category = CategoryFactory()

        published_organization = OrganizationFactory(should_publish=True)
        not_published_organization = OrganizationFactory()

        person = PersonFactory(
            page_title="My page title",
            fill_portrait=True,
            fill_bio=True,
            fill_maincontent=True,
            fill_categories=[published_category, not_published_category],
            fill_organizations=[
                published_organization, not_published_organization
            ],
        )

        # Modify the draft version of the published category
        title_obj = published_category.extended_object.title_set.get(
            language="en")
        title_obj.title = "modified category"
        title_obj.save()

        # Modify the draft version of the published organization
        title_obj = published_category.extended_object.title_set.get(
            language="en")
        title_obj.title = "modified organization"
        title_obj.save()
        page = person.extended_object

        # The page should be visible as draft to the superuser
        url = page.get_absolute_url()
        response = self.client.get(url)
        content = htmlmin.minify(
            response.content.decode("UTF-8"),
            reduce_empty_attributes=False,
            remove_optional_attribute_quotes=False,
        )

        self.assertContains(response,
                            "<title>My page title</title>",
                            html=True,
                            status_code=200)
        title = person.extended_object.get_title()
        self.assertContains(
            response,
            f'<h1 class="subheader__title">{title:s}</h1>',
            html=True,
        )

        # Main content should be present when not empty
        self.assertContains(response, "person-detail__maincontent")

        # The published category should be on the page in its published version
        self.assertContains(
            response,
            (
                # pylint: disable=consider-using-f-string
                '<a class="category-badge" href="{:s}">'
                '<span class="category-badge__title">{:s}</span></a>').format(
                    published_category.public_extension.extended_object.
                    get_absolute_url(),
                    published_category.public_extension.extended_object.
                    get_title(),
                ),
            html=True,
        )
        # The not published category should not be on the page
        self.assertContains(
            response,
            (
                # pylint: disable=consider-using-f-string
                '<a class="category-badge category-badge--draft" href="{:s}">'
                '<span class="category-badge__title">{:s}</span></a>').format(
                    not_published_category.extended_object.get_absolute_url(),
                    not_published_category.extended_object.get_title(),
                ),
            html=True,
        )

        # The published organization should be on the page in its published version
        self.assertIn(
            # pylint: disable=consider-using-f-string
            '<div class="organization-glimpse" property="contributor" '
            'typeof="CollegeOrUniversity"><a href="{:s}" title="{:s}">'.format(
                published_organization.extended_object.get_absolute_url(),
                published_organization.extended_object.get_title(),
            ),
            content,
        )
        self.assertContains(
            response,
            # pylint: disable=consider-using-f-string
            '<div class="organization-glimpse__title" property="name">{:s}</div>'
            .format(published_organization.public_extension.extended_object.
                    get_title()),
            html=True,
        )
        # The not published organization should not be on the page
        self.assertIn(
            # pylint: disable=consider-using-f-string
            '<a href="{:s}" title="{:s}">'.format(
                not_published_organization.extended_object.get_absolute_url(),
                not_published_organization.extended_object.get_title(),
            ),
            content,
        )

        self.assertContains(
            response,
            # pylint: disable=consider-using-f-string
            '<div class="organization-glimpse__title" property="name">{:s}</div>'
            .format(not_published_organization.extended_object.get_title()),
            html=True,
        )

        self.assertNotContains(response, "modified")
示例#16
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"],
        )

        # Publish only 2 out of 4 categories, icons and organizations
        self.assertTrue(categories[0].extended_object.publish("en"))
        self.assertTrue(categories[1].extended_object.publish("en"))
        self.assertTrue(icons[0].extended_object.publish("en"))
        self.assertTrue(icons[1].extended_object.publish("en"))
        self.assertTrue(organizations[0].extended_object.publish("en"))
        self.assertTrue(organizations[1].extended_object.publish("en"))

        # The unpublished objects may have been published and unpublished which puts them in a
        # status different from objects that have never been published.
        # We want to test both cases.
        self.assertTrue(categories[2].extended_object.publish("en"))
        self.assertTrue(categories[2].extended_object.unpublish("en"))
        self.assertTrue(icons[2].extended_object.publish("en"))
        self.assertTrue(icons[2].extended_object.unpublish("en"))
        self.assertTrue(organizations[2].extended_object.publish("en"))
        self.assertTrue(organizations[2].extended_object.unpublish("en"))

        # The page should not be visible before it is published
        url = page.get_absolute_url()
        response = self.client.get(url)
        self.assertEqual(response.status_code, 404)

        # Publish and ensure content is correct
        self.assertTrue(page.publish("en"))

        # Create an unpublished ongoing open course run (created after
        # publishing the page)
        CourseRunFactory(
            direct_course=course,
            start=now - timedelta(hours=1),
            end=now + timedelta(hours=2),
            enrollment_end=now + timedelta(hours=1),
            languages=["en", "fr"],
        )

        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)

        self.assertContains(response,
                            "<title>Very interesting course</title>",
                            html=True)
        self.assertContains(
            response,
            '<h1 class="subheader__title">Very interesting course</h1>',
            html=True,
        )

        # Only published categories should be present on the page
        for category in categories[:2]:
            self.assertContains(
                response,
                ('<a class="category-badge" href="{:s}">'
                 '<span class="category-badge__title">{:s}</span></a>').format(
                     category.extended_object.get_absolute_url(),
                     category.extended_object.get_title(),
                 ),
                html=True,
            )
        for category in categories[-2:]:
            self.assertNotContains(response,
                                   category.extended_object.get_title())

        # Only published icons should be present on the page
        pattern = (
            r'<a.*class="category-badge".*href="{link:s}".*>'
            r'<img src="/media/filer_public_thumbnails/filer_public/.*icon\.jpg.*alt="{title:s}">'
            r'<span class="category-badge__title">'
            r".*{title:s}.*</span>")

        for icon in icons[:2]:
            self.assertIsNotNone(
                re.search(
                    pattern.format(
                        link=icon.extended_object.get_absolute_url(),
                        title=icon.extended_object.get_title(),
                    ),
                    str(response.content),
                ))
        for icon in icons[-2:]:
            self.assertNotContains(response, icon.extended_object.get_title())

        # Public organizations should be in response content
        for organization in organizations[:2]:
            self.assertContains(
                response,
                '<div class="organization-glimpse__title">{title:s}</div>'.
                format(title=organization.extended_object.get_title()),
                html=True,
            )

        # Draft organizations should not be in response content
        for organization in organizations[-2:]:
            self.assertNotContains(response,
                                   organization.extended_object.get_title(),
                                   html=True)

        # Only the published course run should be in response content
        self.assertEqual(CourseRun.objects.count(), 3)
        self.assertContains(response,
                            "<dd>English and french</dd>",
                            html=True,
                            count=1)
    def test_templates_organization_detail_cms_draft_content(self):
        """
        A staff user should see a draft organization including only the related objects that
        are published.
        """
        user = UserFactory(is_staff=True, is_superuser=True)
        self.client.login(username=user.username, password="******")

        published_category = CategoryFactory(should_publish=True)
        not_published_category = CategoryFactory()

        organization = OrganizationFactory(
            page_title="La Sorbonne",
            fill_categories=[published_category, not_published_category],
        )

        published_course = CourseFactory(
            fill_organizations=[organization], should_publish=True
        )
        not_published_course = CourseFactory(fill_organizations=[organization])

        # Republish courses to take into account adding the organization
        published_course.extended_object.publish("en")

        # Modify the draft version of the published category
        title_obj = published_category.extended_object.title_set.get(language="en")
        title_obj.title = "modified category"
        title_obj.save()

        # Modify the draft version of the published course
        title_obj = published_course.extended_object.title_set.get(language="en")
        title_obj.title = "modified course"
        title_obj.save()

        page = organization.extended_object

        # The page should be visible as draft to the staff user
        url = page.get_absolute_url()
        response = self.client.get(url)
        self.assertContains(
            response, "<title>La Sorbonne</title>", html=True, status_code=200
        )
        self.assertContains(
            response,
            '<h1 class="organization-detail__title">La Sorbonne</h1>',
            html=True,
        )

        # The published category should be on the page in its published version
        self.assertContains(
            response,
            (
                # pylint: disable=consider-using-f-string
                '<a class="category-tag" href="{:s}">'
                '<span class="category-tag__title">{:s}</span></a>'
            ).format(
                published_category.public_extension.extended_object.get_absolute_url(),
                published_category.public_extension.extended_object.get_title(),
            ),
            html=True,
        )
        # The not published category should be on the page
        self.assertContains(
            response,
            (
                # pylint: disable=consider-using-f-string
                '<a class="category-tag category-tag--draft" href="{:s}">'
                '<span class="category-tag__title">{:s}</span></a>'
            ).format(
                not_published_category.extended_object.get_absolute_url(),
                not_published_category.extended_object.get_title(),
            ),
            html=True,
        )
        # The modified draft category should not be leaked
        self.assertNotContains(response, "modified category")

        # The published course should be on the page in its draft version
        self.assertContains(
            response,
            '<p class="course-glimpse__title">modified course</p>',
            html=True,
        )

        # The not published course should be on the page
        self.assertContains(
            response,
            not_published_course.extended_object.get_title(),
        )
    def test_templates_course_detail_cms_published_content(self):
        """
        Validate that the important elements are displayed on a published course page
        """
        categories = CategoryFactory.create_batch(4)
        icons = CategoryFactory.create_batch(4, fill_icon=True)
        organizations = OrganizationFactory.create_batch(4)

        course = CourseFactory(
            page_title="Very interesting course",
            fill_organizations=organizations,
            fill_categories=categories,
            fill_icons=icons,
        )
        page = course.extended_object
        course_run1, _course_run2 = CourseRunFactory.create_batch(
            2, page_parent=course.extended_object, languages=["en", "fr"]
        )
        self.assertFalse(course_run1.extended_object.publish("en"))

        # Publish only 2 out of 4 categories, icons and organizations
        categories[0].extended_object.publish("en")
        categories[1].extended_object.publish("en")
        icons[0].extended_object.publish("en")
        icons[1].extended_object.publish("en")
        organizations[0].extended_object.publish("en")
        organizations[1].extended_object.publish("en")

        # The unpublished objects may have been published and unpublished which puts them in a
        # status different from objects that have never been published.
        # We want to test both cases.
        categories[2].extended_object.publish("en")
        categories[2].extended_object.unpublish("en")
        icons[2].extended_object.publish("en")
        icons[2].extended_object.unpublish("en")
        organizations[2].extended_object.publish("en")
        organizations[2].extended_object.unpublish("en")

        # The page should not be visible before it is published
        url = page.get_absolute_url()
        response = self.client.get(url)
        self.assertEqual(response.status_code, 404)

        # Publish and ensure content is correct
        page.publish("en")

        # Now we can publish children course runs: publish only 1 of the 2
        course_run1.extended_object.parent_page.refresh_from_db()
        self.assertTrue(course_run1.extended_object.publish("en"))

        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)

        self.assertContains(
            response, "<title>Very interesting course</title>", html=True
        )
        self.assertContains(
            response,
            '<h1 class="course-detail__content__title">Very interesting course</h1>',
            html=True,
        )

        # Only published categories should be present on the page
        for category in categories[:2]:
            self.assertContains(
                response,
                (
                    '<a class="category-plugin-tag" href="{:s}">'
                    '<div class="category-plugin-tag__title">{:s}</div></a>'
                ).format(
                    category.extended_object.get_absolute_url(),
                    category.extended_object.get_title(),
                ),
                html=True,
            )
        for category in categories[-2:]:
            self.assertNotContains(response, category.extended_object.get_title())

        # Only published icons should be present on the page
        pattern = (
            r'<a.*class="category-plugin-tag".*href="{link:s}".*>'
            r'<figure class="category-plugin-tag__figure">'
            r'<figcaption.*class="category-plugin-tag__figure__caption".*>'
            r".*{title:s}.*</figcaption>"
            r'<img src="/media/filer_public_thumbnails/filer_public/.*icon\.jpg.*alt=""/>'
        )

        for icon in icons[:2]:
            self.assertIsNotNone(
                re.search(
                    pattern.format(
                        link=icon.extended_object.get_absolute_url(),
                        title=icon.extended_object.get_title(),
                    ),
                    str(response.content),
                )
            )
        for icon in icons[-2:]:
            self.assertNotContains(response, icon.extended_object.get_title())

        # Public organizations should be in response content
        for organization in organizations[:2]:
            self.assertContains(
                response,
                '<div class="organization-glimpse__title">{title:s}</div>'.format(
                    title=organization.extended_object.get_title()
                ),
                html=True,
            )

        # Draft organizations should not be in response content
        for organization in organizations[-2:]:
            self.assertNotContains(
                response, organization.extended_object.get_title(), html=True
            )

        # Only the published course run should be in response content
        self.assertContains(response, "<dd>English and french</dd>", html=True, count=1)
示例#19
0
    def test_indexers_courses_get_es_documents_from_models(self, _mock_picture):
        """
        Happy path: the data is retrieved from the models properly formatted
        """
        # Create a course with a page in both english and french
        published_categories = [
            CategoryFactory(  # L-0001
                fill_icon=True,
                page_title={"en": "Title L-0001", "fr": "Titre L-0001"},
                should_publish=True,
            ),
            CategoryFactory(  # L-0002
                fill_icon=True,
                page_title={"en": "Title L-0002", "fr": "Titre L-0002"},
                should_publish=True,
            ),
        ]
        draft_category = CategoryFactory(fill_icon=True)  # L-0003

        main_organization = OrganizationFactory(  # L-0004
            page_title={
                "en": "english main organization title",
                "fr": "titre organisation principale français",
            },
            should_publish=True,
        )
        other_draft_organization = OrganizationFactory(  # L-0005
            page_title={
                "en": "english other organization title",
                "fr": "titre autre organisation français",
            }
        )
        other_published_organization = OrganizationFactory(  # L-0006
            page_title={
                "en": "english other organization title",
                "fr": "titre autre organisation français",
            },
            should_publish=True,
        )

        person1 = PersonFactory(
            page_title={"en": "Eugène Delacroix", "fr": "Eugène Delacroix"},
            should_publish=True,
        )
        person2 = PersonFactory(
            page_title={"en": "Comte de Saint-Germain", "fr": "Earl of Saint-Germain"},
            should_publish=True,
        )
        person_draft = PersonFactory(
            page_title={"en": "Jules de Polignac", "fr": "Jules de Polignac"}
        )

        course = CourseFactory(
            duration=[3, WEEK],
            effort=[2, HOUR, WEEK],
            fill_categories=published_categories + [draft_category],
            fill_cover=True,
            fill_icons=published_categories + [draft_category],
            fill_organizations=[
                main_organization,
                other_draft_organization,
                other_published_organization,
            ],
            fill_team=[person1, person_draft, person2],
            page_title={
                "en": "an english course title",
                "fr": "un titre cours français",
            },
        )
        CourseRunFactory.create_batch(2, direct_course=course)
        course.extended_object.publish("en")
        course.extended_object.publish("fr")
        course.refresh_from_db()

        # Add a description in several languages
        placeholder = course.public_extension.extended_object.placeholders.get(
            slot="course_description"
        )
        plugin_params = {"placeholder": placeholder, "plugin_type": "CKEditorPlugin"}
        add_plugin(body="english description line 1.", language="en", **plugin_params)
        add_plugin(body="english description line 2.", language="en", **plugin_params)
        add_plugin(body="a propos français ligne 1.", language="fr", **plugin_params)
        add_plugin(body="a propos français ligne 2.", language="fr", **plugin_params)

        # The results were properly formatted and passed to the consumer
        expected_course = {
            "_id": str(course.public_extension.extended_object_id),
            "_index": "some_index",
            "_op_type": "some_action",
            "_type": "course",
            "absolute_url": {
                "en": "/en/an-english-course-title/",
                "fr": "/fr/un-titre-cours-francais/",
            },
            "categories": ["L-0001", "L-0002"],
            "categories_names": {
                "en": ["Title L-0001", "Title L-0002"],
                "fr": ["Titre L-0001", "Titre L-0002"],
            },
            "complete": {
                "en": [
                    "an english course title",
                    "english course title",
                    "course title",
                    "title",
                ],
                "fr": [
                    "un titre cours français",
                    "titre cours français",
                    "cours français",
                    "français",
                ],
            },
            "course_runs": [
                {
                    "start": course_run.public_course_run.start,
                    "end": course_run.public_course_run.end,
                    "enrollment_start": course_run.public_course_run.enrollment_start,
                    "enrollment_end": course_run.public_course_run.enrollment_end,
                    "languages": course_run.public_course_run.languages,
                }
                for course_run in course.get_course_runs().order_by("-end")
            ],
            "cover_image": {
                "en": {"info": "picture info"},
                "fr": {"info": "picture info"},
            },
            "description": {
                "en": "english description line 1. english description line 2.",
                "fr": "a propos français ligne 1. a propos français ligne 2.",
            },
            "duration": {"en": "3 weeks", "fr": "3 semaines"},
            "effort": {"en": "2 hours/week", "fr": "2 heures/semaine"},
            "icon": {
                "en": {
                    "color": published_categories[0].color,
                    "info": "picture info",
                    "title": "Title L-0001",
                },
                "fr": {
                    "color": published_categories[0].color,
                    "info": "picture info",
                    "title": "Titre L-0001",
                },
            },
            "is_new": False,
            "is_listed": True,
            "organization_highlighted": {
                "en": "english main organization title",
                "fr": "titre organisation principale français",
            },
            "organizations": ["L-0004", "L-0006"],
            "organizations_names": {
                "en": [
                    "english main organization title",
                    "english other organization title",
                ],
                "fr": [
                    "titre organisation principale français",
                    "titre autre organisation français",
                ],
            },
            "persons": [
                str(person1.public_extension.extended_object_id),
                str(person2.public_extension.extended_object_id),
            ],
            "persons_names": {
                "en": ["Eugène Delacroix", "Comte de Saint-Germain"],
                "fr": ["Eugène Delacroix", "Earl of Saint-Germain"],
            },
            "title": {"fr": "un titre cours français", "en": "an english course title"},
        }
        indexed_courses = list(
            CoursesIndexer.get_es_documents(index="some_index", action="some_action")
        )
        self.assertEqual(len(indexed_courses), 1)
        self.assertEqual(indexed_courses[0], expected_course)
示例#20
0
    def test_indexers_courses_get_es_document_for_course_related_names_course_unpublished(
        self,
    ):
        """
        When a course is unpublished in one language, but it has related objects that are still
        published in this language, should or shouldn't we use this language's content for the
        related objects when building the course glimpse?

        This choice is controversial and I had to write these tests to fully understand it. So I
        propose to commit them to keep this memory.

        Note: We could argue in the future that a course glimpse should be built with content
        only in the same language and not with "as few fallback languages as possible, using
        available content in each language".
        """
        # Create a course with related pages in both english and french but only
        # published in one language
        category = CategoryFactory(
            page_title={
                "en": "english category title",
                "fr": "titre catégorie français",
            },
            should_publish=True,
        )

        organization_title = {
            "en": "english organization title",
            "fr": "titre organisation français",
        }
        organization = OrganizationFactory(
            page_title=organization_title, should_publish=True
        )

        person = PersonFactory(
            page_title={"en": "Brian", "fr": "François"}, should_publish=True
        )

        course = CourseFactory(
            fill_categories=[category],
            fill_organizations=[organization],
            fill_team=[person],
            page_title={
                "en": "an english course title",
                "fr": "un titre cours français",
            },
            should_publish=True,
        )
        course.extended_object.unpublish("fr")

        course_document = CoursesIndexer.get_es_document_for_course(course)

        self.assertEqual(
            course_document["organizations_names"],
            {
                "en": ["english organization title"],
                "fr": ["titre organisation français"],
            },
        )
        self.assertEqual(
            course_document["organization_highlighted"], organization_title
        )
        self.assertEqual(
            course_document["categories_names"],
            {"en": ["english category title"], "fr": ["titre catégorie français"]},
        )
        self.assertEqual(
            course_document["persons_names"], {"en": ["Brian"], "fr": ["François"]}
        )
示例#21
0
def create_categories(
    children=None,
    color=None,
    fill_banner=True,
    fill_description=True,
    fill_icon=False,
    fill_logo=True,
    page_in_navigation=True,
    page_title=None,
    page_parent=None,
    page_reverse_id=None,
    should_publish=True,
):
    """
    Create the category tree from the SUBJECTS dictionary.


    Arguments:
        info (List): definition of the category tree to create in the following format:

            {
                "title": "Subject",
                "children": [
                    {
                        "title": "Computer science",
                        "children": [
                            {"title": "Coding"},
                            {"title": "Security"},
                        ],
                    },
                    {"title": "Languages"},
                ],
            }

        page (cms.models.pagemodel.Page): Instance of a Page below which the category
            tree is created.

    Returns:
        generator[courses.models.Category]: yield only the leaf categories of the created tree.

    """
    category = CategoryFactory(
        color=color,
        fill_banner=fill_banner,
        fill_description=fill_description,
        fill_icon=fill_icon,
        fill_logo=fill_logo,
        page_title=page_title,
        page_reverse_id=page_reverse_id,
        page_in_navigation=page_in_navigation,
        page_parent=page_parent,
        should_publish=should_publish,
    )

    if children:
        for child_info in children:
            kwargs = {
                "fill_banner": fill_banner,
                "fill_description": fill_description,
                "fill_icon": fill_icon,
                "fill_logo": fill_logo,
                "page_parent": category.extended_object,
                "should_publish": should_publish,
            }
            kwargs.update(child_info)
            yield from create_categories(**kwargs)
    else:
        # we only return leaf categories (no children)
        yield category
示例#22
0
    def execute_query(self, querystring=""):
        """
        Not a test.
        This method is doing the heavy lifting for the tests in this class: create and fill the
        index with our courses so we can run our queries and check our facet counts.
        It also executes the query and returns the result from the API.
        """
        # Create the subject category page. This is necessary to link the subjects we
        # defined above with the "subjects" filter
        # As it is the only page we create, we expect it to have the path "0001"
        CategoryFactory(page_reverse_id="subjects", should_publish=True)

        # Index these 4 courses in Elasticsearch
        indices_client = IndicesClient(client=ES_CLIENT)
        # Delete any existing indexes so we get a clean slate
        indices_client.delete(index="_all")
        # Create an index we'll use to test the ES features
        indices_client.create(index="test_courses")
        indices_client.close(index="test_courses")
        indices_client.put_settings(body=ANALYSIS_SETTINGS,
                                    index="test_courses")
        indices_client.open(index="test_courses")

        # Use the default courses mapping from the Indexer
        indices_client.put_mapping(body=CoursesIndexer.mapping,
                                   doc_type="course",
                                   index="test_courses")
        # Add the sorting script
        ES_CLIENT.put_script(id="state", body=CoursesIndexer.scripts["state"])
        # Actually insert our courses in the index
        actions = [{
            "_id":
            course["id"],
            "_index":
            "test_courses",
            "_op_type":
            "create",
            "_type":
            "course",
            "absolute_url": {
                "en": "url"
            },
            "cover_image": {
                "en": "image"
            },
            "title": {
                "en": "title"
            },
            **course,
            "course_runs": [{
                "languages": course_run["languages"],
                "start": arrow.utcnow().datetime,
                "end": arrow.utcnow().datetime,
                "enrollment_start": arrow.utcnow().datetime,
                "enrollment_end": arrow.utcnow().datetime,
            } for course_run in course["course_runs"]],
        } for course in COURSES]
        bulk(actions=actions, chunk_size=500, client=ES_CLIENT)
        indices_client.refresh()

        response = self.client.get(f"/api/v1.0/courses/?{querystring:s}")
        self.assertEqual(response.status_code, 200)

        return json.loads(response.content)
示例#23
0
    def test_templates_course_run_detail_cms_published_content(self):
        """
        Validate that the important elements are displayed on a published course run page
        """
        categories = CategoryFactory.create_batch(4)
        organizations = OrganizationFactory.create_batch(4)

        course = CourseFactory(
            page_title="Very interesting course",
            fill_organizations=organizations,
            fill_categories=categories,
            should_publish=True,
        )
        course_run = CourseRunFactory(
            page_title="first session",
            page_parent=course.extended_object,
            resource_link="https://www.example.com/enroll",
            enrollment_start=datetime(2018, 10, 21, tzinfo=pytz.utc),
            enrollment_end=datetime(2019, 1, 18, tzinfo=pytz.utc),
            start=datetime(2018, 12, 10, tzinfo=pytz.utc),
            end=datetime(2019, 2, 14, tzinfo=pytz.utc),
            languages=["en", "fr"],
        )
        page = course_run.extended_object

        # Publish only 2 out of 4 categories and 2 out of 4 organizations
        categories[0].extended_object.publish("en")
        categories[1].extended_object.publish("en")
        organizations[0].extended_object.publish("en")
        organizations[1].extended_object.publish("en")

        # The unpublished objects may have been published and unpublished which puts them in a
        # status different from objects that have never been published.
        # We want to test both cases.
        categories[2].extended_object.publish("en")
        categories[2].extended_object.unpublish("en")
        organizations[2].extended_object.publish("en")
        organizations[2].extended_object.unpublish("en")

        # The page should not be visible before it is published
        url = page.get_absolute_url()
        response = self.client.get(url)
        self.assertEqual(response.status_code, 404)

        # Now publish the page and check its content
        page.publish("en")
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)

        self.assertContains(
            response,
            "<title>First session - Very interesting course</title>",
            html=True,
        )
        self.assertContains(
            response,
            '<h1 class="subheader__title">'
            "Very interesting course<br>first session</h1>",
            html=True,
        )

        # Only published categories should be present on the page
        for category in categories[:2]:
            self.assertContains(
                response,
                ('<a class="category-badge" href="{:s}">'
                 '<span class="category-badge__title">{:s}</span></a>').format(
                     category.extended_object.get_absolute_url(),
                     category.extended_object.get_title(),
                 ),
                html=True,
            )
        for category in categories[-2:]:
            self.assertNotContains(response,
                                   category.extended_object.get_title())

        # Public organizations should be in response content
        for organization in organizations[:2]:
            self.assertContains(
                response,
                '<div class="organization-glimpse__title">{title:s}</div>'.
                format(title=organization.extended_object.get_title()),
                html=True,
            )

        # Draft organizations should not be in response content
        for organization in organizations[-2:]:
            self.assertNotContains(response,
                                   organization.extended_object.get_title(),
                                   html=True)

        # The course run details should be on the page
        self.assertContains(
            response,
            "<strong>Enrollment starts</strong><span>Oct. 21, 2018</span>")
        self.assertContains(
            response,
            "<strong>Enrollment ends</strong><span>Jan. 18, 2019</span>")
        self.assertContains(
            response,
            "<strong>Course starts</strong><span>Dec. 10, 2018</span>")
        self.assertContains(
            response, "<strong>Course ends</strong><span>Feb. 14, 2019</span>")
        self.assertContains(
            response,
            "<strong>Languages</strong><span>English and french</span>")
示例#24
0
 def test_factories_category_color(self):
     """The category factory should leave the color field null."""
     category = CategoryFactory()
     self.assertIsNone(category.color)
示例#25
0
    def test_templates_person_detail_cms_draft_content(self):
        """
        A superuser should see a draft person including its draft elements with an
        annotation.
        """
        user = UserFactory(is_staff=True, is_superuser=True)
        self.client.login(username=user.username, password="******")

        published_category = CategoryFactory(should_publish=True)
        not_published_category = CategoryFactory()

        published_organization = OrganizationFactory(should_publish=True)
        not_published_organization = OrganizationFactory()

        person = PersonFactory(
            page_title="My page title",
            fill_portrait=True,
            fill_bio=True,
            fill_maincontent=True,
            fill_categories=[published_category, not_published_category],
            fill_organizations=[
                published_organization, not_published_organization
            ],
        )

        # Modify the draft version of the published category
        title_obj = published_category.extended_object.title_set.get(
            language="en")
        title_obj.title = "modified category"
        title_obj.save()

        # Modify the draft version of the published organization
        title_obj = published_category.extended_object.title_set.get(
            language="en")
        title_obj.title = "modified organization"
        title_obj.save()
        page = person.extended_object

        # The page should be visible as draft to the superuser
        url = page.get_absolute_url()
        response = self.client.get(url)
        self.assertContains(response,
                            "<title>My page title</title>",
                            html=True,
                            status_code=200)
        title = person.extended_object.get_title()
        self.assertContains(
            response,
            f'<h1 class="subheader__title">{title:s}</h1>',
            html=True,
        )

        # Main content should be present when not empty
        self.assertContains(response, "person-detail__maincontent")

        # The published category should be on the page in its published version
        self.assertContains(
            response,
            ('<a class="category-badge" href="{:s}">'
             '<span class="category-badge__title">{:s}</span></a>').format(
                 published_category.public_extension.extended_object.
                 get_absolute_url(),
                 published_category.public_extension.extended_object.get_title(
                 ),
             ),
            html=True,
        )
        # The not published category should be on the page, mark as draft
        self.assertContains(
            response,
            ('<a class="category-badge category-badge--draft" '
             'href="{:s}"><span class="category-badge__title">{:s}</span></a>'
             ).format(
                 not_published_category.extended_object.get_absolute_url(),
                 not_published_category.extended_object.get_title(),
             ),
            html=True,
        )

        # The published organization should be on the page in its published version
        self.assertContains(
            response,
            '<div class="organization-glimpse__title">{:s}</div>'.format(
                published_organization.public_extension.extended_object.
                get_title()),
            html=True,
        )

        # The not published organization should be on the page, mark as draft
        self.assertContains(
            response,
            '<div class="organization-glimpse__title">{:s}</div>'.format(
                not_published_organization.extended_object.get_title()),
            html=True,
        )
        self.assertIn(
            ('<a class="organization-glimpse organization-glimpse--draft" '
             'href="{url:s}" title="{title:s}">').format(
                 url=not_published_organization.extended_object.
                 get_absolute_url(),
                 title=not_published_organization.extended_object.get_title(),
             ),
            re.sub(" +", " ",
                   str(response.content).replace("\\n", "")),
        )

        self.assertNotContains(response, "modified")
    def test_views_filter_definitions(self):
        """
        Returns an object with all the filter definitions, keyed by machine name.
        """
        # Create pages with expected `reverse_id` to ensure the base paths are returned
        # as part of the filter definitions.
        create_i18n_page(
            {
                "en": "Organizations",
                "fr": "Organisations"
            },
            reverse_id="organizations",
            published=True,
        )
        create_i18n_page({
            "en": "Persons",
            "fr": "Personnes"
        },
                         reverse_id="persons",
                         published=True)
        categories_page = create_i18n_page(
            {
                "en": "Categories",
                "fr": "Catégories"
            }, published=True)
        CategoryFactory(
            page_parent=categories_page,
            page_reverse_id="subjects",
            page_title={
                "en": "Subjects",
                "fr": "Sujets"
            },
            should_publish=True,
        )
        CategoryFactory(
            page_parent=categories_page,
            page_reverse_id="levels",
            page_title={
                "en": "Levels",
                "fr": "Niveaux"
            },
            should_publish=True,
        )

        response = self.client.get("/api/v1.0/filter-definitions/")
        self.assertEqual(
            json.loads(response.content),
            {
                "new": {
                    "base_path": None,
                    "human_name": "New courses",
                    "is_autocompletable": False,
                    "is_drilldown": False,
                    "is_searchable": False,
                    "name": "new",
                    "position": 0,
                },
                "availability": {
                    "base_path": None,
                    "human_name": "Availability",
                    "is_autocompletable": False,
                    "is_drilldown": True,
                    "is_searchable": False,
                    "name": "availability",
                    "position": 1,
                },
                "subjects": {
                    "base_path": "00030001",
                    "human_name": "Subjects",
                    "is_autocompletable": True,
                    "is_drilldown": False,
                    "is_searchable": True,
                    "name": "subjects",
                    "position": 2,
                },
                "levels": {
                    "base_path": "00030002",
                    "human_name": "Levels",
                    "is_autocompletable": True,
                    "is_drilldown": False,
                    "is_searchable": True,
                    "name": "levels",
                    "position": 3,
                },
                "organizations": {
                    "base_path": "0001",
                    "human_name": "Organizations",
                    "is_autocompletable": True,
                    "is_drilldown": False,
                    "is_searchable": True,
                    "name": "organizations",
                    "position": 4,
                },
                "languages": {
                    "base_path": None,
                    "human_name": "Languages",
                    "is_autocompletable": False,
                    "is_drilldown": False,
                    "is_searchable": False,
                    "name": "languages",
                    "position": 5,
                },
                "persons": {
                    "base_path": "0002",
                    "human_name": "Persons",
                    "is_autocompletable": True,
                    "is_drilldown": False,
                    "is_searchable": True,
                    "name": "persons",
                    "position": 5,
                },
            },
        )
    def test_templates_organization_detail_cms_draft_content(self):
        """
        A staff user should see a draft organization including its draft elements with an
        annotation.
        """
        user = UserFactory(is_staff=True, is_superuser=True)
        self.client.login(username=user.username, password="******")

        published_category = CategoryFactory(should_publish=True)
        not_published_category = CategoryFactory()

        organization = OrganizationFactory(
            page_title="La Sorbonne",
            fill_categories=[published_category, not_published_category],
        )

        published_course = CourseFactory(fill_organizations=[organization],
                                         should_publish=True)
        not_published_course = CourseFactory(fill_organizations=[organization])

        # Republish courses to take into account adding the organization
        published_course.extended_object.publish("en")

        # Modify the draft version of the published category
        title_obj = published_category.extended_object.title_set.get(
            language="en")
        title_obj.title = "modified category"
        title_obj.save()

        # Modify the draft version of the published course
        title_obj = published_course.extended_object.title_set.get(
            language="en")
        title_obj.title = "modified course"
        title_obj.save()

        page = organization.extended_object

        # The page should be visible as draft to the staff user
        url = page.get_absolute_url()
        response = self.client.get(url)
        self.assertContains(response,
                            "<title>La Sorbonne</title>",
                            html=True,
                            status_code=200)
        self.assertContains(
            response,
            '<h1 class="organization-detail__title">La Sorbonne</h1>',
            html=True,
        )

        # The published category should be on the page in its published version
        self.assertContains(
            response,
            ('<a class="category-plugin-tag" href="{:s}">'
             '<div class="category-plugin-tag__title">{:s}</div></a>').format(
                 published_category.public_extension.extended_object.
                 get_absolute_url(),
                 published_category.public_extension.extended_object.get_title(
                 ),
             ),
            html=True,
        )
        # The not published category should be on the page, mark as draft
        self.assertContains(
            response,
            ('<a class="category-plugin-tag category-plugin-tag--draft" '
             'href="{:s}"><div class="category-plugin-tag__title">{:s}</div></a>'
             ).format(
                 not_published_category.extended_object.get_absolute_url(),
                 not_published_category.extended_object.get_title(),
             ),
            html=True,
        )
        # The modified draft category should not be leaked
        self.assertNotContains(response, "modified category")

        # The published course should be on the page in its draft version
        self.assertContains(
            response,
            '<p class="course-glimpse__content__title">modified course</p>',
            html=True,
        )

        # The not published course should be on the page, mark as draft
        self.assertContains(
            response,
            '<p class="course-glimpse__content__title">{:s}</p>'.format(
                not_published_course.extended_object.get_title()),
            html=True,
        )
        self.assertIn(
            '<a class=" course-glimpse course-glimpse--link course-glimpse--draft " '
            'href="{:s}"'.format(
                not_published_course.extended_object.get_absolute_url()),
            re.sub(" +", " ",
                   str(response.content).replace("\\n", "")),
        )
    def test_templates_blogpost_detail_cms_draft_content(self):
        """
        A staff user should see a draft blogpost including only its published linked objects.
        """
        user = UserFactory(is_staff=True, is_superuser=True)
        self.client.login(username=user.username, password="******")

        category = CategoryFactory()
        published_category = CategoryFactory(should_publish=True)
        author = PersonFactory(page_title={"en": "Comte de Saint-Germain"},
                               should_publish=True)

        blogpost = BlogPostFactory(
            page_title="Preums",
            fill_cover=True,
            fill_body=True,
            fill_categories=[category, published_category],
            fill_author=[author],
        )
        page = blogpost.extended_object

        # The page should be visible as draft to the staff user
        url = page.get_absolute_url()
        response = self.client.get(url)

        self.assertContains(response,
                            "<title>Preums - example.com</title>",
                            html=True,
                            status_code=200)
        self.assertContains(response,
                            '<h1 class="blogpost-detail__title">Preums</h1>',
                            html=True)
        self.assertContains(response, "Comte de Saint-Germain", html=True)
        self.assertContains(
            response,
            (
                # pylint: disable=consider-using-f-string
                '<a class="category-tag" '
                'href="{:s}"><span class="category-tag__title">{:s}</span></a>'
            ).format(
                published_category.extended_object.get_absolute_url(),
                published_category.extended_object.get_title(),
            ),
            html=True,
        )
        self.assertContains(
            response,
            (
                # pylint: disable=consider-using-f-string
                '<a class="category-tag category-tag--draft" '
                'href="{:s}"><span class="category-tag__title">{:s}</span></a>'
            ).format(
                category.extended_object.get_absolute_url(),
                category.extended_object.get_title(),
            ),
            html=True,
        )
        self.assertContains(
            response,
            '<p class="blogpost-detail__pubdate">Not published yet</p>',
            html=True,
        )
    def test_cms_plugins_organizations_by_category_render_on_public_page(self):
        """
        The organizations by category plugin should render as expected on a public page.
        """
        # Create a category
        category = CategoryFactory(
            page_title={"en": "category title", "fr": "titre catégorie"}
        )
        category_page = category.extended_object

        # Create organizations
        published_organization = OrganizationFactory(
            page_title={"en": "public title", "fr": "titre public"},
            fill_categories=[category],
            fill_logo={"original_filename": "logo.jpg"},
            should_publish=True,
        )
        OrganizationFactory(
            page_title={"en": "private title", "fr": "titre privé"},
            fill_categories=[category],
            fill_logo={"original_filename": "logo.jpg"},
        )

        # Create a page to add the plugin to
        page = create_i18n_page({"en": "A page", "fr": "Une page"})
        placeholder = page.placeholders.get(slot="maincontent")
        add_plugin(
            placeholder, OrganizationsByCategoryPlugin, "en", **{"page": category_page}
        )
        add_plugin(
            placeholder, OrganizationsByCategoryPlugin, "fr", **{"page": category_page}
        )

        category_page.publish("en")
        category_page.publish("fr")
        category.refresh_from_db()

        page.publish("en")
        page.publish("fr")

        # Check the page content in English
        url = page.get_absolute_url(language="en")

        # The plugin should not be visible on the public page before it is published
        category_page.unpublish("en")
        response = self.client.get(url)
        self.assertNotContains(response, "public title")

        # # Republish the plugin
        category_page.publish("en")

        # Now modify the organization to have a draft different from the public version
        title_obj = published_organization.extended_object.get_title_obj(language="en")
        title_obj.title = "draft title"
        title_obj.save()

        # Publishing the page again should make the plugin public
        page.publish("en")

        # Check the page content in English
        response = self.client.get(url)
        # The organization's name should be present as a link to the cms page
        # And CMS page title should be in title attribute of the link
        self.assertIn(
            '<a class="organization-glimpse" '
            'href="/en/public-title/" title="public title"',
            re.sub(" +", " ", str(response.content).replace("\\n", "")),
        )

        # The organization's title should be wrapped in a div
        self.assertContains(
            response,
            '<div class="organization-glimpse__title">{:s}</div>'.format(
                published_organization.public_extension.extended_object.get_title()
            ),
            html=True,
        )
        self.assertNotContains(response, "draft")
        self.assertNotContains(response, "private")

        # Organization's logo should be present
        pattern = (
            r'<div class="organization-glimpse__logo">'
            r'<img src="/media/filer_public_thumbnails/filer_public/.*logo\.jpg__200x113'
            r'.*alt=""'
        )
        self.assertIsNotNone(re.search(pattern, str(response.content)))

        # Same checks in French
        url = page.get_absolute_url(language="fr")
        response = self.client.get(url)
        self.assertIn(
            '<a class="organization-glimpse" '
            'href="/fr/titre-public/" title="titre public"',
            re.sub(" +", " ", str(response.content).replace("\\n", "")),
        )
        pattern = (
            r'<div class="organization-glimpse__logo">'
            r'<img src="/media/filer_public_thumbnails/filer_public/.*logo\.jpg__200x113'
            r'.*alt=""'
        )
        self.assertIsNotNone(re.search(pattern, str(response.content)))
示例#30
0
    def test_templates_course_detail_cms_draft_content(self):
        """
        A staff user should see a draft course including its draft elements with
        an annotation
        """
        user = UserFactory(is_staff=True, is_superuser=True)
        self.client.login(username=user.username, password="******")

        categories = CategoryFactory.create_batch(4)
        organizations = OrganizationFactory.create_batch(4)

        course = CourseFactory(
            page_title="Very interesting course",
            fill_organizations=organizations,
            fill_categories=categories,
        )
        page = course.extended_object
        now = timezone.now()
        CourseRunFactory(
            direct_course=course,
            start=now - timedelta(hours=1),
            end=now + timedelta(hours=2),
            enrollment_end=now + timedelta(hours=1),
            languages=["en", "fr"],
        )

        # Publish only 2 out of 4 categories and 2 out of 4 organizations
        self.assertTrue(categories[0].extended_object.publish("en"))
        self.assertTrue(categories[1].extended_object.publish("en"))
        self.assertTrue(organizations[0].extended_object.publish("en"))
        self.assertTrue(organizations[1].extended_object.publish("en"))

        # The unpublished objects may have been published and unpublished which puts them in a
        # status different from objects that have never been published.
        # We want to test both cases.
        self.assertTrue(categories[2].extended_object.publish("en"))
        self.assertTrue(categories[2].extended_object.unpublish("en"))
        self.assertTrue(organizations[2].extended_object.publish("en"))
        self.assertTrue(organizations[2].extended_object.unpublish("en"))

        # The page should be visible as draft to the staff user
        url = page.get_absolute_url()
        response = self.client.get(url)

        self.assertEqual(response.status_code, 200)

        self.assertContains(response,
                            "<title>Very interesting course</title>",
                            html=True)
        self.assertContains(
            response,
            '<h1 class="subheader__title">Very interesting course</h1>',
            html=True,
        )

        # Draft and public organizations should all be present on the page
        for organization in organizations:
            self.assertContains(
                response,
                '<div class="organization-glimpse__title">{title:s}</div>'.
                format(title=organization.extended_object.get_title()),
                html=True,
            )

        # Draft organizations should be annotated for styling
        self.assertContains(response, "organization-glimpse--draft", count=2)

        # The published categories should be present on the page
        for category in categories[:2]:
            self.assertContains(
                response,
                ('<a class="category-badge" href="{:s}">'
                 '<span class="category-badge__title">{:s}</span></a>').format(
                     category.extended_object.get_absolute_url(),
                     category.extended_object.get_title(),
                 ),
                html=True,
            )
        # Draft categories should also be present on the page with an annotation for styling
        for category in categories[-2:]:
            self.assertContains(
                response,
                ('<a class="{element:s} {element:s}--draft" href="{url:s}">'
                 '<span class="category-badge__title">{title:s}</span></a>'
                 ).format(
                     url=category.extended_object.get_absolute_url(),
                     element="category-badge",
                     title=category.extended_object.get_title(),
                 ),
                html=True,
            )
        # The course run should be in the page
        self.assertContains(response,
                            "<dd>English and french</dd>",
                            html=True,
                            count=1)