Ejemplo n.º 1
0
    def test_models_course_run_mark_dirty_parler(self):
        """
        Updating the value of a field translatable via parler should mark the related
        course page dirty (waiting to be published) only in the impacted language.
        """
        course = CourseFactory(page_languages=["en", "fr"])
        course_run = CourseRunFactory(direct_course=course)
        CourseRunTranslation.objects.create(master=course_run,
                                            language_code="fr",
                                            title="mon titre")
        self.assertTrue(course_run.direct_course.extended_object.publish("en"))
        self.assertTrue(course_run.direct_course.extended_object.publish("fr"))
        course_run.refresh_from_db()
        self.assertIsNotNone(course_run.public_course_run)

        title_query = course_run.direct_course.extended_object.title_set
        title_obj_en = title_query.get(language="en")
        title_obj_fr = title_query.get(language="fr")
        self.assertEqual(title_query.count(), 2)

        with switch_language(course_run, "fr"):
            course_run.title = "nouveau titre"
        course_run.save()

        self.assertEqual(title_obj_en.publisher_state, PUBLISHER_STATE_DEFAULT)
        self.assertEqual(title_obj_fr.publisher_state, PUBLISHER_STATE_DEFAULT)

        course_run.mark_course_dirty()
        title_obj_en.refresh_from_db()
        title_obj_fr.refresh_from_db()

        self.assertEqual(title_obj_en.publisher_state, PUBLISHER_STATE_DEFAULT)
        self.assertEqual(title_obj_fr.publisher_state, PUBLISHER_STATE_DIRTY)
Ejemplo n.º 2
0
    def test_api_course_run_sync_catalog_visibility_course_and_search(
            self, mock_signal):
        """
        Verify that the catalog visibility is updated with `course_and_search`
        """
        link = "http://example.edx:8073/courses/course-v1:edX+DemoX+01/course/"
        course = CourseFactory(code="DemoX")
        course_run = CourseRunFactory(direct_course=course, resource_link=link)
        mock_signal.reset_mock()

        data = {
            "resource_link": link,
            "catalog_visibility": "course_and_search",
        }

        response = self.client.post(
            "/api/v1.0/course-runs-sync",
            data,
            content_type="application/json",
            HTTP_AUTHORIZATION=
            ("SIG-HMAC-SHA256 84583c2b96759c26ed1ecb3fc7d6d4805853cbbcd5ac40e777c0aa1c319fd490"
             ),
        )
        self.assertEqual(response.status_code, 200)
        self.assertEqual(json.loads(response.content), {"success": True})
        course_run.refresh_from_db()
        self.assertEqual(course_run.catalog_visibility,
                         CourseRunCatalogVisibility.COURSE_AND_SEARCH)
        self.assertFalse(mock_signal.called)
Ejemplo n.º 3
0
    def test_api_course_run_sync_catalog_visibility_hidden(self, mock_signal):
        """
        Verify that the catalog visibility is updated with `hidden`
        """
        link = "http://example.edx:8073/courses/course-v1:edX+DemoX+01/course/"
        course = CourseFactory(code="DemoX")
        course_run = CourseRunFactory(direct_course=course, resource_link=link)
        mock_signal.reset_mock()

        data = {
            "resource_link": link,
            "catalog_visibility": "hidden",
        }

        response = self.client.post(
            "/api/v1.0/course-runs-sync",
            data,
            content_type="application/json",
            HTTP_AUTHORIZATION=
            ("SIG-HMAC-SHA256 b18aaa037ac3c4630fb3d88597cf5138e8c145a10b912688de82ee2c963a7ed6"
             ),
        )
        self.assertEqual(response.status_code, 200)
        self.assertEqual(json.loads(response.content), {"success": True})
        course_run.refresh_from_db()
        self.assertEqual(course_run.catalog_visibility,
                         CourseRunCatalogVisibility.HIDDEN)
        self.assertFalse(mock_signal.called)
Ejemplo n.º 4
0
    def test_models_course_run_copy_translations_one_language(self):
        """
        The "copy_translations" method called for a specific language should only port parler's
        translations for this language from one course run to the other.
        """
        course_run = CourseRunFactory(title="my title")
        CourseRunTranslation.objects.create(master=course_run,
                                            language_code="fr",
                                            title="mon titre")
        old_course_run = CourseRun.objects.get(pk=course_run.pk)
        self.assertEqual(CourseRunTranslation.objects.count(), 2)
        with switch_language(old_course_run, "fr"):
            self.assertEqual(old_course_run.title, "mon titre")

        course_run.pk = None
        course_run.save()
        self.assertEqual(CourseRunTranslation.objects.count(), 2)

        course_run.copy_translations(old_course_run, language="fr")

        self.assertEqual(CourseRun.objects.count(), 2)
        self.assertEqual(CourseRunTranslation.objects.count(), 3)
        self.assertEqual(
            CourseRunTranslation.objects.filter(master=course_run).count(), 1)

        course_run.refresh_from_db()
        self.assertEqual(course_run.title, "mon titre")  # Fallback to french
        with switch_language(course_run, "fr"):
            self.assertEqual(course_run.title, "mon titre")
Ejemplo n.º 5
0
    def test_api_course_run_sync_catalog_visibility_none(self, mock_signal):
        """
        Test the adaptation of the Open edX catalog visibility of the `none` value to the Richie
        `hidden`.
        """
        link = "http://example.edx:8073/courses/course-v1:edX+DemoX+01/course/"
        course = CourseFactory(code="DemoX")
        course_run = CourseRunFactory(direct_course=course, resource_link=link)
        mock_signal.reset_mock()

        data = {
            "resource_link": link,
            "catalog_visibility": "none",
        }

        response = self.client.post(
            "/api/v1.0/course-runs-sync",
            data,
            content_type="application/json",
            HTTP_AUTHORIZATION=
            ("SIG-HMAC-SHA256 e9f23ed36de0865dbfd543ce1c83e0ec1700c8821cf6ff960ec549f2e6c4a6db"
             ),
        )
        self.assertEqual(response.status_code, 200)
        self.assertEqual(json.loads(response.content), {"success": True})
        course_run.refresh_from_db()
        self.assertEqual(course_run.catalog_visibility,
                         CourseRunCatalogVisibility.HIDDEN)
        self.assertFalse(mock_signal.called)
Ejemplo n.º 6
0
    def test_api_course_run_sync_catalog_visibility_both(self, mock_signal):
        """
        Test the adaptation of the Open edX catalog visibility of the `both` value to the Richie
        `course_and_search`.
        """
        link = "http://example.edx:8073/courses/course-v1:edX+DemoX+01/course/"
        course = CourseFactory(code="DemoX")
        course_run = CourseRunFactory(direct_course=course, resource_link=link)
        mock_signal.reset_mock()

        data = {
            "resource_link": link,
            "catalog_visibility": "both",
        }

        response = self.client.post(
            "/api/v1.0/course-runs-sync",
            data,
            content_type="application/json",
            HTTP_AUTHORIZATION=
            ("SIG-HMAC-SHA256 e93c38b636ec55fa4c1393f59e1758d53f55b0753737f3c451e4084ec10dd1ca"
             ),
        )
        self.assertEqual(response.status_code, 200)
        self.assertEqual(json.loads(response.content), {"success": True})
        course_run.refresh_from_db()
        self.assertEqual(course_run.catalog_visibility,
                         CourseRunCatalogVisibility.COURSE_AND_SEARCH)
        self.assertFalse(mock_signal.called)
Ejemplo n.º 7
0
    def test_models_course_run_copy_translations_all_languages(self):
        """
        The "copy_translations" method should port parler's translations for all languages
        from one course run to the other.
        """
        course_run = CourseRunFactory(title="my title")
        CourseRunTranslation.objects.create(master=course_run,
                                            language_code="fr",
                                            title="mon titre")
        old_course_run = CourseRun.objects.get(pk=course_run.pk)
        self.assertEqual(CourseRunTranslation.objects.count(), 2)
        with switch_language(old_course_run, "fr"):
            self.assertEqual(old_course_run.title, "mon titre")

        course_run.pk = None
        course_run.save()
        self.assertEqual(CourseRunTranslation.objects.count(), 2)

        course_run.copy_translations(old_course_run)

        self.assertEqual(CourseRun.objects.count(), 2)
        self.assertEqual(CourseRunTranslation.objects.count(), 4)
        self.assertEqual(
            CourseRunTranslation.objects.filter(master=course_run).count(), 2)

        course_run.refresh_from_db()
        self.assertEqual(course_run.title, "my title")
        with switch_language(course_run, "fr"):
            self.assertEqual(course_run.title, "mon titre")
Ejemplo n.º 8
0
    def test_api_course_run_sync_catalog_visibility_about(self, mock_signal):
        """
        Test the adaptation of the Open edX catalog visibility of the `about` value to the Richie
        `course_only`.
        """
        link = "http://example.edx:8073/courses/course-v1:edX+DemoX+01/course/"
        course = CourseFactory(code="DemoX")
        course_run = CourseRunFactory(direct_course=course, resource_link=link)
        mock_signal.reset_mock()

        data = {
            "resource_link": link,
            "catalog_visibility": "about",
        }

        response = self.client.post(
            "/api/v1.0/course-runs-sync",
            data,
            content_type="application/json",
            HTTP_AUTHORIZATION=
            ("SIG-HMAC-SHA256 58f845704d268db96dfdcbd88a7f8a6f0124611fd741ec7a7eb6a12cd0af79f1"
             ),
        )
        self.assertEqual(response.status_code, 200)
        self.assertEqual(json.loads(response.content), {"success": True})
        course_run.refresh_from_db()
        self.assertEqual(course_run.catalog_visibility,
                         CourseRunCatalogVisibility.COURSE_ONLY)
        self.assertFalse(mock_signal.called)
Ejemplo n.º 9
0
    def test_api_course_run_sync_enrollment_count(self, mock_signal):
        """
        Check if the enrollment count of a course is updated
        """
        link = "http://example.edx:8073/courses/course-v1:edX+DemoX+01/course/"
        course = CourseFactory(code="DemoX")
        course_run = CourseRunFactory(direct_course=course, resource_link=link)
        mock_signal.reset_mock()

        data = {
            "resource_link": link,
            "enrollment_count": 865,
        }

        response = self.client.post(
            "/api/v1.0/course-runs-sync",
            data,
            content_type="application/json",
            HTTP_AUTHORIZATION=
            ("SIG-HMAC-SHA256 f510d35ba7492eb5c5133fab6bd2fa18328307089735a20f4a7f4a828e0598b5"
             ),
        )
        self.assertEqual(response.status_code, 200)
        self.assertEqual(json.loads(response.content), {"success": True})
        course_run.refresh_from_db()
        self.assertEqual(course_run.enrollment_count, 865)
        self.assertFalse(mock_signal.called)
Ejemplo n.º 10
0
    def test_api_course_run_sync_catalog_visibility_course_only(
            self, mock_signal):
        """
        Verify that the catalog visibility is updated with `course_only`
        """
        link = "http://example.edx:8073/courses/course-v1:edX+DemoX+01/course/"
        course = CourseFactory(code="DemoX")
        course_run = CourseRunFactory(direct_course=course, resource_link=link)
        mock_signal.reset_mock()

        data = {
            "resource_link": link,
            "catalog_visibility": "course_only",
        }

        response = self.client.post(
            "/api/v1.0/course-runs-sync",
            data,
            content_type="application/json",
            HTTP_AUTHORIZATION=
            ("SIG-HMAC-SHA256 c0c06e769ebb2d84ff14149f50aea5503ae192a4074f9c9b2478732a02936601"
             ),
        )
        self.assertEqual(response.status_code, 200)
        self.assertEqual(json.loads(response.content), {"success": True})
        course_run.refresh_from_db()
        self.assertEqual(course_run.catalog_visibility,
                         CourseRunCatalogVisibility.COURSE_ONLY)
        self.assertFalse(mock_signal.called)
Ejemplo n.º 11
0
    def test_admin_course_run_delete_superuser_public(self):
        """
        Validate that the public course run can not be deleted via the admin.
        """
        course_run = CourseRunFactory()
        course_run.direct_course.extended_object.publish("en")
        course_run.refresh_from_db()

        user = UserFactory(is_staff=True, is_superuser=True)
        self.client.login(username=user.username, password="******")

        self._prepare_delete(course_run.public_course_run, 200, self.assertFalse)
Ejemplo n.º 12
0
    def test_models_course_run_mark_dirty_delete_course_run_to_be_scheduled(self):
        """
        Deleting a course run yet to be scheduled should not mark the related course page dirty.
        """
        field = random.choice(["start", "enrollment_start"])
        course_run = CourseRunFactory(**{field: None})
        self.assertTrue(course_run.direct_course.extended_object.publish("en"))
        title_obj = course_run.direct_course.extended_object.title_set.first()
        course_run.refresh_from_db()

        course_run.delete()
        title_obj.refresh_from_db()

        self.assertEqual(title_obj.publisher_state, PUBLISHER_STATE_DEFAULT)
Ejemplo n.º 13
0
    def test_admin_course_run_change_view_post_superuser_public(self):
        """
        Validate that the public course run can not be updated via the admin.
        """
        course_run = CourseRunFactory()
        snapshot = CourseFactory(
            page_parent=course_run.direct_course.extended_object)
        course_run.direct_course.extended_object.publish("en")
        course_run.refresh_from_db()

        user = UserFactory(is_staff=True, is_superuser=True)
        self.client.login(username=user.username, password="******")

        self._prepare_change_view_post(course_run.public_course_run, snapshot,
                                       403, self.assertNotEqual)
Ejemplo n.º 14
0
    def test_models_course_run_mark_dirty_delete_course_run(self):
        """
        Deleting a scheduled course run should mark the related course page dirty.
        """
        course_run = CourseRunFactory()
        self.assertTrue(course_run.direct_course.extended_object.publish("en"))
        title_obj = course_run.direct_course.extended_object.title_set.first()
        course_run.refresh_from_db()

        self.assertEqual(title_obj.publisher_state, PUBLISHER_STATE_DEFAULT)

        course_run.delete()
        title_obj.refresh_from_db()

        self.assertEqual(title_obj.publisher_state, PUBLISHER_STATE_DIRTY)
Ejemplo n.º 15
0
    def test_admin_course_run_delete_superuser_draft(self):
        """
        Validate that the draft course run can be deleted via the admin and that
        it deletes the associated public course run.
        """
        course_run = CourseRunFactory()
        course_run.direct_course.extended_object.publish("en")
        course_run.refresh_from_db()
        self.assertEqual(CourseRun.objects.count(), 2)

        user = UserFactory(is_staff=True, is_superuser=True)
        self.client.login(username=user.username, password="******")

        response = self._prepare_delete(course_run, 200, self.assertFalse)
        self.assertEqual(Course.objects.count(), 2)
        self.assertContains(response, "was deleted successfully")
Ejemplo n.º 16
0
    def test_admin_course_run_change_view_post(self):
        """
        Validate that the course run can be updated via the admin.
        """
        user = UserFactory(is_staff=True, is_superuser=True)
        self.client.login(username=user.username, password="******")

        # Create a course run
        course_run = CourseRunFactory()

        # Get the admin change view
        url = reverse("admin:courses_courserun_change", args=[course_run.id])
        data = {
            "languages": ["fr", "en"],
            "resource_link": "https://example.com/my-resource-link",
            "start_0": "2015-01-15",
            "start_1": "07:06:15",
            "end_0": "2015-01-30",
            "end_1": "23:52:34",
            "enrollment_start_0": "2015-01-02",
            "enrollment_start_1": "13:13:07",
            "enrollment_end_0": "2015-01-23",
            "enrollment_end_1": "09:07:11",
        }
        with timezone.override(pytz.utc):
            response = self.client.post(url, data)
        self.assertEqual(response.status_code, 302)

        # Check that the course run was updated as expected
        course_run.refresh_from_db()
        self.assertEqual(course_run.languages, ["fr", "en"])
        self.assertEqual(
            course_run.resource_link, "https://example.com/my-resource-link"
        )
        self.assertEqual(
            course_run.start, datetime(2015, 1, 15, 7, 6, 15, tzinfo=pytz.utc)
        )
        self.assertEqual(
            course_run.end, datetime(2015, 1, 30, 23, 52, 34, tzinfo=pytz.utc)
        )
        self.assertEqual(
            course_run.enrollment_start,
            datetime(2015, 1, 2, 13, 13, 7, tzinfo=pytz.utc),
        )
        self.assertEqual(
            course_run.enrollment_end, datetime(2015, 1, 23, 9, 7, 11, tzinfo=pytz.utc)
        )
Ejemplo n.º 17
0
    def test_models_course_run_get_course_child_of_snapshot(self):
        """
        We should be able to retrieve the course from a course run that is a child of one of
        its snapshots.
        """
        course = CourseFactory(should_publish=True)
        snapshot = CourseFactory(page_parent=course.extended_object)
        course_run = CourseRunFactory(direct_course=snapshot)
        self.assertTrue(snapshot.extended_object.publish("en"))
        course_run.refresh_from_db()

        # Add a sibling course to make sure it is not returned
        CourseFactory(should_publish=True)

        self.assertEqual(course_run.get_course(), course)
        self.assertEqual(course_run.public_course_run.get_course(),
                         course.public_extension)
Ejemplo n.º 18
0
    def test_models_course_copy_relations_publish(self):
        """
        When publishing a draft course, the draft course run should be copied to a newly created
        course run with its parler translations.

        In a second part of the test, we check that when publishing a course that was already
        published, the draft course run should be copied to the existing public course run with its
        parler translations.

        """
        # 1- Publishing a draft course

        course = CourseFactory(page_title="my course title")
        TitleFactory(page=course.extended_object,
                     language="fr",
                     title="mon titre de cours")
        course_run = CourseRunFactory(direct_course=course, title="my run")
        CourseRunTranslation.objects.create(master=course_run,
                                            language_code="fr",
                                            title="ma session")
        self.assertEqual(Course.objects.count(), 1)
        self.assertEqual(CourseRun.objects.count(), 1)
        self.assertEqual(CourseRunTranslation.objects.count(), 2)
        self.assertEqual(Title.objects.count(), 2)

        self.assertTrue(course.extended_object.publish("fr"))

        self.assertEqual(Course.objects.count(), 2)
        self.assertEqual(CourseRun.objects.count(), 2)
        self.assertEqual(CourseRunTranslation.objects.count(), 3)
        self.assertEqual(Title.objects.count(), 3)

        # 2- Publishing a course that was already published
        self.assertTrue(course.extended_object.publish("en"))

        self.assertEqual(CourseRunTranslation.objects.count(), 4)
        self.assertEqual(Title.objects.count(), 4)

        course_run.refresh_from_db()

        public_course_run = course_run.public_course_run
        self.assertEqual(public_course_run.title, "my run")
        with switch_language(public_course_run, "fr"):
            self.assertEqual(public_course_run.title, "ma session")
Ejemplo n.º 19
0
    def test_models_course_run_get_course_direct_child(self):
        """
        We should be able to retrieve the course from a course run that is its direct child.
        """
        course = CourseFactory()
        course_run = CourseRunFactory(direct_course=course)
        self.assertTrue(course.extended_object.publish("en"))

        course.refresh_from_db()
        course_run.refresh_from_db()

        # Add a sibling course to make sure it is not returned
        CourseFactory(should_publish=True)

        # Add a snapshot to make sure it does not interfere
        CourseFactory(page_parent=course.extended_object, should_publish=True)

        self.assertEqual(course_run.get_course(), course)
        self.assertEqual(course_run.public_course_run.get_course(),
                         course.public_extension)
Ejemplo n.º 20
0
    def test_update_course_run_by_anonymous_user(self):
        """
        The course run API does not support course run update.
        """
        course_run = CourseRunFactory(
            resource_link="https://example.com/old-course-run/")

        response = self.client.put(
            f"/api/v1.0/course-runs/{course_run.id}/",
            {
                **CourseRunSerializer(course_run).data,
                "resource_link":
                "https://example.com/new-course-run/",
            },
        )

        self.assertEqual(response.status_code, 403)
        course_run.refresh_from_db()
        self.assertEqual(course_run.resource_link,
                         "https://example.com/old-course-run/")
Ejemplo n.º 21
0
    def test_admin_course_run_delete_staff_user_page_permission(self):
        """Staff users with all necessary permissions can delete a course run via the admin."""
        course_run = CourseRunFactory()
        course_run.direct_course.extended_object.publish("en")
        course_run.refresh_from_db()

        user = UserFactory(is_staff=True)
        self.client.login(username=user.username, password="******")

        # Add all necessary model and object level permissions
        self.add_permission(user, "delete_courserun")
        self.add_permission(user, "change_page")
        PagePermission.objects.create(
            page=course_run.direct_course.extended_object,
            user=user,
            can_add=False,
            can_change=True,
            can_delete=False,
            can_publish=False,
            can_move_page=False,
        )

        self._prepare_delete(course_run, 200, self.assertFalse)
Ejemplo n.º 22
0
    def test_models_course_run_get_course_direct_child_with_parent(self):
        """
        We should be able to retrieve the course from a course run that is its direct child
        when the course is below a root page (this is creating a difficulty because the
        query we build in `get_course` can create duplicates if we don't add the right clauses).
        """
        page = create_i18n_page("A page", published=True)
        course = CourseFactory(page_parent=page)
        course_run = CourseRunFactory(direct_course=course)
        self.assertTrue(course.extended_object.publish("en"))

        course.refresh_from_db()
        course_run.refresh_from_db()

        # Add a sibling course to make sure it is not returned
        CourseFactory(should_publish=True)

        # Add a snapshot to make sure it does not interfere
        CourseFactory(page_parent=course.extended_object, should_publish=True)

        self.assertEqual(course_run.get_course(), course)
        self.assertEqual(course_run.public_course_run.get_course(),
                         course.public_extension)
Ejemplo n.º 23
0
    def test_admin_page_snapshot_with_cms_permissions(self):
        """
        Confirm the creation of a snapshot works as expected:
        - snapshot title and slug are set to a timestamp,
        - publication status of the course is respected on the snapshot,
        - course runs are moved below the snapshot,
        - publication status of course runs is respected,
        """
        user = UserFactory(is_staff=True)
        self.client.login(username=user.username, password="******")

        # Create a course page (not published in german)
        course = CourseFactory(
            page_title={"en": "a course", "fr": "un cours", "de": "ein Kurs"}
        )
        # Create a course run for this course
        course_run = CourseRunFactory(direct_course=course)

        self.assertTrue(course.extended_object.publish("en"))
        self.assertTrue(course.extended_object.publish("fr"))

        # It should have copied the course run to the published page
        self.assertEqual(CourseRun.objects.count(), 2)

        # 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, course.extended_object, can_change=True, can_add=True
        )

        # Trigger the creation of a snapshot for the course
        url = f"/en/admin/courses/course/{course.id:d}/snapshot/"
        now = datetime(2010, 1, 1, tzinfo=timezone.utc)
        with mock.patch.object(timezone, "now", return_value=now):
            response = self.client.post(url, follow=True)

        self.assertEqual(response.status_code, 200)
        content = json.loads(response.content)
        self.assertEqual(Course.objects.count(), 4)
        self.assertEqual(CourseRun.objects.count(), 2)

        snapshot = (
            Course.objects.exclude(id=course.id)
            .exclude(public_extension__isnull=True)
            .get()
        )
        self.assertEqual(content, {"id": snapshot.id})

        # The snapshot title and slug should include the version with datetime of snapshot
        expected_titles = {
            "en": "a course (Archived on 2010-01-01 00:00:00)",
            "fr": "un cours (Archived on 2010-01-01 00:00:00)",
            "de": "ein Kurs (Archived on 2010-01-01 00:00:00)",
        }
        for language in ["en", "fr", "de"]:
            self.assertEqual(
                snapshot.extended_object.get_title(language), expected_titles[language]
            )
            self.assertEqual(
                snapshot.extended_object.get_slug(language),
                "archived-on-2010-01-01-000000",
            )

        # The publication status of the course should be respected on the snapshot
        self.assertTrue(snapshot.check_publication("en"))
        self.assertTrue(snapshot.check_publication("fr"))
        self.assertFalse(snapshot.check_publication("de"))

        # The course run should have moved below the snapshot
        self.assertEqual(CourseRun.objects.count(), 2)
        course_run.refresh_from_db()
        self.assertEqual(course_run.direct_course, snapshot)