def setUp(self): self.client = Client() # Mocks api.api_enabled = self.mock_api_enabled(True) # Create two accounts self.password = '******' self.student = User.objects.create_user('student', '*****@*****.**', self.password) self.student2 = User.objects.create_user('student2', '*****@*****.**', self.password) self.instructor = User.objects.create_user('instructor', '*****@*****.**', self.password) self.course_key = SlashSeparatedCourseKey('HarvardX', 'CB22x', 'The_Ancient_Greek_Hero') self.note = { 'user': self.student, 'course_id': self.course_key, 'uri': '/', 'text': 'foo', 'quote': 'bar', 'range_start': 0, 'range_start_offset': 0, 'range_end': 100, 'range_end_offset': 0, 'tags': 'a,b,c' } # Make sure no note with this ID ever exists for testing purposes self.NOTE_ID_DOES_NOT_EXIST = 99999
def test_delete_course_map(self): """ Test that course location is properly remove from loc_mapper and cache when course is deleted """ org = u'foo_org' course = u'bar_course' run = u'baz_run' course_location = SlashSeparatedCourseKey(org, course, run) loc_mapper().create_map_entry(course_location) # pylint: disable=protected-access entry = loc_mapper().location_map.find_one({ '_id': loc_mapper()._construct_course_son(course_location) }) self.assertIsNotNone(entry, 'Entry not found in loc_mapper') self.assertEqual(entry['offering'], u'{1}.{2}'.format(org, course, run)) # now delete course location from loc_mapper and cache and test that course location no longer # exists in loca_mapper and cache loc_mapper().delete_course_mapping(course_location) # pylint: disable=protected-access entry = loc_mapper().location_map.find_one({ '_id': loc_mapper()._construct_course_son(course_location) }) self.assertIsNone(entry, 'Entry found in loc_mapper') # pylint: disable=protected-access cached_value = loc_mapper()._get_location_from_cache(course_location.make_usage_key('course', run)) self.assertIsNone(cached_value, 'course_locator found in cache') # pylint: disable=protected-access cached_value = loc_mapper()._get_course_location_from_cache(course_location) self.assertIsNone(cached_value, 'Entry found in cache')
def setUp(self): self.student = '*****@*****.**' self.instructor = '*****@*****.**' self.password = '******' self.create_account('u1', self.student, self.password) self.create_account('u2', self.instructor, self.password) self.activate_user(self.student) self.activate_user(self.instructor) self.course_id = SlashSeparatedCourseKey("edX", "toy", "2012_Fall") self.location_string = self.course_id.make_usage_key('html', 'TestLocation').to_deprecated_string() self.toy = modulestore().get_course(self.course_id) location = "i4x://edX/toy/peergrading/init" field_data = DictFieldData({'data': "<peergrading/>", 'location': location, 'category':'peergrading'}) self.mock_service = peer_grading_service.MockPeerGradingService() self.system = LmsModuleSystem( static_url=settings.STATIC_URL, track_function=None, get_module=None, render_template=render_to_string, replace_urls=None, s3_interface=test_util_open_ended.S3_INTERFACE, open_ended_grading_interface=test_util_open_ended.OPEN_ENDED_GRADING_INTERFACE, mixins=settings.XBLOCK_MIXINS, error_descriptor_class=ErrorDescriptor, descriptor_runtime=None, ) self.descriptor = peer_grading_module.PeerGradingDescriptor(self.system, field_data, ScopeIds(None, None, None, None)) self.descriptor.xmodule_runtime = self.system self.peer_module = self.descriptor self.peer_module.peer_gs = self.mock_service self.logout()
def test_form_typo(self): # Munge course id bad_id = SlashSeparatedCourseKey( u'Broken{}'.format(self.course.id.org), '', self.course.id.run + '_typo') form_data = { 'course_id': bad_id.to_deprecated_string(), 'email_enabled': True } form = CourseAuthorizationAdminForm(data=form_data) # Validation shouldn't work self.assertFalse(form.is_valid()) msg = u'COURSE NOT FOUND' msg += u' --- Entered course id was: "{0}". '.format( bad_id.to_deprecated_string()) msg += 'Please recheck that you have supplied a valid course id.' self.assertEquals(msg, form._errors['course_id'][0]) # pylint: disable=protected-access with self.assertRaisesRegexp( ValueError, "The CourseAuthorization could not be created because the data didn't validate." ): form.save()
def test_basic(self): upload_date = datetime(2013, 6, 1, 10, 30, tzinfo=UTC) course_key = SlashSeparatedCourseKey('org', 'class', 'run') location = course_key.make_asset_key('asset', 'my_file_name.jpg') thumbnail_location = course_key.make_asset_key( 'thumbnail', 'my_file_name_thumb.jpg') output = assets._get_asset_json("my_file", upload_date, location, thumbnail_location, True) self.assertEquals(output["display_name"], "my_file") self.assertEquals(output["date_added"], "Jun 01, 2013 at 10:30 UTC") self.assertEquals(output["url"], "/c4x/org/class/asset/my_file_name.jpg") self.assertEquals(output["external_url"], "lms_base_url/c4x/org/class/asset/my_file_name.jpg") self.assertEquals(output["portable_url"], "/static/my_file_name.jpg") self.assertEquals(output["thumbnail"], "/c4x/org/class/thumbnail/my_file_name_thumb.jpg") self.assertEquals(output["id"], unicode(location)) self.assertEquals(output['locked'], True) output = assets._get_asset_json("name", upload_date, location, None, False) self.assertIsNone(output["thumbnail"])
def is_enrolled_by_partial(cls, user, course_id_partial): """ Returns `True` if the user is enrolled in a course that starts with `course_id_partial`. Otherwise, returns False. Can be used to determine whether a student is enrolled in a course whose run name is unknown. `user` is a Django User object. If it hasn't been saved yet (no `.id` attribute), this method will automatically save it before adding an enrollment for it. `course_id_partial` (CourseKey) is missing the run component """ assert isinstance(course_id_partial, SlashSeparatedCourseKey) assert not course_id_partial.run # None or empty string course_key = SlashSeparatedCourseKey(course_id_partial.org, course_id_partial.course, '') querystring = unicode(course_key.to_deprecated_string()) try: return CourseEnrollment.objects.filter( user=user, course_id__startswith=querystring, is_active=1 ).exists() except cls.DoesNotExist: return False
def setUp(self): self.course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall') self.location = self.course_key.make_usage_key('chapter', 'Overview') self.toy_course = modulestore().get_course(self.course_key) self.mock_user = UserFactory() self.mock_user.id = 1 self.request_factory = RequestFactory() # Construct a mock module for the modulestore to return self.mock_module = MagicMock() self.mock_module.id = 1 self.dispatch = 'score_update' # Construct a 'standard' xqueue_callback url self.callback_url = reverse('xqueue_callback', kwargs={ 'course_id': self.course_key.to_deprecated_string(), 'userid': str(self.mock_user.id), 'mod_id': self.mock_module.id, 'dispatch': self.dispatch })
def test_get_courses_for_wiki(self): """ Test the get_courses_for_wiki method """ for course_number in self.courses: course_locations = self.store.get_courses_for_wiki(course_number) assert_equals(len(course_locations), 1) assert_equals(Location('edX', course_number, '2012_Fall', 'course', '2012_Fall'), course_locations[0]) course_locations = self.store.get_courses_for_wiki('no_such_wiki') assert_equals(len(course_locations), 0) # set toy course to share the wiki with simple course toy_course = self.store.get_course(SlashSeparatedCourseKey('edX', 'toy', '2012_Fall')) toy_course.wiki_slug = 'simple' self.store.update_item(toy_course) # now toy_course should not be retrievable with old wiki_slug course_locations = self.store.get_courses_for_wiki('toy') assert_equals(len(course_locations), 0) # but there should be two courses with wiki_slug 'simple' course_locations = self.store.get_courses_for_wiki('simple') assert_equals(len(course_locations), 2) for course_number in ['toy', 'simple']: assert_in(Location('edX', course_number, '2012_Fall', 'course', '2012_Fall'), course_locations) # configure simple course to use unique wiki_slug. simple_course = self.store.get_course(SlashSeparatedCourseKey('edX', 'simple', '2012_Fall')) simple_course.wiki_slug = 'edX.simple.2012_Fall' self.store.update_item(simple_course) # it should be retrievable with its new wiki_slug course_locations = self.store.get_courses_for_wiki('edX.simple.2012_Fall') assert_equals(len(course_locations), 1) assert_in(Location('edX', 'simple', '2012_Fall', 'course', '2012_Fall'), course_locations)
def test_errored_course_regular_access(self): """ Test the course list for regular staff when get_course returns an ErrorDescriptor """ GlobalStaff().remove_users(self.user) CourseStaffRole(SlashSeparatedCourseKey('Non', 'Existent', 'Course')).add_users(self.user) course_key = SlashSeparatedCourseKey('Org1', 'Course1', 'Run1') self._create_course_with_access_groups(course_key, self.user) with patch('xmodule.modulestore.mongo.base.MongoKeyValueStore', Mock(side_effect=Exception)): self.assertIsInstance(modulestore().get_course(course_key), ErrorDescriptor) # get courses through iterating all courses courses_list = _accessible_courses_list(self.request) self.assertEqual(courses_list, []) # get courses by reversing group name formats courses_list_by_groups = _accessible_courses_list_from_groups( self.request) self.assertEqual(courses_list_by_groups, []) self.assertEqual(courses_list, courses_list_by_groups)
def setUp(self): self.course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall') self.course_loc = self.course_key.make_usage_key('course', '2012_Fall') self.anonymous_user = AnonymousUserFactory() self.student = UserFactory() self.global_staff = UserFactory(is_staff=True) self.course_staff = StaffFactory(course_key=self.course_key) self.course_instructor = InstructorFactory(course_key=self.course_key)
def setUp(self): self.course = CourseFactory.create() self.page = ItemFactory.create(category="static_tab", parent_location=self.course.location, data="OOGIE BLOOGIE", display_name="new_tab") self.toy_course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall')
def setUp(self): course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall') self.course = course_key.make_usage_key('course', course_key.run) self.anonymous_user = AnonymousUserFactory() self.student = UserFactory() self.global_staff = UserFactory(is_staff=True) self.course_staff = StaffFactory(course_key=self.course.course_key) self.course_instructor = InstructorFactory(course_key=self.course.course_key)
def test_invalid_chars_ssck(self): """ Test that the ssck constructor fails if given invalid chars """ valid_base = SlashSeparatedCourseKey(u'org.dept-1%2', u'course.sub-2%3', u'run.faster-4%5') for key in SlashSeparatedCourseKey.KEY_FIELDS: with self.assertRaises(InvalidKeyError): # this ends up calling the constructor where the legality check should occur valid_base.replace(**{key: u'funny thing'})
def setUp(self): self.user = UserFactory.create(username="******", password="******") self.client.login(username="******", password="******") self.course_key = SlashSeparatedCourseKey("Robot", "999", "Test_Course") CourseFactory.create(org='Robot', number='999', display_name='Test Course') patcher = patch('student.models.tracker') self.mock_tracker = patcher.start() self.addCleanup(patcher.stop)
def i_click_on_error_dialog(step): world.click_link_by_text('Correct failed component') assert_true(world.css_html("span.inline-error").startswith("Problem i4x://MITx/999/problem")) course_key = SlashSeparatedCourseKey("MITx", "999", "Robot_Super_Course") # we don't know the actual ID of the vertical. So just check that we did go to a # vertical page in the course (there should only be one). vertical_usage_key = course_key.make_usage_key("vertical", "") vertical_url = reverse_usage_url('unit_handler', vertical_usage_key) assert_equal(1, world.browser.url.count(vertical_url))
def setUp(self): self.data = {'foo': 'foo_value'} self.course_id = SlashSeparatedCourseKey('org', 'course', 'run') self.children = [ self.course_id.make_usage_key('child', 'a'), self.course_id.make_usage_key('child', 'b') ] self.metadata = {'meta': 'meta_val'} self.kvs = MongoKeyValueStore(self.data, self.children, self.metadata)
def test_get_course_list_with_same_course_id(self): """ Test getting courses with same id but with different name case. Then try to delete one of them and check that it is properly deleted and other one is accessible """ # create and log in a non-staff user self.user = UserFactory() request = self.factory.get('/course') request.user = self.user self.client.login(username=self.user.username, password='******') course_location_caps = SlashSeparatedCourseKey('Org', 'COURSE', 'Run') self._create_course_with_access_groups(course_location_caps, self.user) # get courses through iterating all courses courses_list = _accessible_courses_list(request) self.assertEqual(len(courses_list), 1) # get courses by reversing group name formats courses_list_by_groups = _accessible_courses_list_from_groups(request) self.assertEqual(len(courses_list_by_groups), 1) # check both course lists have same courses self.assertEqual(courses_list, courses_list_by_groups) # now create another course with same course_id but different name case course_location_camel = SlashSeparatedCourseKey('Org', 'Course', 'Run') self._create_course_with_access_groups(course_location_camel, self.user) # test that get courses through iterating all courses returns both courses courses_list = _accessible_courses_list(request) self.assertEqual(len(courses_list), 2) # test that get courses by reversing group name formats returns both courses courses_list_by_groups = _accessible_courses_list_from_groups(request) self.assertEqual(len(courses_list_by_groups), 2) # now delete first course (course_location_caps) and check that it is no longer accessible delete_course_and_groups(course_location_caps, commit=True) # test that get courses through iterating all courses now returns one course courses_list = _accessible_courses_list(request) self.assertEqual(len(courses_list), 1) # test that get courses by reversing group name formats also returns one course courses_list_by_groups = _accessible_courses_list_from_groups(request) self.assertEqual(len(courses_list_by_groups), 1) # now check that deleted course is not accessible outline_url = reverse_course_url('course_handler', course_location_caps) response = self.client.get(outline_url, HTTP_ACCEPT='application/json') self.assertEqual(response.status_code, 403) # now check that other course is accessible outline_url = reverse_course_url('course_handler', course_location_camel) response = self.client.get(outline_url, HTTP_ACCEPT='application/json') self.assertEqual(response.status_code, 200)
class TestInstructorEnrollmentStudentModule(TestCase): """ Test student module manipulations. """ def setUp(self): self.course_key = SlashSeparatedCourseKey('fake', 'course', 'id') def test_reset_student_attempts(self): user = UserFactory() msk = self.course_key.make_usage_key('dummy', 'module') original_state = json.dumps({'attempts': 32, 'otherstuff': 'alsorobots'}) StudentModule.objects.create(student=user, course_id=self.course_key, module_state_key=msk, state=original_state) # lambda to reload the module state from the database module = lambda: StudentModule.objects.get(student=user, course_id=self.course_key, module_state_key=msk) self.assertEqual(json.loads(module().state)['attempts'], 32) reset_student_attempts(self.course_key, user, msk) self.assertEqual(json.loads(module().state)['attempts'], 0) def test_delete_student_attempts(self): user = UserFactory() msk = self.course_key.make_usage_key('dummy', 'module') original_state = json.dumps({'attempts': 32, 'otherstuff': 'alsorobots'}) StudentModule.objects.create(student=user, course_id=self.course_key, module_state_key=msk, state=original_state) self.assertEqual(StudentModule.objects.filter(student=user, course_id=self.course_key, module_state_key=msk).count(), 1) reset_student_attempts(self.course_key, user, msk, delete_module=True) self.assertEqual(StudentModule.objects.filter(student=user, course_id=self.course_key, module_state_key=msk).count(), 0) def test_delete_submission_scores(self): user = UserFactory() problem_location = self.course_key.make_usage_key('dummy', 'module') # Create a student module for the user StudentModule.objects.create( student=user, course_id=self.course_key, module_state_key=problem_location, state=json.dumps({}) ) # Create a submission and score for the student using the submissions API student_item = { 'student_id': anonymous_id_for_user(user, self.course_key), 'course_id': self.course_key.to_deprecated_string(), 'item_id': problem_location.to_deprecated_string(), 'item_type': 'openassessment' } submission = sub_api.create_submission(student_item, 'test answer') sub_api.set_score(submission['uuid'], 1, 2) # Delete student state using the instructor dash reset_student_attempts( self.course_key, user, problem_location, delete_module=True ) # Verify that the student's scores have been reset in the submissions API score = sub_api.get_score(student_item) self.assertIs(score, None)
def test_sandbox_inclusion(self): """ Test to make sure that a match works across course runs """ self.assertTrue( can_execute_unsafe_code( SlashSeparatedCourseKey('edX', 'full', '2012_Fall'))) self.assertTrue( can_execute_unsafe_code( SlashSeparatedCourseKey('edX', 'full', '2013_Spring')))
def test_courses_with_unsafe_code_default(self): """ Test that the default setting for COURSES_WITH_UNSAFE_CODE is an empty setting, e.g. we don't use @override_settings in these tests """ self.assertFalse( can_execute_unsafe_code( SlashSeparatedCourseKey('edX', 'full', '2012_Fall'))) self.assertFalse( can_execute_unsafe_code( SlashSeparatedCourseKey('edX', 'full', '2013_Spring')))
def setup_xml_course(self): """ Define the XML backed course to use. Toy courses are already loaded in XML and mixed modulestores. """ course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall') location = course_key.make_usage_key('chapter', 'Overview') descriptor = modulestore().get_item(location) self.module = self._get_module(course_key, descriptor, location)
def setUp(self): course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall') self.course = course_key.make_usage_key('course', course_key.run) self.anonymous_user = AnonymousUserFactory() self.student = UserFactory() self.global_staff = UserFactory(is_staff=True) # TODO please change the StaffFactory and InstructorFactory parameters ASAP! self.course_staff = StaffFactory(course=self.course.course_key) self.course_instructor = InstructorFactory( course=self.course.course_key)
def setup_xml_course(self): """ Define the XML backed course to use. Toy courses are already loaded in XML and mixed modulestores. """ course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall') location = course_key.make_usage_key('chapter', 'Overview') descriptor = modulestore().get_item(location) self.module = self._get_module(course_key, descriptor, location)
def setUp(self): system = get_test_descriptor_system() course_key = SlashSeparatedCourseKey('org', 'course', 'run') usage_key = course_key.make_usage_key('video', 'name') self.descriptor = system.construct_xblock_from_class( VideoDescriptor, scope_ids=ScopeIds(None, None, usage_key, usage_key), field_data=DictFieldData({}), ) self.descriptor.runtime.handler_url = MagicMock()
def test_invalid_chars_location(self): """ Test that the location constructor fails if given invalid chars """ course_key = SlashSeparatedCourseKey(u'org.dept-1%2', u'course.sub-2%3', u'run.faster-4%5') valid_base = course_key.make_usage_key('tomato-again%9', 'block-head:sub-4%9') for key in SlashSeparatedCourseKey.KEY_FIELDS: with self.assertRaises(InvalidKeyError): # this ends up calling the constructor where the legality check should occur valid_base.replace(**{key: u'funny thing'})
def setUp(self): system = get_test_descriptor_system() course_key = SlashSeparatedCourseKey('org', 'course', 'run') usage_key = course_key.make_usage_key('video', 'name') self.descriptor = system.construct_xblock_from_class( VideoDescriptor, scope_ids=ScopeIds(None, None, usage_key, usage_key), field_data=DictFieldData({}), ) self.descriptor.runtime.handler_url = MagicMock()
def test_rewrite_reference_list(self): module_store = modulestore('direct') target_course_id = SlashSeparatedCourseKey('testX', 'conditional_copy', 'copy_run') import_from_xml( module_store, 'common/test/data/', ['conditional'], target_course_id=target_course_id ) conditional_module = module_store.get_item( target_course_id.make_usage_key('conditional', 'condone') ) self.assertIsNotNone(conditional_module) different_course_id = SlashSeparatedCourseKey('edX', 'different_course', 'copy_run') self.assertListEqual( [ target_course_id.make_usage_key('problem', 'choiceprob'), different_course_id.make_usage_key('html', 'for_testing_import_rewrites') ], conditional_module.sources_list ) self.assertListEqual( [ target_course_id.make_usage_key('html', 'congrats'), target_course_id.make_usage_key('html', 'secret_page') ], conditional_module.show_tag_list )
def create_course(self, org, offering, user_id=None, fields=None, **kwargs): """ Creates and returns the course. Args: org (str): the organization that owns the course offering (str): the name of the course offering user_id: id of the user creating the course fields (dict): Fields to set on the course at initialization kwargs: Any optional arguments understood by a subset of modulestores to customize instantiation Returns: a CourseDescriptor Raises: InvalidLocationError: If a course with the same org and offering already exists """ course, _, run = offering.partition('/') course_id = SlashSeparatedCourseKey(org, course, run) # Check if a course with this org/course has been defined before (case-insensitive) course_search_location = SON([ ('_id.tag', 'i4x'), ('_id.org', re.compile(u'^{}$'.format(course_id.org), re.IGNORECASE)), ('_id.course', re.compile(u'^{}$'.format(course_id.course), re.IGNORECASE)), ('_id.category', 'course'), ]) courses = self.collection.find(course_search_location, fields=('_id')) if courses.count() > 0: raise InvalidLocationError( "There are already courses with the given org and course id: {}" .format([course['_id'] for course in courses])) location = course_id.make_usage_key('course', course_id.run) course = self.create_and_save_xmodule(location, fields=fields, **kwargs) # clone a default 'about' overview module as well about_location = location.replace(category='about', name='overview') overview_template = AboutDescriptor.get_template('overview.yaml') self.create_and_save_xmodule( about_location, system=course.system, definition_data=overview_template.get('data')) return course
def i_click_on_error_dialog(step): world.click_link_by_text('Correct failed component') assert_true( world.css_html("span.inline-error").startswith( "Problem i4x://MITx/999/problem")) course_key = SlashSeparatedCourseKey("MITx", "999", "Robot_Super_Course") # we don't know the actual ID of the vertical. So just check that we did go to a # vertical page in the course (there should only be one). vertical_usage_key = course_key.make_usage_key("vertical", "") vertical_url = reverse_usage_url('unit_handler', vertical_usage_key) assert_equal(1, world.browser.url.count(vertical_url))
def setUp(self): """ Add a user and a course """ super(TestUsersDefaultRole, self).setUp() # create and log in a staff user. self.user = UserFactory(is_staff=True) # pylint: disable=no-member self.client = AjaxEnabledTestClient() self.client.login(username=self.user.username, password='******') # create a course via the view handler to create course self.course_key = SlashSeparatedCourseKey('Org_1', 'Course_1', 'Run_1') self._create_course_with_given_location(self.course_key)
def setUp(self): self.course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall') # Create instructor account instructor = AdminFactory.create() self.client.login(username=instructor.username, password="******") # URL for instructor dash self.url = reverse( 'instructor_dashboard', kwargs={'course_id': self.course_key.to_deprecated_string()}) # URL for email view self.email_link = '<a href="" data-section="send_email">Email</a>'
def create_course(self, org, offering, user_id=None, fields=None, **kwargs): """ Creates and returns the course. Args: org (str): the organization that owns the course offering (str): the name of the course offering user_id: id of the user creating the course fields (dict): Fields to set on the course at initialization kwargs: Any optional arguments understood by a subset of modulestores to customize instantiation Returns: a CourseDescriptor Raises: InvalidLocationError: If a course with the same org and offering already exists """ course, _, run = offering.partition('/') course_id = SlashSeparatedCourseKey(org, course, run) # Check if a course with this org/course has been defined before (case-insensitive) course_search_location = SON([ ('_id.tag', 'i4x'), ('_id.org', re.compile(u'^{}$'.format(course_id.org), re.IGNORECASE)), ('_id.course', re.compile(u'^{}$'.format(course_id.course), re.IGNORECASE)), ('_id.category', 'course'), ]) courses = self.collection.find(course_search_location, fields=('_id')) if courses.count() > 0: raise InvalidLocationError( "There are already courses with the given org and course id: {}".format([ course['_id'] for course in courses ])) location = course_id.make_usage_key('course', course_id.run) course = self.create_and_save_xmodule(location, fields=fields, **kwargs) # clone a default 'about' overview module as well about_location = location.replace( category='about', name='overview' ) overview_template = AboutDescriptor.get_template('overview.yaml') self.create_and_save_xmodule( about_location, system=course.system, definition_data=overview_template.get('data') ) return course
def handle(self, *args, **options): source_key = SlashSeparatedCourseKey.from_deprecated_string(options['source_course']) dest_key = SlashSeparatedCourseKey.from_deprecated_string(options['dest_course']) source_students = User.objects.filter( courseenrollment__course_id=source_key ) for user in source_students: if CourseEnrollment.is_enrolled(user, dest_key): # Un Enroll from source course but don't mess # with the enrollment in the destination course. CourseEnrollment.unenroll(user, source_key) print("Unenrolled {} from {}".format(user.username, source_key.to_deprecated_string())) msg = "Skipping {}, already enrolled in destination course {}" print(msg.format(user.username, dest_key.to_deprecated_string())) continue print("Moving {}.".format(user.username)) # Find the old enrollment. enrollment = CourseEnrollment.objects.get( user=user, course_id=source_key ) # Move the Student between the classes. mode = enrollment.mode old_is_active = enrollment.is_active CourseEnrollment.unenroll(user, source_key) new_enrollment = CourseEnrollment.enroll(user, dest_key, mode=mode) # Unenroll from the new coures if the user had unenrolled # form the old course. if not old_is_active: new_enrollment.update_enrollment(is_active=False) if mode == 'verified': try: certificate_item = CertificateItem.objects.get( course_id=source_key, course_enrollment=enrollment ) except CertificateItem.DoesNotExist: print("No certificate for {}".format(user)) continue certificate_item.course_id = dest_key certificate_item.course_enrollment = new_enrollment certificate_item.save()
def test_graphicslidertool_import(self): ''' Check to see if definition_from_xml in gst_module.py works properly. Pulls data from the graphic_slider_tool directory in the test data directory. ''' modulestore = XMLModuleStore(DATA_DIR, course_dirs=['graphic_slider_tool']) sa_id = SlashSeparatedCourseKey("edX", "gst_test", "2012_Fall") location = sa_id.make_usage_key("graphical_slider_tool", "sample_gst") gst_sample = modulestore.get_item(location) render_string_from_sample_gst_xml = """ <slider var="a" style="width:400px;float:left;"/>\ <plot style="margin-top:15px;margin-bottom:15px;"/>""".strip() self.assertIn(render_string_from_sample_gst_xml, gst_sample.data)
def lms_link_test(self): """ Tests get_lms_link_for_item. """ course_key = SlashSeparatedCourseKey("mitX", "101", "test") location = course_key.make_usage_key("vertical", "contacting_us") link = utils.get_lms_link_for_item(location, False) self.assertEquals(link, "//localhost:8000/courses/mitX/101/test/jump_to/i4x://mitX/101/vertical/contacting_us") # test preview link = utils.get_lms_link_for_item(location, True) self.assertEquals(link, "//preview/courses/mitX/101/test/jump_to/i4x://mitX/101/vertical/contacting_us") # now test with the course' location location = course_key.make_usage_key("course", "test") link = utils.get_lms_link_for_item(location) self.assertEquals(link, "//localhost:8000/courses/mitX/101/test/jump_to/i4x://mitX/101/course/test")
def test_enrollment(self): user = User.objects.create_user("joe", "*****@*****.**", "password") course_id = SlashSeparatedCourseKey("edX", "Test101", "2013") course_id_partial = SlashSeparatedCourseKey("edX", "Test101", None) # Test basic enrollment self.assertFalse(CourseEnrollment.is_enrolled(user, course_id)) self.assertFalse( CourseEnrollment.is_enrolled_by_partial(user, course_id_partial)) CourseEnrollment.enroll(user, course_id) self.assertTrue(CourseEnrollment.is_enrolled(user, course_id)) self.assertTrue( CourseEnrollment.is_enrolled_by_partial(user, course_id_partial)) self.assert_enrollment_event_was_emitted(user, course_id) # Enrolling them again should be harmless CourseEnrollment.enroll(user, course_id) self.assertTrue(CourseEnrollment.is_enrolled(user, course_id)) self.assertTrue( CourseEnrollment.is_enrolled_by_partial(user, course_id_partial)) self.assert_no_events_were_emitted() # Now unenroll the user CourseEnrollment.unenroll(user, course_id) self.assertFalse(CourseEnrollment.is_enrolled(user, course_id)) self.assertFalse( CourseEnrollment.is_enrolled_by_partial(user, course_id_partial)) self.assert_unenrollment_event_was_emitted(user, course_id) # Unenrolling them again should also be harmless CourseEnrollment.unenroll(user, course_id) self.assertFalse(CourseEnrollment.is_enrolled(user, course_id)) self.assertFalse( CourseEnrollment.is_enrolled_by_partial(user, course_id_partial)) self.assert_no_events_were_emitted() # The enrollment record should still exist, just be inactive enrollment_record = CourseEnrollment.objects.get(user=user, course_id=course_id) self.assertFalse(enrollment_record.is_active) # Make sure mode is updated properly if user unenrolls & re-enrolls enrollment = CourseEnrollment.enroll(user, course_id, "verified") self.assertEquals(enrollment.mode, "verified") CourseEnrollment.unenroll(user, course_id) enrollment = CourseEnrollment.enroll(user, course_id, "audit") self.assertTrue(CourseEnrollment.is_enrolled(user, course_id)) self.assertEquals(enrollment.mode, "audit")
def test_graphicslidertool_import(self): ''' Check to see if definition_from_xml in gst_module.py works properly. Pulls data from the graphic_slider_tool directory in the test data directory. ''' modulestore = XMLModuleStore(DATA_DIR, course_dirs=['graphic_slider_tool']) sa_id = SlashSeparatedCourseKey("edX", "gst_test", "2012_Fall") location = sa_id.make_usage_key("graphical_slider_tool", "sample_gst") gst_sample = modulestore.get_item(location) render_string_from_sample_gst_xml = """ <slider var="a" style="width:400px;float:left;"/>\ <plot style="margin-top:15px;margin-bottom:15px;"/>""".strip() self.assertIn(render_string_from_sample_gst_xml, gst_sample.data)
def clean_course_id(self): """Validate the course id""" cleaned_id = self.cleaned_data["course_id"] try: course_key = CourseKey.from_string(cleaned_id) except InvalidKeyError: try: course_key = SlashSeparatedCourseKey.from_deprecated_string(cleaned_id) except InvalidKeyError: msg = u'Course id invalid.' msg += u' --- Entered course id was: "{0}". '.format(cleaned_id) msg += 'Please recheck that you have supplied a valid course id.' raise forms.ValidationError(msg) if not modulestore().has_course(course_key): msg = u'COURSE NOT FOUND' msg += u' --- Entered course id was: "{0}". '.format(course_key.to_deprecated_string()) msg += 'Please recheck that you have supplied a valid course id.' raise forms.ValidationError(msg) # Now, try and discern if it is a Studio course - HTML editor doesn't work with XML courses is_studio_course = modulestore().get_modulestore_type(course_key) != XML_MODULESTORE_TYPE if not is_studio_course: msg = "Course Email feature is only available for courses authored in Studio. " msg += '"{0}" appears to be an XML backed course.'.format(course_key.to_deprecated_string()) raise forms.ValidationError(msg) return course_key
def remove_user_from_cohort(request, course_key, cohort_id): """ Expects 'username': username in POST data. Return json dict of: {'success': True} or {'success': False, 'msg': error_msg} """ # this is a string when we get it here course_key = SlashSeparatedCourseKey.from_deprecated_string(course_key) get_course_with_access(request.user, 'staff', course_key) username = request.POST.get('username') if username is None: return json_http_response({'success': False, 'msg': 'No username specified'}) cohort = cohorts.get_cohort_by_id(course_key, cohort_id) try: user = User.objects.get(username=username) cohort.users.remove(user) return json_http_response({'success': True}) except User.DoesNotExist: log.debug('no user') return json_http_response({'success': False, 'msg': "No user '{0}'".format(username)})
def handle(self, *args, **options): dry_run = options['dry_run'] task_number = options['task_number'] if len(args) == 4: course_id = SlashSeparatedCourseKey.from_deprecated_string(args[0]) location = course_id.make_usage_key_from_deprecated_string(args[1]) students_ids = [line.strip() for line in open(args[2])] hostname = args[3] else: print self.help return try: course = get_course(course_id) except ValueError as err: print err return descriptor = modulestore().get_item(location, depth=0) if descriptor is None: print "Location not found in course" return if dry_run: print "Doing a dry run." students = User.objects.filter(id__in=students_ids).order_by('username') print "Number of students: {0}".format(students.count()) for student in students: post_submission_for_student(student, course, location, task_number, dry_run=dry_run, hostname=hostname)
def course_info(request, course_id): """ Display the course's info.html, or 404 if there is no such course. Assumes the course_id is in a valid format. """ course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course = get_course_with_access(request.user, 'load', course_key) staff_access = has_access(request.user, 'staff', course) masq = setup_masquerade(request, staff_access) # allow staff to toggle masquerade on info page reverifications = fetch_reverify_banner_info(request, course_key) studio_url = get_studio_url(course_key, 'course_info') context = { 'request': request, 'course_id': course_key.to_deprecated_string(), 'cache': None, 'course': course, 'staff_access': staff_access, 'masquerade': masq, 'studio_url': studio_url, 'reverifications': reverifications, } return render_to_response('courseware/info.html', context)
def handle(self, *args, **options): if not options['course_id']: raise CommandError("You must specify a course id for this command") if not options['from_mode'] or not options['to_mode']: raise CommandError('You must specify a "to" and "from" mode as parameters') try: course_key = CourseKey.from_string(options['course_id']) except InvalidKeyError: course_key = SlashSeparatedCourseKey.from_deprecated_string(options['course_id']) filter_args = dict( course_id=course_key, mode=options['from_mode'] ) if options['user']: if '@' in options['user']: user = User.objects.get(email=options['user']) else: user = User.objects.get(username=options['user']) filter_args['user'] = user enrollments = CourseEnrollment.objects.filter(**filter_args) if options['noop']: print "Would have changed {num_enrollments} students from {from_mode} to {to_mode}".format( num_enrollments=enrollments.count(), from_mode=options['from_mode'], to_mode=options['to_mode'] ) else: for enrollment in enrollments: enrollment.update_enrollment(mode=options['to_mode']) enrollment.save()
def handle(self, *args, **options): if len(args) != 1: raise CommandError("course_id not specified") # Get the modulestore try: name = options["modulestore"] store = modulestore(name) except KeyError: raise CommandError("Unknown modulestore {}".format(name)) # Get the course data try: course_id = SlashSeparatedCourseKey.from_deprecated_string(args[0]) except InvalidKeyError: raise CommandError("Invalid course_id") course = store.get_course(course_id) if course is None: raise CommandError("Invalid course_id") # precompute inherited metadata at the course level, if needed: if options["inherited"]: compute_inherited_metadata(course) # Convert course data to dictionary and dump it as JSON to stdout info = dump_module(course, inherited=options["inherited"], defaults=options["inherited_defaults"]) return json.dumps(info, indent=2, sort_keys=True)
def test_per_course_anonymized_id(self, descriptor_class): self.assertEquals( # This value is set by observation, so that later changes to the student # id computation don't break old data 'e3b0b940318df9c14be59acb08e78af5', self._get_anonymous_id( SlashSeparatedCourseKey('MITx', '6.00x', '2012_Fall'), descriptor_class)) self.assertEquals( # This value is set by observation, so that later changes to the student # id computation don't break old data 'f82b5416c9f54b5ce33989511bb5ef2e', self._get_anonymous_id( SlashSeparatedCourseKey('MITx', '6.00x', '2013_Spring'), descriptor_class))
def show_unit_extensions(request, course_id): """ Shows all of the students which have due date extensions for the given unit. """ course = get_course_by_id(SlashSeparatedCourseKey.from_deprecated_string(course_id)) unit = find_unit(course, request.GET.get('url')) return JsonResponse(dump_module_extensions(course, unit))
def test_xml_course_authorization(self): course_id = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall') # Assert this is an XML course self.assertEqual(modulestore().get_modulestore_type(course_id), XML_MODULESTORE_TYPE) form_data = {'course_id': course_id.to_deprecated_string(), 'email_enabled': True} form = CourseAuthorizationAdminForm(data=form_data) # Validation shouldn't work self.assertFalse(form.is_valid()) msg = u"Course Email feature is only available for courses authored in Studio. " msg += u'"{0}" appears to be an XML backed course.'.format(course_id.to_deprecated_string()) self.assertEquals(msg, form._errors['course_id'][0]) # pylint: disable=protected-access with self.assertRaisesRegexp(ValueError, "The CourseAuthorization could not be created because the data didn't validate."): form.save()
def test_form_typo(self): # Munge course id bad_id = SlashSeparatedCourseKey(u'Broken{}'.format(self.course.id.org), '', self.course.id.run + '_typo') form_data = {'course_id': bad_id.to_deprecated_string(), 'email_enabled': True} form = CourseAuthorizationAdminForm(data=form_data) # Validation shouldn't work self.assertFalse(form.is_valid()) msg = u'COURSE NOT FOUND' msg += u' --- Entered course id was: "{0}". '.format(bad_id.to_deprecated_string()) msg += 'Please recheck that you have supplied a valid course id.' self.assertEquals(msg, form._errors['course_id'][0]) # pylint: disable=protected-access with self.assertRaisesRegexp(ValueError, "The CourseAuthorization could not be created because the data didn't validate."): form.save()
def jump_to(request, course_id, location): """ Show the page that contains a specific location. If the location is invalid or not in any class, return a 404. Otherwise, delegates to the index view to figure out whether this user has access, and what they should see. """ try: course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) usage_key = course_key.make_usage_key_from_deprecated_string(location) except InvalidKeyError: raise Http404(u"Invalid course_key or usage_key") try: (course_key, chapter, section, position) = path_to_location(modulestore(), usage_key) except ItemNotFoundError: raise Http404(u"No data at this location: {0}".format(usage_key)) except NoPathToItem: raise Http404(u"This location is not in any class: {0}".format(usage_key)) # choose the appropriate view (and provide the necessary args) based on the # args provided by the redirect. # Rely on index to do all error handling and access control. if chapter is None: return redirect('courseware', course_id=course_key.to_deprecated_string()) elif section is None: return redirect('courseware_chapter', course_id=course_key.to_deprecated_string(), chapter=chapter) elif position is None: return redirect('courseware_section', course_id=course_key.to_deprecated_string(), chapter=chapter, section=section) else: return redirect('courseware_position', course_id=course_key.to_deprecated_string(), chapter=chapter, section=section, position=position)
def clean_course_id(self): """Validate the course id""" cleaned_id = self.cleaned_data["course_id"] try: course_key = CourseKey.from_string(cleaned_id) except InvalidKeyError: try: course_key = SlashSeparatedCourseKey.from_deprecated_string( cleaned_id) except InvalidKeyError: msg = u'Course id invalid.' msg += u' --- Entered course id was: "{0}". '.format( cleaned_id) msg += 'Please recheck that you have supplied a valid course id.' raise forms.ValidationError(msg) if not modulestore().has_course(course_key): msg = u'COURSE NOT FOUND' msg += u' --- Entered course id was: "{0}". '.format( course_key.to_deprecated_string()) msg += 'Please recheck that you have supplied a valid course id.' raise forms.ValidationError(msg) # Now, try and discern if it is a Studio course - HTML editor doesn't work with XML courses is_studio_course = modulestore().get_modulestore_type( course_key) != XML_MODULESTORE_TYPE if not is_studio_course: msg = "Course Email feature is only available for courses authored in Studio. " msg += '"{0}" appears to be an XML backed course.'.format( course_key.to_deprecated_string()) raise forms.ValidationError(msg) return course_key
def setUp(self): self.client = Client() # Mocks api.api_enabled = self.mock_api_enabled(True) # Create two accounts self.password = '******' self.student = User.objects.create_user('student', '*****@*****.**', self.password) self.student2 = User.objects.create_user('student2', '*****@*****.**', self.password) self.instructor = User.objects.create_user('instructor', '*****@*****.**', self.password) self.course_key = SlashSeparatedCourseKey('HarvardX', 'CB22x', 'The_Ancient_Greek_Hero') self.note = { 'user': self.student, 'course_id': self.course_key, 'uri': '/', 'text': 'foo', 'quote': 'bar', 'range_start': 0, 'range_start_offset': 0, 'range_end': 100, 'range_end_offset': 0, 'tags': 'a,b,c' } # Make sure no note with this ID ever exists for testing purposes self.NOTE_ID_DOES_NOT_EXIST = 99999
def static_tab(request, course_id, tab_slug): """ Display the courses tab with the given name. Assumes the course_id is in a valid format. """ course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course = get_course_with_access(request.user, 'load', course_key) tab = CourseTabList.get_tab_by_slug(course.tabs, tab_slug) if tab is None: raise Http404 contents = get_static_tab_contents( request, course, tab ) if contents is None: raise Http404 return render_to_response('courseware/static_tab.html', { 'course': course, 'tab': tab, 'tab_contents': contents, })
def add_cohort(request, course_key): """ Return json of dict: {'success': True, 'cohort': {'id': id, 'name': name}} or {'success': False, 'msg': error_msg} if there's an error """ # this is a string when we get it here course_key = SlashSeparatedCourseKey.from_deprecated_string(course_key) get_course_with_access(request.user, 'staff', course_key) name = request.POST.get("name") if not name: return json_http_response({'success': False, 'msg': "No name specified"}) try: cohort = cohorts.add_cohort(course_key, name) except ValueError as err: return json_http_response({'success': False, 'msg': str(err)}) return json_http_response({'success': 'True', 'cohort': { 'id': cohort.id, 'name': cohort.name }})
def send_email(request, course_id): """ Send an email to self, staff, or everyone involved in a course. Query Parameters: - 'send_to' specifies what group the email should be sent to Options are defined by the CourseEmail model in lms/djangoapps/bulk_email/models.py - 'subject' specifies email's subject - 'message' specifies email's content """ course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id) if not bulk_email_is_enabled_for_course(course_id): return HttpResponseForbidden("Email is not enabled for this course.") send_to = request.POST.get("send_to") subject = request.POST.get("subject") message = request.POST.get("message") # Create the CourseEmail object. This is saved immediately, so that # any transaction that has been pending up to this point will also be # committed. email = CourseEmail.create(course_id, request.user, send_to, subject, message) # Submit the task, so that the correct InstructorTask object gets created (for monitoring purposes) instructor_task.api.submit_bulk_course_email(request, course_id, email.id) # pylint: disable=E1101 response_payload = { 'course_id': course_id.to_deprecated_string(), 'success': True, } return JsonResponse(response_payload)
def progress(request, course_id, student_id=None): """ Wraps "_progress" with the manual_transaction context manager just in case there are unanticipated errors. """ with grades.manual_transaction(): return _progress(request, SlashSeparatedCourseKey.from_deprecated_string(course_id), student_id)
def get_anon_ids(request, course_id): # pylint: disable=W0613 """ Respond with 2-column CSV output of user-id, anonymized-user-id """ # TODO: the User.objects query and CSV generation here could be # centralized into analytics. Currently analytics has similar functionality # but not quite what's needed. course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id) def csv_response(filename, header, rows): """Returns a CSV http response for the given header and rows (excel/utf-8).""" response = HttpResponse(mimetype='text/csv') response['Content-Disposition'] = 'attachment; filename={0}'.format(filename) writer = csv.writer(response, dialect='excel', quotechar='"', quoting=csv.QUOTE_ALL) # In practice, there should not be non-ascii data in this query, # but trying to do the right thing anyway. encoded = [unicode(s).encode('utf-8') for s in header] writer.writerow(encoded) for row in rows: encoded = [unicode(s).encode('utf-8') for s in row] writer.writerow(encoded) return response students = User.objects.filter( courseenrollment__course_id=course_id, ).order_by('id') header = ['User ID', 'Anonymized user ID', 'Course Specific Anonymized user ID'] rows = [[s.id, unique_id_for_user(s), anonymous_id_for_user(s, course_id)] for s in students] return csv_response(course_id.to_deprecated_string().replace('/', '-') + '-anon-ids.csv', header, rows)
class TestNewInstructorDashboardEmailViewXMLBacked(ModuleStoreTestCase): """ Check for email view on the new instructor dashboard """ def setUp(self): self.course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall') # Create instructor account instructor = AdminFactory.create() self.client.login(username=instructor.username, password="******") # URL for instructor dash self.url = reverse('instructor_dashboard', kwargs={'course_id': self.course_key.to_deprecated_string()}) # URL for email view self.email_link = '<a href="" data-section="send_email">Email</a>' # The flag is enabled, and since REQUIRE_COURSE_EMAIL_AUTH is False, all courses should # be authorized to use email. But the course is not Mongo-backed (should not work) @patch.dict(settings.FEATURES, {'ENABLE_INSTRUCTOR_EMAIL': True, 'REQUIRE_COURSE_EMAIL_AUTH': False}) def test_email_flag_true_mongo_false(self): response = self.client.get(self.url) self.assertFalse(self.email_link in response.content) # The flag is disabled and the course is not Mongo-backed (should not work) @patch.dict(settings.FEATURES, {'ENABLE_INSTRUCTOR_EMAIL': False, 'REQUIRE_COURSE_EMAIL_AUTH': False}) def test_email_flag_false_mongo_false(self): response = self.client.get(self.url) self.assertFalse(self.email_link in response.content)