def test_models_course_get_organizations(self): """ The `get_organizations` method should return all organizations linked to a course and should respect publication status. """ # The 2 first organizations are grouped in one variable name and will be linked to the # course in the following, the third category will not be linked so we can check that # only the organizations linked to the course are retrieved (its name starts with `_` # because it is not used and only here for unpacking purposes) *draft_organizations, _other_draft = OrganizationFactory.create_batch( 3) *published_organizations, _other_public = OrganizationFactory.create_batch( 3, should_publish=True) course = CourseFactory( fill_organizations=draft_organizations + published_organizations, should_publish=True, ) self.assertEqual( list(course.get_organizations()), draft_organizations + published_organizations, ) self.assertEqual(list(course.public_extension.get_organizations()), published_organizations)
def test_cms_toolbars_organization_has_page_extension_settings_item(self): """ Validate that a new item to edit the organization is available only when visiting the page in edit mode and for users with permission to edit the page. """ organization = OrganizationFactory() url = "/en/admin/courses/organization/{id:d}/change/".format( id=organization.id) for args, method in self.get_cases_for_page_change(): toolbar = self.get_toolbar_for_page(organization.extended_object, *args) item = method(toolbar, "Organization settings...") if item: self.assertEqual(item.url, url)
def test_models_organization_get_organizations_codes_with_2_orgs(self): """ Check if the get_organizations_codes works for an organization page. """ org_page_code = "ORG_XPTO_1" organization = OrganizationFactory(should_publish=True, code=org_page_code) organization_page = organization.extended_object organization_codes = list( Organization.get_organizations_codes(organization_page, "en")) self.assertListEqual( organization_codes, [org_page_code], )
def test_indexers_courses_get_es_documents_unpublished_organization(self): """Unpublished organizations should not be indexed.""" organization = OrganizationFactory(should_publish=True) CourseFactory(fill_organizations=[organization], should_publish=True) # Unpublish the organization self.assertTrue(organization.extended_object.unpublish("en")) course_document = list( CoursesIndexer.get_es_documents(index="some_index", action="some_action"))[0] # The unpublished organization should not be linked to the course self.assertEqual(course_document["organizations"], []) self.assertEqual(course_document["organizations_names"], {})
def test_cms_wizards_course_create_wizards_list_insufficient_permissions(self, *_): """ The wizard to create a new course page should not be present on the wizards list page for a user with insufficient permissions. """ organization = OrganizationFactory() required_permissions = ["courses.add_course", "cms.add_page", "cms.change_page"] required_page_permissions = ["can_add", "can_change"] url = "{:s}?page={:d}".format( reverse("cms_wizard_create"), organization.extended_object_id ) for permission_to_be_removed in required_permissions + [None]: for page_permission_to_be_removed in required_page_permissions + [None]: if ( permission_to_be_removed is None and page_permission_to_be_removed is None ): # This is the case of sufficient permissions treated in the next test continue altered_permissions = required_permissions.copy() if permission_to_be_removed: altered_permissions.remove(permission_to_be_removed) altered_page_permissions = required_page_permissions.copy() if page_permission_to_be_removed: altered_page_permissions.remove(page_permission_to_be_removed) user = UserFactory(is_staff=True, permissions=altered_permissions) PagePermission.objects.create( page=organization.extended_object, user=user, can_add="can_add" in altered_page_permissions, can_change="can_change" in altered_page_permissions, can_delete=False, can_publish=False, can_move_page=False, ) self.client.login(username=user.username, password="******") # Let the authorized user get the page with all wizards listed response = self.client.get(url) # Check that our wizard to create courses is not on this page self.assertNotContains(response, "course", status_code=200, html=True)
def test_templates_organization_detail_cms_published_content(self): """ Validate that the important elements are displayed on a published organization page """ courses = CourseFactory.create_batch(4) organization = OrganizationFactory(page_title="La Sorbonne", fill_courses=courses) page = organization.extended_object # Publish only 2 out of 4 courses courses[0].extended_object.publish("en") courses[1].extended_object.publish("en") # The unpublished objects may have been published and unpublished which puts them in a # status different from objects that have never been published. # We want to test both cases. courses[2].extended_object.publish("en") courses[2].extended_object.unpublish("en") # The page should not be visible before it is published url = page.get_absolute_url() response = self.client.get(url) self.assertEqual(response.status_code, 404) # Publish the organization and ensure the content is correct page.publish("en") 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, ) # Only published courses should be present on the page for course in courses[:2]: self.assertContains( response, '<p class="course-glimpse__content__title">{:s}</p>'.format( course.extended_object.get_title()), html=True, ) for course in courses[-2:]: self.assertNotContains(response, course.extended_object.get_title())
def test_templates_organization_detail_open_graph_description_empty(self): """ The opengraph description should not be present if the excerpt placeholder isn't filled """ organization = OrganizationFactory() page = organization.extended_object page.publish("en") url = organization.extended_object.get_absolute_url() response = self.client.get(url) self.assertEqual(response.status_code, 200) self.assertNotContains( response, "og:description", )
def test_templates_organization_detail_meta_description_empty(self): """ The organization meta description should not be present if neither the meta_description field on the page, nor the excerpt placeholder are filled """ organization = OrganizationFactory() page = organization.extended_object page.publish("en") url = organization.extended_object.get_absolute_url() response = self.client.get(url) self.assertEqual(response.status_code, 200) self.assertNotContains( response, '<meta name="description"', )
def test_web_analytics_enabled_without_google_analytics(self): """ Test Web Analytics with a organization page using a custom provider. Can test the html because the information is only on the template context. """ org_page_code = "PUBLIC_ORG" org_page_title = "public title" # Create an Organization organization = OrganizationFactory(page_title=org_page_title, should_publish=True, code=org_page_code) course_page_code = "XPTO_CODE_FOR_COURSE" course_page_title = { "en": "A fancy title for a course", "fr": "Un titre fantaisiste pour un cours", } course = CourseFactory( page_title=course_page_title, fill_organizations=[organization], code=course_page_code, ) course_page = course.extended_object # publish after course run creation so the course run is also published course_page.publish("fr") course_page.publish("en") url = course_page.get_absolute_url(language="fr") request = RequestFactory().get(url) request.current_page = course_page context = WebAnalyticsContextProcessor().context_processor(request) dimensions = context["WEB_ANALYTICS_DIMENSIONS"] self.assertListEqual(list(dimensions["organizations_codes"]), ["PUBLIC_ORG"]) self.assertListEqual(dimensions["course_code"], ["XPTO_CODE_FOR_COURSE"]) self.assertListEqual(dimensions["course_runs_titles"], []) self.assertListEqual(list(dimensions["course_runs_resource_links"]), []) self.assertListEqual(dimensions["page_title"], ["A fancy title for a course"])
def test_context_processors_get_organizations_code(self): """ If an organization is linked to the page or there are organization plugins on the page, marketing context should contains the code of these organizations """ organizations = OrganizationFactory.create_batch(2, should_publish=True) course = CourseFactory(should_publish=True, fill_organizations=organizations) response = self.client.get(course.extended_object.get_public_url(), follow=True) organizations_codes = Organization.get_organizations_codes( course.extended_object, "fr") pattern = fr'"organizations_codes": "{" | ".join(list(organizations_codes))}"' self.assertEqual(response.status_code, 200) self.assertIsNotNone(re.search(pattern, str(response.content)))
def test_admin_organization_change_view_get(self): """ The admin change view should work for organizations """ user = UserFactory(is_staff=True, is_superuser=True) self.client.login(username=user.username, password="******") # Create an organization linked to a page page = create_page("My title", "richie/fullwidth.html", "en") organization = OrganizationFactory(extended_object=page) # Get the admin change view url = reverse("admin:courses_organization_change", args=[organization.id]) response = self.client.get(url) # Check that the page includes all our fields self.assertContains(response, "My title", status_code=200) self.assertContains(response, organization.code)
def test_cms_wizards_course_submit_form_slugify_long_title(self): """ When generating the slug from the title, we should respect the slug's "max_length" """ # An organization and a parent page should pre-exist organization = OrganizationFactory() create_page("Courses", "richie/fullwidth.html", "en", reverse_id=Course.ROOT_REVERSE_ID) # Submit a title at max length data = {"title": "t" * 255, "organization": organization.id} form = CourseWizardForm(data=data) self.assertTrue(form.is_valid()) page = form.save() # Check that the slug has been truncated self.assertEqual(page.get_slug(), "t" * 200)
def test_cms_plugins_organizations_by_category_render_on_draft_page(self): """ The organization 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 organizations organization = OrganizationFactory( page_title={"en": "public title", "fr": "titre public"}, fill_categories=[category], fill_logo={"original_filename": "logo.jpg"}, ) # Create a page to add the plugin to page = create_i18n_page("A page") placeholder = page.placeholders.get(slot="maincontent") add_plugin( placeholder, OrganizationsByCategoryPlugin, "en", **{"page": category_page} ) category_page.publish("en") category_page.unpublish("en") category_page.refresh_from_db() url = "{:s}?edit".format(page.get_absolute_url(language="en")) # The organization plugin should still be visible on the draft page response = self.client.get(url) self.assertContains(response, "public title") # Now modify the organization to have a draft different from the public version title_obj = organization.extended_object.get_title_obj(language="en") title_obj.title = "draft title" title_obj.save() # The draft version of the organization plugin should now be visible response = self.client.get(url) self.assertContains(response, "draft title") self.assertNotContains(response, "public title")
def test_web_analytics_organization_page(self): """ Test Web Analytics with a organization page on js on html head """ org_page_code = "PUBLIC_ORG" org_page_title = "public title" # Create an Organization organization = OrganizationFactory(page_title=org_page_title, should_publish=True, code=org_page_code) page = organization.extended_object url = page.get_absolute_url(language="en") response = self.client.get(url) self.assertContains( response, "google-analytics.com", msg_prefix="Page should include the Google Analytics js code", ) self.assertContains( response, "UA-XXXXXXXXX-X", msg_prefix= "Page should include the Google Analytics tracking id code", ) self.assertRegex( response.content.decode("UTF-8"), "dimension1.*" + org_page_code, msg= "Google Analytics should include organization code on the first custom dimension", ) self.assertRegex( response.content.decode("UTF-8"), "dimension5.*" + org_page_title, msg= "Google Analytics should include page title on the 5th custom dimension", ) response_content = response.content.decode("UTF-8") self.assertGreater( response_content.index("<body"), response_content.index("google-analytics.com"), "Web tracking should be at the bottom of the page", )
def test_cms_plugins_organization_form_page_choices(self): """ The form to create a organization plugin should only list organization pages in the select box. """ class OrganizationPluginModelForm(forms.ModelForm): """A form for testing the choices in the select box""" class Meta: model = OrganizationPluginModel fields = ["page"] organization = OrganizationFactory() other_page_title = "other page" create_page(other_page_title, "richie/fullwidth.html", settings.LANGUAGE_CODE) plugin_form = OrganizationPluginModelForm() self.assertIn(organization.extended_object.get_title(), plugin_form.as_table()) self.assertNotIn(other_page_title, plugin_form.as_table())
def test_cms_wizards_course_parent_page_should_exist(self): """ We should not be able to create a course page if the parent page does not exist """ organization = OrganizationFactory() form = CourseWizardForm(data={ "title": "My title", "organization": organization.id }) self.assertFalse(form.is_valid()) self.assertEqual( form.errors, { "slug": [ "You must first create a parent page and set its `reverse_id` to `courses`." ] }, )
def test_views_redirect_edx_courses_fallback_organization(self): """ OpenEdX course urls are redirected to the organization page if the course page can not be found. """ OrganizationFactory(page_title="Sorbonne", code="sorbonne", should_publish=True) response = self.client.get( "/courses/course-v1:sorbonne+abc+001/about/") self.assertRedirects( response, "/fr/sorbonne/", status_code=301, target_status_code=200, fetch_redirect_response=True, )
def test_indexers_organizations_get_es_documents_language_fallback(self): """Absolute urls should be computed as expected with language fallback.""" OrganizationFactory( page_title={ "fr": "ma première organisation", }, should_publish=True, ) indexed_organizations = list( OrganizationsIndexer.get_es_documents(index="some_index", action="some_action")) self.assertEqual( indexed_organizations[0]["absolute_url"], { "en": "/en/ma-premiere-organisation/", "fr": "/fr/ma-premiere-organisation/", }, )
def test_course_change_view_post(self): """ Validate that the course can be updated via the admin. In particular, make sure that when a course is updated from the admin, the main organization is automatically added to the many-to-many field "organizations". See http://stackoverflow.com/a/1925784/469575 for details on the issue. """ user = UserFactory(is_staff=True, is_superuser=True) self.client.login(username=user.username, password="******") # Create a course, some organizations and some subjects organization1, organization2, organization3 = OrganizationFactory.create_batch( 3 ) subject1, subject2 = SubjectFactory.create_batch(2) course = CourseFactory( with_organizations=[organization1], with_subjects=[subject1] ) self.assertEqual( set(course.organizations.all()), {organization1, course.organization_main} ) self.assertEqual(set(course.subjects.all()), {subject1}) # Get the admin change view url = reverse("admin:courses_course_change", args=[course.id]) data = { "active_session": "xyz", "organization_main": organization2.id, "organizations": [organization3.id], "subjects": [subject2.id], } response = self.client.post(url, data) self.assertEqual(response.status_code, 302) # Check that the course was updated as expected course.refresh_from_db() self.assertEqual(course.active_session, "xyz") self.assertEqual(course.organization_main, organization2) self.assertEqual(set(course.subjects.all()), {subject2}) # Check that the main organization was added and the old organization cleared self.assertEqual( set(course.organizations.all()), {organization2, organization3} )
def test_cms_wizards_course_submit_form_from_organization_page_no_role( self, *_): """ Creating a course via the wizard should not fail if the organization has no associated page role. """ # A parent page should pre-exist create_page( "Courses", "richie/single_column.html", "en", reverse_id=Course.PAGE["reverse_id"], ) organization = OrganizationFactory() user = UserFactory(is_staff=True, is_superuser=True) form = CourseWizardForm( data={"title": "My title"}, wizard_language="en", wizard_user=user, wizard_page=organization.extended_object, ) self.assertTrue(form.is_valid()) page = form.save() course = page.course # The course and its related page should have been created as draft Page.objects.drafts().get(id=page.id) Course.objects.get(id=course.id, extended_object=page) self.assertEqual(page.get_title(), "My title") # The slug should have been automatically set self.assertEqual(page.get_slug(), "my-title") # The course should have a plugin with the organization self.assertEqual(OrganizationPluginModel.objects.count(), 1) plugin = OrganizationPluginModel.objects.first() self.assertEqual(plugin.page_id, organization.extended_object_id) # No other permissions should have been created self.assertFalse( PagePermission.objects.filter( page=organization.extended_object).exists())
def test_context_processors_queries_are_cached(self): """ Once the page is cached, no db queries should be made again """ organizations = OrganizationFactory.create_batch(2, should_publish=True, page_languages=["fr"]) course = CourseFactory(should_publish=True, fill_organizations=organizations, page_languages=["fr"]) page = course.extended_object # Get the page a first time to cache it self.client.get(page.get_public_url()) # Check that db queries are well cached # The one remaining is related to django-cms with self.assertNumQueries(1): self.client.get(page.get_public_url())
def test_factories_organization_description(self): """ OrganizationFactory should be able to generate plugins with a realistic description for several languages. """ organization = OrganizationFactory(page_languages=["fr", "en"], fill_description=True) # Check that the description plugins were created as expected description = organization.extended_object.placeholders.get( slot="description") self.assertEqual(description.cmsplugin_set.count(), 2) # The description plugins should contain paragraphs for language in ["fr", "en"]: description_plugin = description.cmsplugin_set.get( plugin_type="CKEditorPlugin", language=language) self.assertIn( "<p>", description_plugin.simple_text_ckeditor_simpletext.body)
def test_cms_wizards_course_create_wizards_list_user_with_permissions( self, *_): """ The wizard to create a new course page should be present on the wizards list page for a user with the required permissions visiting an organization page that he can change. """ organization = OrganizationFactory() # Login with a user with just the required permissions user = UserFactory( is_staff=True, permissions=[ "courses.add_course", "cms.add_page", "cms.change_page" ], ) PagePermission.objects.create( page=organization.extended_object, user=user, can_add=True, can_change=True, can_delete=False, can_publish=False, can_move_page=False, ) self.client.login(username=user.username, password="******") # Let the authorized user get the page with all wizards listed url = "{:s}?page={:d}".format(reverse("cms_wizard_create"), organization.extended_object_id) response = self.client.get(url) # Check that our wizard to create courses is on this page self.assertContains( response, '<span class="info">Create a new course page</span>', status_code=200, html=True, ) self.assertContains(response, "<strong>New course page</strong>", html=True)
def test_cms_plugins_organization_render_instance_variant(self): """ The organization plugin should render according to variant variable eventually present in the context of its container. """ staff = UserFactory(is_staff=True, is_superuser=True) self.client.login(username=staff.username, password="******") # Create an Organization organization = OrganizationFactory(page_title="public title", should_publish=True) organization_page = organization.extended_object # Create a page to add the plugin to page = create_i18n_page("A page") placeholder = page.placeholders.get(slot="maincontent") # Add organization plugin with default variant add_plugin(placeholder, OrganizationPlugin, "en", page=organization_page) page_url = page.get_absolute_url(language="en") url = f"{page_url:s}?edit" # The organization-glimpse default variant should not have the small attribute response = self.client.get(url) self.assertNotContains(response, "--small") # Add organization plugin with small variant add_plugin( placeholder, OrganizationPlugin, "en", page=organization_page, variant="small", ) # The new organization-glimpse should have the small attribute response = self.client.get(url) self.assertContains(response, "organization-small")
def test_organization_courses_copied_when_publishing(self): """ When publishing a organization, the links to draft courses on the draft version of the organization should be copied (clear then add) to the published version. Links to published courses should not be copied as they are redundant and not up-to-date. """ # Create draft courses course1, course2 = CourseFactory.create_batch(2) # Create a draft organization draft_organization = OrganizationFactory( with_courses=[course1, course2]) # Publish course1 course1.extended_object.publish("en") course1.refresh_from_db() # The draft organization should see all courses and propose a custom filter to easily # access the draft versions self.assertEqual( set(draft_organization.courses.all()), {course1, course1.public_extension, course2}, ) self.assertEqual(set(draft_organization.courses.drafts()), {course1, course2}) # Publish the organization and check that the courses are copied draft_organization.extended_object.publish("en") published_organization = Organization.objects.get( extended_object__publisher_is_draft=False) self.assertEqual(set(published_organization.courses.all()), {course1, course2}) # When publishing, the courses that are obsolete should be cleared draft_organization.courses.remove(course2) self.assertEqual(set(published_organization.courses.all()), {course1, course2}) # courses on the published organization are only cleared after publishing the draft page draft_organization.extended_object.publish("en") self.assertEqual(set(published_organization.courses.all()), {course1})
def test_cms_plugins_organization_form_page_choices(self): """ The form to create a organization plugin should only list organization pages in the select box. There shouldn't be any duplicate because of published status. """ class OrganizationPluginModelForm(forms.ModelForm): """A form for testing the choices in the select box""" class Meta: model = OrganizationPluginModel fields = ["page"] organization = OrganizationFactory(should_publish=True) other_page_title = "other page" create_page(other_page_title, "richie/single_column.html", settings.LANGUAGE_CODE) plugin_form = OrganizationPluginModelForm() rendered_form = plugin_form.as_table() self.assertEqual( rendered_form.count(organization.extended_object.get_title()), 1) self.assertNotIn(other_page_title, plugin_form.as_table())
def create_filter_pages(self): """Create pages for each filter based on an indexable.""" if not self.__filter_pages__: self.__filter_pages__ = { "levels": CategoryFactory( page_reverse_id="levels", page_title="Levels", should_publish=True ), "subjects": CategoryFactory( page_reverse_id="subjects", page_title="Subjects", should_publish=True, ), "organizations": OrganizationFactory( page_reverse_id="organizations", page_title="Organizations", should_publish=True, ), } return self.__filter_pages__
def test_organization_list_view(self): """ The organizations admin list view should display their code and the title of the related page """ user = UserFactory(is_staff=True, is_superuser=True) self.client.login(username=user.username, password="******") # Create an organization linked to a page organization = OrganizationFactory() # Get the admin list view url = reverse("admin:courses_organization_changelist") response = self.client.get(url, follow=True) # Check that the page includes all our fields self.assertContains(response, organization.extended_object.get_title(), status_code=200) self.assertContains(response, organization.code)
def test_factories_organization_banner(self): """ OrganizationFactory should be able to generate plugins with a realistic banner for several languages. """ organization = OrganizationFactory(page_languages=["fr", "en"], fill_banner=True) # Check that the banner plugins were created as expected banner = organization.extended_object.placeholders.get(slot="banner") self.assertEqual(banner.cmsplugin_set.count(), 2) # The banner plugins should point to one of our fixtures images for language in ["fr", "en"]: banner_plugin = banner.cmsplugin_set.get( plugin_type="PicturePlugin", language=language) self.assertIn( "banner", os.path.basename( banner_plugin.djangocms_picture_picture.picture.file.name), )
def test_course_organization_main_always_included_in_organizations(self): """ The main organization should always be in the organizations linked via many-to-many """ organization1, organization2 = OrganizationFactory.create_batch(2) course = CourseFactory(organization_main=organization1) self.assertEqual(list(course.organizations.all()), [organization1]) # Now set the second organization as the main course.organization_main = organization2 course.save() self.assertEqual(course.organization_main, organization2) self.assertEqual(list(course.organizations.all()), [organization1, organization2]) # Setting an organization that is already included as many-to-many should not fail course.organization_main = organization1 course.save() self.assertEqual(course.organization_main, organization1) self.assertEqual(list(course.organizations.all()), [organization1, organization2])