def test_views_bootstrap_elasticsearch_with_permission(self, mock_command): """Confirm triggering the search index bootstrapping works as expected.""" user = UserFactory(is_staff=True) self.client.login(username=user.username, password="******") # Add the necessary permission self.add_permission(user, "can_manage_elasticsearch") url = "/api/v1.0/bootstrap-elasticsearch/" response = self.client.post(url, follow=True) self.assertEqual(response.status_code, 200) content = json.loads(response.content) self.assertEqual(content, {}) # Check the presence of a confirmation message messages = list(get_messages(response.wsgi_request)) self.assertEqual(len(messages), 1) self.assertEqual(str(messages[0]), "The search index was successfully bootstrapped") mock_command.assert_called_once_with("bootstrap_elasticsearch")
def test_cms_wizards_person_create_wizards_list(self): """ The wizard to create a new person page should be present on the wizards list page """ user = UserFactory(is_staff=True, is_superuser=True) self.client.login(username=user.username, password="******") # Let the authorized user get the page with all wizards listed url = reverse("cms_wizard_create") response = self.client.get(url) # Check that our wizard to create persons is on this page self.assertContains( response, '<span class="info">Create a new person page</span>', status_code=200, html=True, ) self.assertContains(response, "<strong>New person page</strong>", html=True)
def test_cms_wizards_course_run_create_wizards_list_superuser_snapshot( self, *_): """ The wizard to create a new course run page should not be present on the wizards list page for a superuser visiting a course snapshot page. """ snapshot = CourseFactory() CourseFactory(page_parent=snapshot.extended_object) user = UserFactory(is_staff=True, is_superuser=True) 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"), snapshot.extended_object.id) response = self.client.get(url) # Check that our wizard to create course runs is not on this page self.assertNotContains(response, "new course run", status_code=200, html=True)
def test_templates_course_detail_organization_main_logo(self): """The main organization logo should be present on the page with a link.""" user = UserFactory(is_staff=True, is_superuser=True) self.client.login(username=user.username, password="******") organizations = OrganizationFactory.create_batch(2, fill_logo=True, should_publish=True) course = CourseFactory(fill_organizations=organizations) response = self.client.get(course.extended_object.get_absolute_url()) self.assertEqual(response.status_code, 200) pattern = ( r'<a href="{url:s}" title="{title:s}" class="course-detail__aside__main-org-logo">' r'<img src="/media/filer_public_thumbnails/filer_public/.*logo\.jpg__200x113' ).format( url=organizations[0].extended_object.get_absolute_url(), title=organizations[0].extended_object.get_title(), ) self.assertIsNotNone(re.search(pattern, str(response.content)))
def test_templates_person_detail_maincontent_empty(self): """ The "maincontent" placeholder block should not be displayed on the public page when empty but only on the draft version for staff. """ person = PersonFactory(should_publish=True) # The "organizations" section should not be present on the public page url = person.public_extension.extended_object.get_absolute_url() response = self.client.get(url) self.assertContains(response, person.extended_object.get_title()) self.assertNotContains(response, "person-detail__maincontent") # But it should be present on the draft page user = UserFactory(is_staff=True, is_superuser=True) self.client.login(username=user.username, password="******") url = person.extended_object.get_absolute_url() response = self.client.get(url) self.assertContains(response, person.extended_object.get_title()) self.assertContains(response, "person-detail__maincontent")
def test_cms_wizards_course_run_language_active(self, *_): """ The language should be set to the active language. """ course = CourseFactory() # Submit a valid form user = UserFactory(is_staff=True, is_superuser=True) form = CourseRunWizardForm( data={"title": "My title"}, wizard_language="en", wizard_user=user, wizard_page=course.extended_object, ) self.assertTrue(form.is_valid()) with translation.override("fr"): page = form.save() # The language field should have been set to the active language self.assertEqual(page.courserun.languages, ["fr"])
def test_cms_wizards_category_create_wizards_list_superuser(self): """ The wizard to create a new category page should be present on the wizards list page for a superuser. """ page = create_page("page", "richie/single_column.html", "en") user = UserFactory(is_staff=True, is_superuser=True) 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"), page.id) response = self.client.get(url) # Check that our wizard to create categories is on this page self.assertContains( response, '<span class="info">Create a new category page</span>', status_code=200, html=True, ) self.assertContains(response, "<strong>New category page</strong>", html=True)
def test_cms_wizards_category_submit_form_invalid_slug(self): """Trying to submit a slug that is not valid should raise a 400 exception.""" # A parent page should pre-exist parent_page = create_page( "Categories", "richie/single_column.html", "en", reverse_id=Category.PAGE["reverse_id"], ) # Submit an invalid slug data = {"title": "my title", "slug": "invalid slug"} user = UserFactory(is_superuser=True, is_staff=True) form = CategoryWizardForm(data=data, wizard_language="en", wizard_user=user) form.page = parent_page self.assertFalse(form.is_valid()) self.assertEqual( form.errors["slug"][0], "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens.", )
def test_cms_wizards_category_submit_form_max_lengths(self): """ Check that the form correctly raises an error when the slug is too long. The path built by combining the slug of the page with the slug of its parent page, should not exceed 255 characters in length. """ # A parent page with a very long slug page = create_page( "y" * 200, "richie/single_column.html", "en", reverse_id=Category.PAGE["reverse_id"], ) # A category with a slug at the limit length should work user = UserFactory(is_staff=True, is_superuser=True) form = CategoryWizardForm( data={"title": "t" * 255, "slug": "s" * 54}, wizard_language="en", wizard_user=user, wizard_page=page, ) self.assertTrue(form.is_valid()) form.save() # A category with a slug too long with regards to the parent's one should raise an error form = CategoryWizardForm( data={"title": "t" * 255, "slug": "s" * 55}, wizard_language="en", wizard_user=user, wizard_page=page, ) self.assertFalse(form.is_valid()) self.assertEqual( form.errors["slug"][0], ( "This slug is too long. The length of the path built by prepending the slug of " "the parent page would be 256 characters long and it should be less than 255" ), )
def test_cms_plugins_organization_render_instance_variant(self): """ The organization plugin should render according to the variant plugin option. """ staff = UserFactory(is_staff=True, is_superuser=True) self.client.login(username=staff.username, password="******") # Create an Organization organization = OrganizationFactory(page_title="public title") 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) url = "{:s}?edit".format(page.get_absolute_url(language="en")) # 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_admin_form_course_run_choices_staff_permissions(self): """ Staff users should only see course pages on which they have change permission. Unless CMS permissions are not activated. """ course1 = CourseFactory(page_title="Title 1", should_publish=True) course2 = CourseFactory(page_title="Title 2", should_publish=True) course3 = CourseFactory(page_title="Title 3", should_publish=True) user = UserFactory(is_staff=True) # Add permission only for courses 2 and 3 self.add_permission(user, "change_page") for course in [course2, course3]: PagePermission.objects.create( page=course.extended_object, user=user, can_add=False, can_change=True, can_delete=False, can_publish=False, can_move_page=False, ) # The user should only see the 2 courses on which he has permissions form = self._get_admin_form(course2, user) self.assertEqual(len(form.fields["direct_course"].choices), 3) html = form.as_ul() self.assertIn('<option value="">---------</option>', html) self.assertNotIn("Title 1", html) self.assertIn( f'<option value="{course2.id:d}" selected>Title 2</option>', html) self.assertIn(f'<option value="{course3.id:d}">Title 3</option>', html) # Unless CMS permissions are not activated with override_settings(CMS_PERMISSION=False): form = self._get_admin_form(course2, user) self.assertEqual(len(form.fields["direct_course"].choices), 4) self.assertIn(f'<option value="{course1.id:d}">Title 1</option>', form.as_ul())
def test_templates_course_detail_placeholder(self): """ Draft editing course page should contain all key placeholders when empty. """ user = UserFactory(is_staff=True, is_superuser=True) self.client.login(username=user.username, password="******") course = CourseFactory(page_title="Very interesting course") page = course.extended_object url = "{:s}?edit".format(page.get_absolute_url(language="en")) response = self.client.get(url) pattern = ( r'<div class="course-detail__row course-detail__introduction">' r'<div class="cms-placeholder') self.assertIsNotNone(re.search(pattern, str(response.content))) pattern = (r'<div class="category-badge-list__container">' r'<div class="cms-placeholder') self.assertIsNotNone(re.search(pattern, str(response.content))) pattern = r'<div class="subheader__teaser"><div class="cms-placeholder' self.assertIsNotNone(re.search(pattern, str(response.content))) pattern = ( r'<div class="subheader__content subheader__content--aside">' r'<div class="cms-placeholder') self.assertIsNotNone(re.search(pattern, str(response.content))) pattern = ( r'<div class="section__items section__items--organizations">' r'<div class="cms-placeholder') self.assertIsNotNone(re.search(pattern, str(response.content))) pattern = (r'<div class="section__items section__items--team">' r'<div class="cms-placeholder') self.assertIsNotNone(re.search(pattern, str(response.content))) pattern = ( r'<div class="course-detail__row course-detail__information">' r'<div class="cms-placeholder') self.assertIsNotNone(re.search(pattern, str(response.content))) pattern = ( r'<h3 class="course-detail__label">' r'License for the course content</h3><div class="cms-placeholder') self.assertIsNotNone(re.search(pattern, str(response.content)))
def test_cms_wizards_blogpost_submit_form(self): """ A user with the required permissions submitting a valid BlogPostWizardForm should be able to create a BlogPost page extension and its related page. """ any_page = create_page("Any page", "richie/single_column.html", "en") # A parent page should pre-exist create_page( "News", "richie/single_column.html", "en", reverse_id=BlogPost.PAGE["reverse_id"], ) # Create a user with just the required permissions user = UserFactory( is_staff=True, permissions=["courses.add_blogpost", "cms.add_page", "cms.change_page"], ) # We can submit a form with just the title set form = BlogPostWizardForm( data={"title": "My title"}, wizard_language="en", wizard_user=user, wizard_page=any_page, ) self.assertTrue(form.is_valid()) blog_page = form.save() # Related page should have been created as draft Page.objects.drafts().get(id=blog_page.id) BlogPost.objects.get(id=blog_page.blogpost.id, extended_object=blog_page) self.assertEqual(blog_page.get_title(), "My title") # The slug should have been automatically set self.assertEqual(blog_page.get_slug(), "my-title") # The page should be in navigation to appear in the breadcrumb self.assertTrue(blog_page.in_navigation)
def test_templates_category_list_cms_content(self): """ Validate that the public website only displays categories that are currently published, while staff users can see draft and unpublished categories. """ page = PageFactory( template="courses/cms/category_list.html", title__language="en", should_publish=True, ) CategoryFactory(page_parent=page, page_title="First category") CategoryFactory(page_parent=page, page_title="Second category", should_publish=True) # Publish with a publication date in the future future = timezone.now() + timedelta(hours=1) with mock.patch("cms.models.pagemodel.now", return_value=future): CategoryFactory(page_parent=page, page_title="Third category", should_publish=True) # Anonymous users should only see published categories response = self.client.get(page.get_absolute_url()) self.assertEqual(response.status_code, 200) self.assertNotContains(response, "First") self.assertContains(response, "Second") self.assertNotContains(response, "Third") # Staff users can see draft and unpublished categories user = UserFactory(is_staff=True, is_superuser=True) self.client.login(username=user.username, password="******") response = self.client.get(page.get_absolute_url()) self.assertEqual(response.status_code, 200) for title in ["First", "Second", "Third"]: self.assertContains(response, title)
def test_cms_wizards_blogpost_submit_form_insufficient_permission(self): """ A user with insufficient permissions trying to submit a BlogPostWizardForm should trigger a PermissionDenied exception. We make loop to remove each time only one permission from the set of required permissions and check that they are all required. """ any_page = create_page("Any page", "richie/single_column.html", "en") # A parent page should pre-exist create_page( "News", "richie/single_column.html", "en", reverse_id=BlogPost.PAGE["reverse_id"], ) required_permissions = ["courses.add_blogpost"] for is_staff in [True, False]: for permission_to_be_removed in required_permissions + [None]: if is_staff is True and 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) user = UserFactory(is_staff=is_staff, permissions=altered_permissions) form = BlogPostWizardForm( data={"title": "My title"}, wizard_language="en", wizard_user=user, wizard_page=any_page, ) with self.assertRaises(PermissionDenied): form.is_valid()
def test_admin_person_list_view(self): """ The admin list view of persons should display page title, person's title first_name and last_name """ user = UserFactory(is_staff=True, is_superuser=True) self.client.login(username=user.username, password="******") # Create a person linked to a page person = PersonFactory() # Get the admin list view url = reverse("admin:persons_person_changelist") response = self.client.get(url) # Check that the page includes all our fields self.assertContains(response, person.extended_object.get_title(), status_code=200) self.assertContains(response, person.first_name) self.assertContains(response, person.last_name) self.assertContains(response, person.person_title)
def test_cms_wizards_blogpost_submit_form_slug_duplicate(self): """ Trying to create a blog post with a slug that would lead to a duplicate path should raise a validation error. """ # A parent page should pre-exist parent_page = create_page( "News", "richie/single_column.html", "en", reverse_id=BlogPost.PAGE["reverse_id"], ) # Create an existing page with a known slug BlogPostFactory(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 = BlogPostWizardForm(data=data, wizard_language="en", wizard_user=user) self.assertFalse(form.is_valid()) self.assertEqual(form.errors, {"slug": ["This slug is already in use"]})
def test_cms_wizards_blogpost_parent_page_should_exist(self): """ We should not be able to create a blogpost page if the parent page does not exist """ user = UserFactory(is_staff=True, is_superuser=True) form = BlogPostWizardForm( data={ "title": "My title", "slug": "my-title" }, wizard_language="en", wizard_user=user, ) self.assertFalse(form.is_valid()) self.assertEqual( form.errors, { "slug": [ "You must first create a parent page and set its `reverse_id` to `blogposts`." ] }, )
def test_enrollment_create_closed(self, lms_mock): """ Attempting to enroll in a course that is not open for enrollment anymore results in an error. """ user = UserFactory() course_run = CourseRunFactory( start=arrow.utcnow().shift(days=-35).datetime, end=arrow.utcnow().shift(days=+60).datetime, enrollment_start=arrow.utcnow().shift(days=-65).datetime, enrollment_end=arrow.utcnow().shift(days=-20).datetime, ) self.client.force_login(user) response = self.client.post("/api/v1.0/enrollments/", data={"course_run": course_run.id}) self.assertEqual(response.status_code, 400) self.assertEqual( response.json(), {"errors": ["Course run is not open for enrollments."]}) lms_mock.set_enrollment.assert_not_called()
def test_cms_wizards_course_run_submit_form_invalid_slug( self, mock_snapshot): """Trying to submit a slug that is not valid should raise a 400 exception.""" # A course should pre-exist course = CourseFactory() # Submit an invalid slug data = {"title": "my title", "slug": "invalid slug"} user = UserFactory(is_superuser=True, is_staff=True) form = CourseRunWizardForm(data=data, wizard_language="en", wizard_user=user) form.page = course.extended_object self.assertFalse(form.is_valid()) self.assertEqual( form.errors["slug"][0], "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens.", ) # Snapshot was not request and should not have been triggered self.assertFalse(mock_snapshot.called)
def test_admin_course_run_change_view_get_superuser_public(self): """Public course runs should not render a change view.""" user = UserFactory(is_staff=True, is_superuser=True) self.client.login(username=user.username, password="******") # Create a public course run course = CourseFactory() CourseRunFactory(direct_course=course) course.extended_object.publish("en") self.assertEqual(CourseRun.objects.count(), 2) public_course_run = CourseRun.objects.get( draft_course_run__isnull=False) # Get the admin change view url = reverse("admin:courses_courserun_change", args=[public_course_run.id]) response = self.client.get(url, follow=True) self.assertEqual(response.status_code, 200) self.assertNotContains(response, "id_title") self.assertContains(response, "Perhaps it was deleted?")
def test_admin_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 = { "organization_main": organization2.id, "organizations": [organization3.id], "subjects": [subject2.id], "courserun_set-TOTAL_FORMS": 0, "courserun_set-INITIAL_FORMS": 0, } 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.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_templatetags_placeholder_as_plugins_edit(self): """ The "placeholder_as_plugins" template tag should inject in the page the edit markup """ page = create_page("Test", "richie/single_column.html", "en", published=True) placeholder = page.placeholders.all()[0] add_plugin(placeholder, CKEditorPlugin, "en", body="<b>Test 1</b>") add_plugin(placeholder, CKEditorPlugin, "en", body="<b>Test 2</b>") request = RequestFactory().get("/") request.current_page = page user = UserFactory(is_staff=True, is_superuser=True) request.user = user request.session = {"cms_edit": True} request.toolbar = CMSToolbar(request) request.toolbar.is_staff = True # Silent keyword argument in edition mode omit markup related to placeholder # edition template = ( "{% load cms_tags extra_tags %}" '{% placeholder_as_plugins "maincontent" as plugins %}' "{% for plugin in plugins %}{% render_plugin plugin %}{% endfor %}" ) output = self.render_template_obj(template, {}, request) # Tag adds HTML placeholder which set stuff for DjangoCMS edition (we don't # assert on Javascript content since it holds too many references to write here) self.assertInHTML( f'<div class="cms-placeholder cms-placeholder-{placeholder.id:d}"></div>', output, ) self.assertIn( "CMS._plugins.push", output, )
def test_cms_wizards_person_submit_form_slugify_long_title(self): """ When generating the slug from the title, we should respect the slug's "max_length" """ # A parent page should pre-exist create_page( "Persons", "richie/single_column.html", "en", reverse_id=Person.PAGE["reverse_id"], ) # Submit a title at max length data = {"title": "t" * 255} user = UserFactory(is_superuser=True, is_staff=True) form = PersonWizardForm(data=data, wizard_language="en", wizard_user=user) 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_wizards_category_root_page_should_exist(self): """ We should not be able to create a category page if the root page does not exist """ page = create_page("page", "richie/single_column.html", "en") user = UserFactory(is_staff=True, is_superuser=True) form = CategoryWizardForm( data={"title": "My title", "slug": "my-title"}, wizard_language="en", wizard_user=user, wizard_page=page, ) self.assertFalse(form.is_valid()) self.assertEqual( form.errors, { "slug": [ "You must first create a parent page and set its `reverse_id` to `categories`." ] }, )
def test_admin_form_course_run_choices_superuser_several_courses_initial(self): """ If there are several courses but an initial value is passed for the "direct_course" field, the choice is made and the "direct_course" field is hidden. """ course = CourseFactory(page_title="Title 1", should_publish=True) CourseFactory(page_title="Title 2", should_publish=True) user = UserFactory(is_staff=True, is_superuser=True) request = RequestFactory().get("/") request.user = user CourseRunAdminForm.request = request form = CourseRunAdminForm(initial={"direct_course": course.id}) self.assertEqual(len(form.fields["direct_course"].choices), 3) self.assertIn( ( f'<input type="hidden" name="direct_course" value="{course.id:d}" ' 'id="id_direct_course">' ), form.as_ul(), )
def test_admin_page_snapshot_blocked_for_public_page(self): """ It should not be possible to snapshot the public page of a course. """ user = UserFactory(is_staff=True) self.client.login(username=user.username, password="******") public_course = CourseFactory(should_publish=True).public_extension # Add the necessary permissions (global and per page) self.add_permission(user, "add_page") self.add_permission(user, "change_page") self.add_page_permission( user, public_course.extended_object, can_change=True, can_add=True ) # Try triggering the creation of a snapshot for the course url = f"/en/admin/courses/course/{public_course.id:d}/snapshot/" response = self.client.post(url, follow=True) self.assertEqual(response.status_code, 400) self.assertEqual(response.content, b"Course could not be found.")
def test_admin_course_run_delete_staff_user_missing_permission(self): """ Staff users with missing page permissions can not delete a course run via the admin unless CMS permissions are not activated. """ course_run = CourseRunFactory() user = UserFactory(is_staff=True) self.client.login(username=user.username, password="******") # Add only model permissions, not page permission on the course page self.add_permission(user, "delete_courserun") self.add_permission(user, "change_page") self._prepare_delete(course_run, 200, self.assertTrue) # But it should work if CMS permissions are not activated with override_settings(CMS_PERMISSION=False): self._prepare_delete(course_run, 200, self.assertFalse) # The course object should not be deleted self.assertEqual(Course.objects.count(), 1)
def test_cms_wizards_person_submit_form(self): """ A user with the required permissions submitting a valid PersonWizardForm should be able to create a Person page extension and its related page. """ any_page = create_page("page", "richie/single_column.html", "en") # A parent page should pre-exist create_page( "Persons", "richie/single_column.html", "en", reverse_id=Person.PAGE["reverse_id"], ) # Create a user with just the required permissions user = UserFactory( is_staff=True, permissions=[ "courses.add_person", "cms.add_page", "cms.change_page" ], ) form = PersonWizardForm( data={"title": "A person"}, wizard_language="en", wizard_user=user, wizard_page=any_page, ) self.assertTrue(form.is_valid()) page = form.save() # Related page should have been created as draft Page.objects.drafts().get(id=page.id) Person.objects.get(id=page.person.id, extended_object=page) self.assertEqual(page.get_title(), "A person") # The slug should have been automatically set self.assertEqual(page.get_slug(), "a-person")
def test_admin_person_title_change_view_post(self): """ Validate that the person title can be updated via the admin. """ user = UserFactory(is_staff=True, is_superuser=True) self.client.login(username=user.username, password="******") # Create a person, title will automaticaly be created person_title = PersonTitleFactory(translation__title="Mister", translation__abbreviation="Mr.") self.assertEqual(PersonTitleTranslation.objects.count(), 1) # Get the admin change view url = reverse("admin:persons_persontitle_change", args=[person_title.id]) data = {"title": "Madam", "abbreviation": "Mm."} response = self.client.post(url, data) self.assertEqual(response.status_code, 302) # Check that the person title was updated as expected person_title = PersonTitle.objects.get(id=person_title.id) self.assertEqual(person_title.title, "Madam") self.assertEqual(person_title.abbreviation, "Mm.")