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_models_course_subjects_copied_when_publishing(self): """ When publishing a course, the links to draft subjects on the draft version of the course should be copied (clear then add) to set equivalent links between the corresponding published course and published subjects. """ # Create subjects: 2 published and 1 draft subject1, subject2 = SubjectFactory.create_batch(2, should_publish=True) subject3 = SubjectFactory() # Create a draft course draft_course = CourseFactory( with_subjects=[subject1, subject2, subject3]) # The draft course should see all subjects self.assertEqual(set(draft_course.subjects.all()), {subject1, subject2, subject3}) # Publish the course and check that the subjects are copied draft_course.extended_object.publish("en") published_course = Course.objects.get( extended_object__publisher_is_draft=False) self.assertEqual( set(published_course.subjects.all()), {subject1.public_extension, subject2.public_extension}, ) # A published subject should see the published course self.assertEqual(subject1.public_extension.courses.first(), published_course) # The subjects that are removed from the draft course should only be cleared from the # published course upon publishing draft_course.subjects.remove(subject1) self.assertEqual( set(published_course.subjects.all()), {subject1.public_extension, subject2.public_extension}, ) draft_course.extended_object.publish("en") self.assertEqual(set(published_course.subjects.all()), {subject2.public_extension}) # The published subject that was removed should not see the published course any more self.assertIsNone(subject1.public_extension.courses.first())
def test_course_subjects_copied_when_publishing(self): """ When publishing a course, the links to draft subjects on the draft version of the course should be copied (clear then add) to the published version. Links to published subjects should not be copied as they are redundant and not up-to-date. """ # Create draft subjects subject1, subject2 = SubjectFactory.create_batch(2) # Create a draft course draft_course = CourseFactory(with_subjects=[subject1, subject2]) # Publish subject1 subject1.extended_object.publish("en") subject1.refresh_from_db() # The draft course should see all subjects and propose a custom filter to easily access # the draft versions self.assertEqual( set(draft_course.subjects.all()), {subject1, subject1.public_extension, subject2}, ) self.assertEqual(set(draft_course.subjects.drafts()), {subject1, subject2}) # Publish the course and check that the subjects are copied draft_course.extended_object.publish("en") published_course = Course.objects.get( extended_object__publisher_is_draft=False) self.assertEqual(set(published_course.subjects.all()), {subject1, subject2}) # When publishing, the subjects that are obsolete should be cleared draft_course.subjects.remove(subject2) self.assertEqual(set(published_course.subjects.all()), {subject1, subject2}) # Subjects on the published course are only cleared after publishing the draft page draft_course.extended_object.publish("en") self.assertEqual(set(published_course.subjects.all()), {subject1})
def test_course_cms_published_content(self): """ Validate that the important elements are displayed on a published course page """ subjects = SubjectFactory.create_batch(4) organizations = OrganizationFactory.create_batch(4) course = CourseFactory( organization_main=organizations[0], title="Very interesting course", with_organizations=organizations, with_subjects=subjects, ) page = course.extended_object # Publish only 2 out of 4 subjects and 2 out of 4 organizations subjects[0].extended_object.publish("en") subjects[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. subjects[2].extended_object.publish("en") subjects[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") 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__title">Very interesting course</h1>', html=True, ) self.assertContains( response, '<div class="course-detail__aside__active-session">{:s}</div>'. format(course.active_session), html=True, ) # Only published subjects should be present on the page for subject in subjects[:2]: self.assertContains( response, '<li class="course-detail__content__subjects__item">{:s}</li>'. format(subject.extended_object.get_title()), html=True, ) for subject in subjects[-2:]: self.assertNotContains(response, subject.extended_object.get_title()) # organization 1 is marked as main organization self.assertContains( response, '<li class="{element:s} {element:s}--main">{title:s}</li>'.format( element="course-detail__content__organizations__item", title=organizations[0].extended_object.get_title(), ), html=True, ) # organization 2 is the only "common" org in listing self.assertContains( response, '<li class="course-detail__content__organizations__item">{:s}</li>' .format(organizations[1].extended_object.get_title()), html=True, ) # Draft organization should not be in response content for organization in organizations[-2:]: self.assertNotContains(response, organization.extended_object.get_title(), html=True)
def test_course_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="******") subjects = SubjectFactory.create_batch(4) organizations = OrganizationFactory.create_batch(4) course = CourseFactory( organization_main=organizations[0], title="Very interesting course", with_organizations=organizations, with_subjects=subjects, ) page = course.extended_object # Publish only 2 out of 4 subjects and 2 out of 4 organizations subjects[0].extended_object.publish("en") subjects[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. subjects[2].extended_object.publish("en") subjects[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>Very interesting course</title>", html=True) self.assertContains( response, '<h1 class="course-detail__title">Very interesting course</h1>', html=True, ) self.assertContains( response, '<div class="course-detail__aside__active-session">{:s}</div>'. format(course.active_session), html=True, ) # organization 1 is marked as main and not duplicated self.assertContains( response, '<li class="{element:s} {element:s}--main">{title:s}</li>'.format( element="course-detail__content__organizations__item", title=organizations[0].extended_object.get_title(), ), html=True, ) self.assertNotContains( response, ('<li class="course-detail__content__organizations__item">{:s}</li>' ).format(organizations[0].extended_object.get_title()), html=True, ) # organization 2 is not marked as a draft since it has been published self.assertContains( response, '<li class="course-detail__content__organizations__item">{:s}</li>' .format(organizations[1].extended_object.get_title()), html=True, ) # Draft organizations should be present on the page with an annotation for styling for organization in organizations[:2]: self.assertNotContains( response, '<li class="{element:s} {element:s}--draft">{title:s}</li>'. format( element="course - detail__content__organizations__item", title=organization.extended_object.get_title(), ), html=True, ) # The published subjects should be present on the page for subject in subjects[:2]: self.assertContains( response, '<li class="course-detail__content__subjects__item">{:s}</li>'. format(subject.extended_object.get_title()), html=True, ) # Draft subjects should also be present on the page with an annotation for styling for subject in subjects[-2:]: self.assertContains( response, '<li class="{element:s} {element:s}--draft">{title:s}</li>'. format( element="course-detail__content__subjects__item", title=subject.extended_object.get_title(), ), html=True, )
def create_demo_site(): """ Create a simple site tree structure for developpers to work in realistic environment. We create multilingual pages, add organizations under the related page and add plugins to each page. """ site = Site.objects.get(id=1) # Create pages as described in PAGES_INFOS pages_created = recursive_page_creation(site, PAGE_INFOS) # Create some licences licences = LicenceFactory.create_batch(NB_LICENCES) # Create organizations under the `Organizations` page organizations = OrganizationFactory.create_batch( NB_ORGANIZATIONS, languages=[l[0] for l in settings.LANGUAGES], parent=pages_created["organizations"], fill_banner=True, fill_description=True, fill_logo=True, should_publish=True, in_navigation=True, ) # Create subjects under the `Subjects` page subjects = SubjectFactory.create_batch( NB_SUBJECTS, languages=[l[0] for l in settings.LANGUAGES], parent=pages_created["subjects"], fill_banner=True, fill_description=True, fill_logo=True, should_publish=True, in_navigation=True, ) # Django parler require a language to be manually set when working out of # request/response flow and PersonTitle use 'parler' translation.activate(settings.LANGUAGE_CODE) # Create persons under the `persons` page persons = PersonFactory.create_batch( NB_PERSONS, languages=[l[0] for l in settings.LANGUAGES], parent=pages_created["persons"], fill_portrait=True, fill_resume=True, should_publish=True, in_navigation=True, ) # Create courses under the `Course` page with subjects and organizations # relations for _ in range(NB_COURSES): course_organizations = random.sample( organizations, NB_COURSES_ORGANIZATION_RELATIONS) video_sample = random.choice(VIDEO_SAMPLE_LINKS) course = CourseFactory( languages=[l[0] for l in settings.LANGUAGES], parent=pages_created["courses"], organization_main=random.choice(course_organizations), fill_licences=[ ("course_license_content", random.choice(licences)), ("course_license_participation", random.choice(licences)), ], fill_team=random.sample(persons, NB_COURSES_PERSONS_PLUGINS), fill_teaser=video_sample, fill_cover=video_sample.image, fill_texts=[ "course_syllabus", "course_format", "course_prerequisites", "course_plan", # "course_license_content", # "course_license_participation", ], with_organizations=course_organizations, with_subjects=random.sample(subjects, NB_COURSES_SUBJECT_RELATIONS), should_publish=True, in_navigation=True, ) # Add a random number of course runs to the course nb_course_runs = get_number_of_course_runs() if nb_course_runs > 0: CourseRunFactory.create_batch(nb_course_runs, course=course)
def test_templates_course_run_detail_cms_published_content(self, _): """ Validate that the important elements are displayed on a published course run page """ subjects = SubjectFactory.create_batch(4) organizations = OrganizationFactory.create_batch(4) course = CourseFactory( page_title="Very interesting course", fill_organizations=organizations, fill_subjects=subjects, 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), enrollment_end=datetime(2019, 1, 18), start=datetime(2018, 12, 10), end=datetime(2019, 2, 14), languages=["en", "fr"], ) page = course_run.extended_object # Publish only 2 out of 4 subjects and 2 out of 4 organizations subjects[0].extended_object.publish("en") subjects[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. subjects[2].extended_object.publish("en") subjects[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="course-detail__content__title">' "Very interesting course<br>first session</h1>", html=True, ) # Only published subjects should be present on the page for subject in subjects[:2]: self.assertContains( response, '<a class="subject-plugin-tag" href="{:s}">{:s}</a>'.format( subject.extended_object.get_absolute_url(), subject.extended_object.get_title(), ), html=True, ) for subject in subjects[-2:]: self.assertNotContains(response, subject.extended_object.get_title()) # Public organizations should be in response content for organization in organizations[:2]: self.assertContains( response, '<div class="organization-plugin__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, "<dt>Enrollment starts</dt><dd>Oct. 21, 2018</dd>" ) self.assertContains(response, "<dt>Enrollment ends</dt><dd>Jan. 18, 2019</dd>") self.assertContains(response, "<dt>Course starts</dt><dd>Dec. 10, 2018</dd>") self.assertContains(response, "<dt>Course ends</dt><dd>Feb. 14, 2019</dd>") self.assertContains( response, '<a class="course-detail__content__run__block__cta" ' 'href="https://www.example.com/enroll">Enroll now</a>', html=True, ) self.assertContains(response, "<dt>Languages</dt><dd>English, French</dd>")
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="******") subjects = SubjectFactory.create_batch(4) organizations = OrganizationFactory.create_batch(4) course = CourseFactory( page_title="Very interesting course", fill_organizations=organizations, fill_subjects=subjects, 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), enrollment_end=datetime(2019, 1, 18), start=datetime(2018, 12, 10), end=datetime(2019, 2, 14), languages=["en", "fr"], ) page = course_run.extended_object # Publish only 2 out of 4 subjects and 2 out of 4 organizations subjects[0].extended_object.publish("en") subjects[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. subjects[2].extended_object.publish("en") subjects[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="course-detail__content__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-plugin__title">{title:s}</div>'.format( title=organization.extended_object.get_title() ), html=True, ) # Draft organizations should be annotated for styling self.assertContains(response, "organization-plugin-container--draft", count=2) # The published subjects should be present on the page for subject in subjects[:2]: self.assertContains( response, '<a class="subject-plugin-tag" href="{:s}">{:s}</a>'.format( subject.extended_object.get_absolute_url(), subject.extended_object.get_title(), ), html=True, ) # Draft subjects should also be present on the page with an annotation for styling for subject in subjects[-2:]: self.assertContains( response, '<a class="{element:s} {element:s}--draft" href="{url:s}">{title:s}</a>'.format( url=subject.extended_object.get_absolute_url(), element="subject-plugin-tag", title=subject.extended_object.get_title(), ), html=True, ) # The course run details should be on the page self.assertContains( response, "<dt>Enrollment starts</dt><dd>Oct. 21, 2018</dd>" ) self.assertContains(response, "<dt>Enrollment ends</dt><dd>Jan. 18, 2019</dd>") self.assertContains(response, "<dt>Course starts</dt><dd>Dec. 10, 2018</dd>") self.assertContains(response, "<dt>Course ends</dt><dd>Feb. 14, 2019</dd>") self.assertContains( response, '<a class="course-detail__content__run__block__cta" ' 'href="https://www.example.com/enroll">Enroll now</a>', html=True, ) self.assertContains(response, "<dt>Languages</dt><dd>English, French</dd>")
def test_templates_course_detail_cms_published_content(self, _): """ Validate that the important elements are displayed on a published course page """ subjects = SubjectFactory.create_batch(4) organizations = OrganizationFactory.create_batch(4) course = CourseFactory( page_title="Very interesting course", fill_organizations=organizations, fill_subjects=subjects, ) 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 subjects and 2 out of 4 organizations subjects[0].extended_object.publish("en") subjects[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. subjects[2].extended_object.publish("en") subjects[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 subjects should be present on the page for subject in subjects[:2]: self.assertContains( response, '<a class="subject-plugin-tag" href="{:s}">{:s}</a>'.format( subject.extended_object.get_absolute_url(), subject.extended_object.get_title(), ), html=True, ) for subject in subjects[-2:]: self.assertNotContains(response, subject.extended_object.get_title()) # Public organizations should be in response content for organization in organizations[:2]: self.assertContains( response, '<div class="organization-plugin__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, "Enroll now", count=1) self.assertContains(response, "<dd>English, French</dd>", html=True, count=1)
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="******") subjects = SubjectFactory.create_batch(4) organizations = OrganizationFactory.create_batch(4) course = CourseFactory( page_title="Very interesting course", fill_organizations=organizations, fill_subjects=subjects, ) page = course.extended_object course_run1, _course_run2 = CourseRunFactory.create_batch( 2, page_parent=course.extended_object, languages=["en", "fr"] ) # Publish only 1 of the course runs course_run1.extended_object.publish("en") # Publish only 2 out of 4 subjects and 2 out of 4 organizations subjects[0].extended_object.publish("en") subjects[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. subjects[2].extended_object.publish("en") subjects[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>Very interesting course</title>", html=True ) self.assertContains( response, '<h1 class="course-detail__content__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-plugin__title">{title:s}</div>'.format( title=organization.extended_object.get_title() ), html=True, ) # Draft organizations should be annotated for styling self.assertContains(response, "organization-plugin-container--draft", count=2) # The published subjects should be present on the page for subject in subjects[:2]: self.assertContains( response, '<a class="subject-plugin-tag" href="{:s}">{:s}</a>'.format( subject.extended_object.get_absolute_url(), subject.extended_object.get_title(), ), html=True, ) # Draft subjects should also be present on the page with an annotation for styling for subject in subjects[-2:]: self.assertContains( response, '<a class="{element:s} {element:s}--draft" href="{url:s}">{title:s}</a>'.format( url=subject.extended_object.get_absolute_url(), element="subject-plugin-tag", title=subject.extended_object.get_title(), ), html=True, ) # The draft and the published course runs should both be in the page self.assertContains(response, "Enroll now", count=2) self.assertContains(response, "<dd>English, French</dd>", html=True, count=2)
def test_cms_plugins_course_render_on_public_page(self): """ Test that an CoursePlugin correctly renders course's page specific information """ # Create an Course with a page in both english and french subjects = SubjectFactory.create_batch(4) organization = OrganizationFactory(page_title="public title") course = CourseFactory( page_title={ "en": "public title", "fr": "titre public" }, fill_organizations=[organization], fill_subjects=subjects, ) course_page = course.extended_object course_page.publish("en") course_page.publish("fr") # 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, CoursePlugin, "en", **{"page": course_page}) add_plugin(placeholder, CoursePlugin, "fr", **{"page": course_page}) page.publish("en") page.publish("fr") # Check the page content in English url = page.get_absolute_url(language="en") response = self.client.get(url) self.assertContains(response, "public title") self.assertNotContains(response, "titre public") # The course's url should be present self.assertContains( response, '<a class="course-plugin" href="{url}"'.format( url=course_page.get_absolute_url()), status_code=200, ) # The course's name should be present self.assertContains( response, '<p class="course-glimpse__content__title">{title}</p>'.format( title=course_page.get_title()), status_code=200, ) # The course's main organization should be present self.assertContains( response, "<p>{title}</p>".format( title=organization.extended_object.get_title()), status_code=200, ) # The draft course plugin should not be present # Check if draft is shown after unpublish course_page.unpublish("en") page.publish("en") response = self.client.get(url) self.assertNotContains(response, "public title") self.assertNotContains(response, course_page.get_absolute_url()) # Check the page content in french url = page.get_absolute_url(language="fr") response = self.client.get(url) self.assertContains(response, "titre public") # The course's url should be present self.assertContains( response, '<a class="course-plugin" href="{url}"'.format( url=course_page.get_absolute_url()), status_code=200, ) # The course's name should be present self.assertContains( response, '<p class="course-glimpse__content__title">{title}</p>'.format( title=course_page.get_title()), status_code=200, ) # The course's main organization should be present self.assertContains( response, "<p>{title}</p>".format( title=organization.extended_object.get_title()), status_code=200, ) # The draft course plugin should not be present # Check if draft is shown after unpublish course_page.unpublish("fr") page.publish("fr") response = self.client.get(url) self.assertNotContains(response, "titre public") self.assertNotContains(response, course_page.get_absolute_url())