def test_add_organization_course(self):
     """ Unit Test: test_add_organization_course """
     with self.assertNumQueries(2):
         api.add_organization_course(
             self.test_organization,
             self.test_course_key
         )
 def test_add_organization_course_bogus_course_key(self):
     """ Unit Test: test_add_organization_course_bogus_course_key """
     with self.assertNumQueries(0):
         with self.assertRaises(exceptions.InvalidCourseKeyException):
             api.add_organization_course(self.test_organization, '12345667avßßß')
     with self.assertNumQueries(0):
         with self.assertRaises(exceptions.InvalidCourseKeyException):
             api.add_organization_course(self.test_organization, None)
 def test_get_course_organizations(self):
     """ Unit Test: test_get_course_organizations """
     api.add_organization_course(
         self.test_organization,
         self.test_course_key
     )
     with self.assertNumQueries(1):
         course_organizations = api.get_course_organizations(self.test_course_key)
     self.assertEqual(len(course_organizations), 1)
 def test_add_organization_course_active_exists(self):
     """ Unit Test: test_add_organization_course_active_exists """
     api.add_organization_course(
         self.test_organization,
         self.test_course_key
     )
     with self.assertNumQueries(1):
         api.add_organization_course(
             self.test_organization,
             self.test_course_key
         )
 def test_add_organization_course_inactive_to_active(self):
     """ Unit Test: test_add_organization_course_inactive_to_active """
     api.add_organization_course(
         self.test_organization,
         self.test_course_key
     )
     api.remove_organization_course(self.test_organization, self.test_course_key)
     with self.assertNumQueries(3):
         api.add_organization_course(
             self.test_organization,
             self.test_course_key
         )
 def test_remove_organization_course(self):
     """ Unit Test: test_remove_organization_course """
     api.add_organization_course(
         self.test_organization,
         self.test_course_key
     )
     organizations = api.get_course_organizations(self.test_course_key)
     self.assertEqual(len(organizations), 1)
     with self.assertNumQueries(3):
         api.remove_organization_course(self.test_organization, self.test_course_key)
     organizations = api.get_course_organizations(self.test_course_key)
     self.assertEqual(len(organizations), 0)
 def test_remove_organization_course_missing_course(self):
     """ Unit Test: test_remove_organization_course_missing_organization """
     api.add_organization_course(
         self.test_organization,
         'edX/DemoX/Demo_Course'
     )
     organizations = api.get_course_organizations('edX/DemoX/Demo_Course')
     self.assertEqual(len(organizations), 1)
     with self.assertNumQueries(1):
         api.remove_organization_course(self.test_organization, self.test_course_key)
     organizations = api.get_course_organizations(self.test_course_key)
     self.assertEqual(len(organizations), 0)
    def test_remove_course_references(self):
        """ Unit Test: test_remove_course_references """
        # Add a course dependency on the test organization
        api.add_organization_course(
            self.test_organization,
            self.test_course_key
        )
        self.assertEqual(len(api.get_organization_courses(self.test_organization)), 1)

        # Remove the course dependency
        with self.assertNumQueries(2):
            api.remove_course_references(self.test_course_key)
        self.assertEqual(len(api.get_organization_courses(self.test_organization)), 0)
    def test_add_organization_inactive_organization_with_relationships(self):
        """ Unit Test: test_add_organization_inactive_organization_with_relationships"""
        organization_data = {
            'name': 'local_organizationßßß',
            'description': 'Local Organization Descriptionßßß'
        }
        organization = api.add_organization(organization_data)
        api.add_organization_course(
            organization,
            self.test_course_key
        )

        with self.assertNumQueries(1):
            organization = api.add_organization(organization_data)
def add_organization_course(organization_data, course_id):
    """
    Client API operation adapter/wrapper
    """
    if not organizations_enabled():
        return None
    from organizations import api as organizations_api
    return organizations_api.add_organization_course(organization_data=organization_data, course_key=course_id)
Example #11
0
def add_organization_course(organization_data, course_id):
    """
    Client API operation adapter/wrapper
    """
    if not organizations_enabled():
        return None
    from organizations import api as organizations_api
    return organizations_api.add_organization_course(organization_data=organization_data, course_key=course_id)
def add_organization_course(organization_data, course_id):
    """
    Client API operation adapter/wrapper
    """
    if not settings.FEATURES.get('ORGANIZATIONS_APP', False):
        return None
    from organizations import api as organizations_api
    return organizations_api.add_organization_course(organization_data=organization_data, course_key=course_id)
def add_organization_course(organization_data, course_id):
    """
    Client API operation adapter/wrapper
    """
    if not settings.FEATURES.get('ORGANIZATIONS_APP', False):
        return None
    from organizations import api as organizations_api
    return organizations_api.add_organization_course(
        organization_data=organization_data, course_key=course_id)
Example #14
0
    def test_add_several_organization_courses(self):
        """
        Test that the query_count of bulk_add_organization_courses does not increase
        when given more organization-course linkages to add.
        """
        org_a = api.add_organization(self.make_organization_data("org_a"))
        org_b = api.add_organization(self.make_organization_data("org_b"))
        org_c = api.add_organization(self.make_organization_data("org_c"))
        course_key_x = CourseKey.from_string("course-v1:x+x+x")
        course_key_y = CourseKey.from_string("course-v1:y+y+y")
        course_key_z = CourseKey.from_string("course-v1:z+z+z")

        # Add linkage A->X.
        api.add_organization_course(org_a, course_key_x)

        # Add linkage A->Y, and then remove (actually: deactivate).
        api.add_organization_course(org_a, course_key_y)
        api.remove_organization_course(org_a, course_key_y)

        # 1 query to load list of existing linkages,
        # 1 query to fetch organizations for existing linkages,
        # 1 query to ensure all existing linkages active,
        # 1 query to get organiations for new linkages,
        # 1 query to create new linkages.
        with self.assertNumQueries(5):
            api.bulk_add_organization_courses([
                (org_a, course_key_x),  # Already existing.
                (org_a, course_key_x),  # Already existing.
                (org_a, course_key_y),  # Reactivation.
                (org_a, course_key_z),  # The rest are new.
                (org_b, course_key_x),
                (org_b, course_key_y),
                (org_b, course_key_z),
                (org_c, course_key_x),
                (org_c, course_key_y),
                (org_c, course_key_z),
                (org_c, course_key_z),  # Redundant.
                (org_c, course_key_z),  # Redundant.
            ])
        assert len(api.get_organization_courses(org_a)) == 3
        assert len(api.get_organization_courses(org_b)) == 3
        assert len(api.get_organization_courses(org_c)) == 3
Example #15
0
 def test_get_course_organization_multi_linked_orgs(self):
     """
     Test that when a course is linked to multiple organizations,
     ``get_course_organization`` and ``get_course_organization_id``
     return the first-linked one.
     """
     # Use non-alphabetically-ordered org names to test that the
     # returned org was the first *linked*, not just the first *alphabetically*.
     api.add_organization_course(
         api.add_organization({
             'short_name': 'orgW',
             'name': 'Org West'
         }),
         self.test_course_key,
     )
     api.add_organization_course(
         api.add_organization({
             'short_name': 'orgN',
             'name': 'Org North'
         }),
         self.test_course_key,
     )
     api.add_organization_course(
         api.add_organization({
             'short_name': 'orgS',
             'name': 'Org South'
         }),
         self.test_course_key,
     )
     org_result = api.get_course_organization(self.test_course_key)
     assert org_result['short_name'] == 'orgW'
     org_id_result = api.get_course_organization_id(self.test_course_key)
     assert org_id_result
     assert org_id_result == org_result['id']
Example #16
0
def rerun_course(source_course_key_string,
                 destination_course_key_string,
                 user_id,
                 fields=None):
    """
    Reruns a course in a new celery task.
    """
    # import here, at top level this import prevents the celery workers from starting up correctly
    from edxval.api import copy_course_videos

    source_course_key = CourseKey.from_string(source_course_key_string)
    destination_course_key = CourseKey.from_string(
        destination_course_key_string)
    try:
        # deserialize the payload
        fields = deserialize_fields(fields) if fields else None

        # use the split modulestore as the store for the rerun course,
        # as the Mongo modulestore doesn't support multiple runs of the same course.
        store = modulestore()
        with store.default_store('split'):
            store.clone_course(source_course_key,
                               destination_course_key,
                               user_id,
                               fields=fields)

        # set initial permissions for the user to access the course.
        initialize_permissions(destination_course_key,
                               User.objects.get(id=user_id))

        # update state: Succeeded
        CourseRerunState.objects.succeeded(course_key=destination_course_key)

        # call edxval to attach videos to the rerun
        copy_course_videos(source_course_key, destination_course_key)

        # Copy OrganizationCourse
        organization_course = OrganizationCourse.objects.filter(
            course_id=source_course_key_string).first()

        if organization_course:
            clone_instance(organization_course,
                           {'course_id': destination_course_key_string})

        # Copy RestrictedCourse
        restricted_course = RestrictedCourse.objects.filter(
            course_key=source_course_key).first()

        if restricted_course:
            country_access_rules = CountryAccessRule.objects.filter(
                restricted_course=restricted_course)
            new_restricted_course = clone_instance(
                restricted_course, {'course_key': destination_course_key})
            for country_access_rule in country_access_rules:
                clone_instance(country_access_rule,
                               {'restricted_course': new_restricted_course})

        org_data = ensure_organization(source_course_key.org)
        add_organization_course(org_data, destination_course_key)
        return "succeeded"

    except DuplicateCourseError:
        # do NOT delete the original course, only update the status
        CourseRerunState.objects.failed(course_key=destination_course_key)
        LOGGER.exception('Course Rerun Error')
        return "duplicate course"

    # catch all exceptions so we can update the state and properly cleanup the course.
    except Exception as exc:  # pylint: disable=broad-except
        # update state: Failed
        CourseRerunState.objects.failed(course_key=destination_course_key)
        LOGGER.exception('Course Rerun Error')

        try:
            # cleanup any remnants of the course
            modulestore().delete_course(destination_course_key, user_id)
        except ItemNotFoundError:
            # it's possible there was an error even before the course module was created
            pass

        return "exception: " + str(exc)
Example #17
0
 def test_add_organization_course(self):
     """ Unit Test: test_add_organization_course """
     with self.assertNumQueries(2):
         api.add_organization_course(self.test_organization,
                                     self.test_course_key)
Example #18
0
    def test_end_to_end(self):
        """
        Test the happy path of the backfill command without any mocking.
        """
        # org_A: already existing, with courses and a library.
        org_a = add_organization({"short_name": "org_A", "name": "Org A"})
        course_a1_key = CourseOverviewFactory(org="org_A", run="1").id
        CourseOverviewFactory(org="org_A", run="2")
        LibraryFactory(org="org_A")

        # Write linkage for org_a->course_a1.
        # (Linkage for org_a->course_a2 is purposefully left out here;
        # it should be created by the backfill).
        add_organization_course(org_a, course_a1_key)

        # org_B: already existing, but has no content.
        add_organization({"short_name": "org_B", "name": "Org B"})

        # org_C: has a couple courses; should be created.
        CourseOverviewFactory(org="org_C", run="1")
        CourseOverviewFactory(org="org_C", run="2")

        # org_D: has both a course and a library; should be created.
        CourseOverviewFactory(org="org_D", run="1")
        LibraryFactory(org="org_D")

        # org_E: just has a library; should be created.
        LibraryFactory(org="org_E")

        # Confirm starting condition:
        # Only orgs are org_A and org_B, and only linkage is org_a->course_a1.
        assert set(org["short_name"]
                   for org in get_organizations()) == {"org_A", "org_B"}
        assert len(
            get_organization_courses(
                get_organization_by_short_name('org_A'))) == 1
        assert len(
            get_organization_courses(
                get_organization_by_short_name('org_B'))) == 0

        # Run the backfill.
        call_command("backfill_orgs_and_org_courses", "--apply")

        # Confirm ending condition:
        # All five orgs present. Each org a has expected number of org-course linkages.
        assert set(org["short_name"] for org in get_organizations()) == {
            "org_A", "org_B", "org_C", "org_D", "org_E"
        }
        assert len(
            get_organization_courses(
                get_organization_by_short_name('org_A'))) == 2
        assert len(
            get_organization_courses(
                get_organization_by_short_name('org_B'))) == 0
        assert len(
            get_organization_courses(
                get_organization_by_short_name('org_C'))) == 2
        assert len(
            get_organization_courses(
                get_organization_by_short_name('org_D'))) == 1
        assert len(
            get_organization_courses(
                get_organization_by_short_name('org_E'))) == 0
Example #19
0
    def test_end_to_end(self, run_type):
        """
        Test the happy path of the backfill command without any mocking.
        """
        # org_A: already existing, with courses and a library.
        org_a = add_organization({"short_name": "org_A", "name": "Org A"})
        course_a1_key = CourseOverviewFactory(org="org_A", run="1").id
        CourseOverviewFactory(org="org_A", run="2")
        LibraryFactory(org="org_A")

        # Write linkage for org_a->course_a1.
        # (Linkage for org_a->course_a2 is purposefully left out here;
        # it should be created by the backfill).
        add_organization_course(org_a, course_a1_key)

        # org_B: already existing, but has no content.
        add_organization({"short_name": "org_B", "name": "Org B"})

        # org_C: has a few courses; should be created.
        CourseOverviewFactory(org="org_C", run="1")
        CourseOverviewFactory(org="org_C", run="2")
        # Include an Old Mongo Modulestore -style deprecated course key.
        # This can be safely removed when Old Mongo Modulestore support is
        # removed.
        CourseOverviewFactory(
            id=CourseLocator.from_string("org_C/toy/3"),
            org="org_C",
            run="3",
        )

        # org_D: has both a course and a library; should be created.
        CourseOverviewFactory(org="org_D", run="1")
        LibraryFactory(org="org_D")

        # org_E: just has a library; should be created.
        LibraryFactory(org="org_E")

        # Confirm starting condition:
        # Only orgs are org_A and org_B, and only linkage is org_a->course_a1.
        assert set(
            org["short_name"] for org in get_organizations()
        ) == {
            "org_A", "org_B"
        }
        assert len(get_organization_courses(get_organization_by_short_name('org_A'))) == 1
        assert len(get_organization_courses(get_organization_by_short_name('org_B'))) == 0

        # Run the backfill.
        call_command("backfill_orgs_and_org_courses", run_type)

        if run_type == "--dry":
            # Confirm ending conditions are the same as the starting conditions.
            assert set(
                org["short_name"] for org in get_organizations()
            ) == {
                "org_A", "org_B"
            }
            assert len(get_organization_courses(get_organization_by_short_name('org_A'))) == 1
            assert len(get_organization_courses(get_organization_by_short_name('org_B'))) == 0
        else:
            # Confirm ending condition:
            # All five orgs present. Each org a has expected number of org-course linkages.
            assert set(
                org["short_name"] for org in get_organizations()
            ) == {
                "org_A", "org_B", "org_C", "org_D", "org_E"
            }
            assert len(get_organization_courses(get_organization_by_short_name('org_A'))) == 2
            assert len(get_organization_courses(get_organization_by_short_name('org_B'))) == 0
            assert len(get_organization_courses(get_organization_by_short_name('org_C'))) == 3
            assert len(get_organization_courses(get_organization_by_short_name('org_D'))) == 1
            assert len(get_organization_courses(get_organization_by_short_name('org_E'))) == 0
Example #20
0
    def test_edge_cases(self, mock_log_info):
        """
        Test that bulk_add_organization_courses handles a few edge cases as expected.
        """
        org_a = api.add_organization(self.make_organization_data("org_a"))
        org_b = api.add_organization(self.make_organization_data("org_b"))
        course_key_x = CourseKey.from_string("course-v1:x+x+x")
        course_key_y = CourseKey.from_string("course-v1:y+y+y")
        course_key_z = CourseKey.from_string("course-v1:z+z+z")

        # Add linkage A->X
        api.add_organization_course(org_a, course_key_x)

        # Add and then remove (under the hood: deactivate) linkage between A->Y.
        api.add_organization_course(org_a, course_key_y)
        api.remove_organization_course(org_a, course_key_y)

        # Add and then remove (under the hood: deactivate) linkage between A->Z.
        # This should NOT be reactivated, as we don't include it in the bulk_add call.
        api.add_organization_course(org_a, course_key_z)
        api.remove_organization_course(org_a, course_key_z)

        # 1 query to load list of existing linkages,
        # 1 query to fetch organizations for existing linkages,
        # 1 query to ensure all existing linkages active,
        # 1 query to get organiations for new linkages,
        # 1 query to create new linkages.
        with self.assertNumQueries(5):
            api.bulk_add_organization_courses([

                # A->X: Existing linkage, should be a no-op.
                (org_a, course_key_x),

                # B->Y: Should create new linkage.
                (org_b, course_key_y),

                # A->Y: Is an inactive linkage; should be re-activated.
                (org_a, course_key_y),

                # B->Y: Is already in this list; shouldn't affect anything.
                (org_b, course_key_y),

                # B->Z: Adding with a stringified course id; should work as if we
                #       used the course key object.
                (org_b, str(course_key_z)),

                # B->Z: Adding again with the course key object; should be a no-op.
                (org_b, course_key_z),
            ])

        # Org A was linked to courses X and Y.
        # Org A also has an inactive link to course Z that we never re-activated.
        org_a_courses = api.get_organization_courses(org_a)
        assert {org_course["course_id"]
                for org_course in org_a_courses
                } == {"course-v1:x+x+x", "course-v1:y+y+y"}

        # Org B was linked to courses Y and Z.
        org_b_courses = api.get_organization_courses(org_b)
        assert {org_course["course_id"]
                for org_course in org_b_courses
                } == {"course-v1:y+y+y", "course-v1:z+z+z"}

        # Based on logging messages, make sure the expected breakdown of
        #    created vs. reactivated vs. not touched
        # is true for the org-course linkages passed to `bulk_add_organization_courses`.
        logged_linkages_to_reactivate = mock_log_info.call_args_list[0][0][2]
        assert set(logged_linkages_to_reactivate) == {
            ("org_a", str(course_key_y)),
        }
        logged_linkages_to_create = mock_log_info.call_args_list[1][0][2]
        assert set(logged_linkages_to_create) == {
            ("org_b", str(course_key_y)),
            ("org_b", str(course_key_z)),
        }