예제 #1
0
    def test_create_with_key(self):
        """ Verify the endpoint supports creating a course_run when specifying a key (if allowed). """
        course = self.course_run.course
        date_key = 'course-v1:{}+1T2000'.format(course.key.replace('/', '+'))
        desired_key = 'course-v1:{}+HowdyDoing'.format(
            course.key.replace('/', '+'))
        url = reverse('api:v1:course_run-list')

        data = {
            'course': course.key,
            'start': '2000-01-01T00:00:00Z',
            'end': '2001-01-01T00:00:00Z',
            'key': desired_key,
        }

        # If org doesn't specifically allow it, incoming key is ignored
        self.mock_post_to_studio(date_key)
        response = self.client.post(url, data, format='json')
        self.assertEqual(response.status_code, 201)
        new_course_run = CourseRun.objects.get(key=date_key)
        self.assertDictEqual(response.data,
                             self.serialize_course_run(new_course_run))

        # Turn on this feature for this org, notice that we can now specify the course key we want
        org_ext = OrganizationExtensionFactory(
            organization=course.authoring_organizations.first())
        org_ext.auto_create_in_studio = False  # badly named, but this controls whether we let org name their keys
        org_ext.save()
        self.mock_post_to_studio(desired_key, access_token=False)
        response = self.client.post(url, data, format='json')
        self.assertEqual(response.status_code, 201)
        new_course_run = CourseRun.objects.get(key=desired_key)
        self.assertDictEqual(response.data,
                             self.serialize_course_run(new_course_run))
예제 #2
0
    def setUpClass(cls):
        super().setUpClass()
        cls.user = UserFactory(is_staff=True)

        first_instructor = PersonFactory(given_name="First",
                                         family_name="Instructor")
        second_instructor = PersonFactory(given_name="Second",
                                          family_name="Instructor")
        cls.instructors = [first_instructor, second_instructor]

        cls.organizations = OrganizationFactory.create_batch(3)
        cls.organization_extensions = []

        for instructor in cls.instructors:
            PositionFactory(organization=cls.organizations[0],
                            title="professor",
                            person=instructor)

        for organization in cls.organizations:
            cls.organization_extensions.append(
                OrganizationExtensionFactory(organization=organization))

        disco_course = CourseFactory(
            authoring_organizations=[cls.organizations[0]])
        disco_course2 = CourseFactory(
            authoring_organizations=[cls.organizations[1]])
        CourseRunFactory(course=disco_course, staff=[first_instructor])
        CourseRunFactory(course=disco_course2, staff=[second_instructor])

        cls.user.groups.add(cls.organization_extensions[0].group)
    def test_course_without_editors(self):
        """ Verify we can modify a course with no editors if we're in its authoring org. """
        url = reverse('api:v1:course-detail', kwargs={'key': self.course.uuid})
        self.user.is_staff = False
        self.user.save()
        self.course.draft = True
        self.course.save()

        # Try without being in the organization nor an editor
        self.assertEqual(self.client.patch(url).status_code, 404)

        # Add to authoring org, and we should be let in
        org_ext = OrganizationExtensionFactory(organization=self.org)
        self.user.groups.add(org_ext.group)
        self.assertNotEqual(self.client.patch(url).status_code, 404)

        # Now add a random other user as an editor to the course, so that we will no longer be granted access.
        editor = UserFactory()
        CourseEditorFactory(user=editor, course=self.course)
        editor.groups.add(org_ext.group)
        self.assertEqual(self.client.patch(url).status_code, 404)

        # But if the editor is no longer valid (even though they exist), we're back to having access.
        editor.groups.remove(org_ext.group)
        self.assertNotEqual(self.client.patch(url).status_code, 404)

        # And finally, for a sanity check, confirm we have access when we become an editor also
        CourseEditorFactory(user=self.user, course=self.course)
        self.assertNotEqual(self.client.patch(url).status_code, 404)
예제 #4
0
    def setUp(self):
        super().setUp()
        self.org = OrganizationFactory(name='MyOrg', key='myorg')
        self.course_run = CourseRunFactory(draft=True,
                                           title_override='MyCourse')
        self.course = self.course_run.course
        self.course.authoring_organizations.add(self.org)
        self.partner = self.course.partner
        self.group = GroupFactory()
        self.pc = self.make_user(email='*****@*****.**')
        self.editor = self.make_user(groups=[self.group])
        self.editor2 = self.make_user(groups=[self.group])
        self.non_editor = self.make_user(groups=[self.group])
        self.legal = self.make_user(
            groups=[Group.objects.get(name=LEGAL_TEAM_GROUP_NAME)])

        CourseEditorFactory(user=self.editor, course=self.course)
        CourseEditorFactory(user=self.editor2, course=self.course)
        OrganizationExtensionFactory(group=self.group, organization=self.org)
        OrganizationUserRoleFactory(user=self.pc,
                                    organization=self.org,
                                    role=InternalUserRole.ProjectCoordinator)

        self.publisher_url = '{}courses/{}'.format(self.partner.publisher_url,
                                                   self.course_run.course.uuid)
        self.studio_url = '{}course/{}'.format(self.partner.studio_url,
                                               self.course_run.key)
        self.admin_url = 'https://{}/admin/course_metadata/courserun/{}/change/'.format(
            self.partner.site.domain, self.course_run.id)
        self.run_num = CourseKey.from_string(self.course_run.key).run
예제 #5
0
    def test_create_course_run_in_studio_with_image_failure(self, __):
        organization = OrganizationFactory()
        OrganizationExtensionFactory(organization=organization)
        partner = organization.partner
        start = datetime.datetime.utcnow()
        course_run_key = 'course-v1:TestX+Testing101x+1T2017'

        body = {'id': course_run_key}
        studio_url_root = partner.studio_url.strip('/')
        url = '{}/api/v1/course_runs/'.format(studio_url_root)
        responses.add(responses.POST, url, json=body, status=200)

        course = CourseFactory(organizations=[organization])

        with mock.patch('course_discovery.apps.api.utils.logger.exception'
                        ) as mock_logger:
            publisher_course_run = CourseRunFactory(
                course=course,
                start=start,
                lms_course_id=None,
            )

        assert mock_logger.call_count == 1
        assert mock_logger.call_args_list[0] == mock.call(
            'An error occurred while setting the course run image for [{key}] in studio. All other fields '
            'were successfully saved in Studio.'.format(key=course_run_key))

        publisher_course_run.refresh_from_db()
        assert len(responses.calls) == 2
        assert publisher_course_run.lms_course_id == course_run_key
    def test_editor_access_detail_endpoint(self, method, is_staff, in_org,
                                           is_editor, allowed):
        """ Verify we check editor access correctly when hitting the course object endpoint. """
        self.user.is_staff = is_staff
        self.user.save()

        # Add another editor, because we have some logic that allows access anyway if a course has no valid editors.
        # That code path is checked in test_course_without_editors below.
        org_ext = OrganizationExtensionFactory(organization=self.org)
        user2 = UserFactory()
        user2.groups.add(org_ext.group)
        CourseEditorFactory(user=user2, course=self.course)

        if in_org:
            # Editors must be in the org to get editor access
            self.user.groups.add(org_ext.group)

        if is_editor:
            CourseEditorFactory(user=self.user, course=self.course)

        response = getattr(self.client,
                           method)(reverse('api:v1:course-detail',
                                           kwargs={'key': self.course.uuid}))

        if not allowed:
            self.assertEqual(response.status_code, 404)
        else:
            # We'll probably fail because we didn't include the right data - but at least we'll have gotten in
            self.assertNotEqual(response.status_code, 404)
예제 #7
0
 def test_create_organizations_added_permissions(self):
     # Make sure created organization automatically have people permissions
     organization = OrganizationExtensionFactory()
     target_permissions = Permission.objects.filter(
         codename__in=['add_person', 'change_person', 'delete_person'])
     for permission in target_permissions:
         assert organization.group.permissions.filter(
             codename=permission.codename).exists()
예제 #8
0
 def setUp(self):
     super(CourseEditorsViewSetTests, self).setUp()
     self.staff_user = UserFactory(is_staff=True, is_superuser=True)
     self.client.login(username=self.staff_user.username, password=USER_PASSWORD)
     self.user = UserFactory()
     partner = Partner.objects.first()
     self.course = CourseFactory(draft=True, partner=partner)
     self.org_ext = OrganizationExtensionFactory()
     self.course.authoring_organizations.add(self.org_ext.organization)  # pylint: disable=no-member
예제 #9
0
 def setUp(self):
     super().setUp()
     self.organization = OrganizationFactory()
     self.organization_extension = OrganizationExtensionFactory()
     self.user = UserFactory()
     self.user.groups.add(self.organization_extension.group)
     self.course = CourseFactory(title='Test course')
     assign_perm(OrganizationExtension.VIEW_COURSE,
                 self.organization_extension.group,
                 self.organization_extension)
예제 #10
0
    def setup_course(self, **course_kwargs):
        """
        Creates the course and add organization and admin to this course.

        Returns:
            course: a course object
            course_admin: a user object
        """
        course = CourseFactory(**course_kwargs)
        course_admin = UserFactory(username='******')
        organization_extension = OrganizationExtensionFactory()
        organization = organization_extension.organization

        course_admin.groups.add(organization_extension.group)
        course.organizations.add(organization)
        return course, course_admin
예제 #11
0
    def test_create_course_run_in_studio_with_organization_opt_out(self):
        with mock.patch(
                'course_discovery.apps.publisher.signals.logger.warning'
        ) as mock_logger:
            course_organization = OrganizationFactory()
            OrganizationExtensionFactory(organization=course_organization,
                                         auto_create_in_studio=False)
            publisher_course_run = CourseRunFactory(
                course__organizations=[course_organization])

            mock_logger.assert_called_with(
                ('Course run [%d] will not be automatically created in studio.'
                 'Organization [%s] has opted out of this feature.'),
                publisher_course_run.course.id,
                course_organization.key,
            )
예제 #12
0
    def test_update(self):
        """
        Verify that we can update course workflow state with serializer.
        """
        course = self.course_state.course
        course.image = make_image_file('test_banner.jpg')
        course.save()
        course.organizations.add(OrganizationExtensionFactory().organization)

        self.assertNotEqual(self.course_state, CourseStateChoices.Review)
        serializer = self.serializer_class(self.course_state, context={'request': self.request})
        data = {'name': CourseStateChoices.Review}
        serializer.update(self.course_state, data)

        self.course_state = CourseState.objects.get(course=self.course_state.course)
        self.assertEqual(self.course_state.name, CourseStateChoices.Review)
        self.assertEqual(self.course_state.owner_role, PublisherUserRole.MarketingReviewer)
예제 #13
0
    def setup_course(self, **course_kwargs):
        """
        Creates the course and add organization and admin to this course.

        Returns:
            course: a course object
            course_admin: a user object
        """
        organization_extension = OrganizationExtensionFactory()
        defaults = {
            'organizations': [organization_extension.organization],
        }
        defaults.update(course_kwargs)
        course = CourseFactory(**defaults)

        course_admin = UserFactory()
        course_admin.groups.add(organization_extension.group)

        return course, course_admin
예제 #14
0
    def test_editor_access_list_endpoint(self, method, is_staff, in_org,
                                         allowed):
        """ Verify we check editor access correctly when hitting the courses endpoint. """
        self.user.is_staff = is_staff
        self.user.save()

        if in_org:
            org_ext = OrganizationExtensionFactory(organization=self.org)
            self.user.groups.add(org_ext.group)

        response = getattr(self.client, method)(reverse('api:v1:course-list'),
                                                {
                                                    'org': self.org.key
                                                },
                                                format='json')

        if not allowed:
            self.assertEqual(response.status_code, 403)
        else:
            self.assertNotEqual(response.status_code, 403)
예제 #15
0
    def test_create_if_in_org(self):
        """ Verify the endpoint supports creating a course_run with organization permissions. """
        url = reverse('api:v1:course_run-list')
        course = self.course_run.course
        data = {'course': course.key}

        self.user.is_staff = False
        self.user.save()

        # Not in org, not allowed to POST
        response = self.client.post(url, data, format='json')
        self.assertEqual(response.status_code, 403)

        # Add to org
        org_ext = OrganizationExtensionFactory(
            organization=course.authoring_organizations.first())
        self.user.groups.add(org_ext.group)

        # now allowed to POST
        response = self.client.post(url, data, format='json')
        self.assertEqual(response.status_code,
                         400)  # missing start, but at least we got that far
예제 #16
0
    def test_update_if_editor(self):
        """ Verify the endpoint supports updating a course_run with editor permissions. """
        self.mock_patch_to_studio(self.course_run.key)
        url = reverse('api:v1:course_run-detail',
                      kwargs={'key': self.course_run.key})

        self.user.is_staff = False
        self.user.save()

        # Not an editor, not allowed to patch
        response = self.client.patch(url, {}, format='json')
        self.assertEqual(response.status_code, 403)

        # Add as editor
        org_ext = OrganizationExtensionFactory(
            organization=self.course_run.course.authoring_organizations.first(
            ))
        self.user.groups.add(org_ext.group)
        CourseEditorFactory(user=self.user, course=self.course_run.course)

        # now allowed to patch
        response = self.client.patch(url, {}, format='json')
        self.assertEqual(response.status_code, 200)