def setUp(self, **kwargs): super(CreditServiceTests, self).setUp() self.service = CreditService() self.course = CourseFactory.create(org='edX', number='DemoX', display_name='Demo_Course') self.credit_course = CreditCourse.objects.create(course_key=self.course.id, enabled=True) self.profile = UserProfile.objects.create(user_id=self.user.id, name='Foo Bar')
def setUp(self): super().setUp() self.service = CreditService() self.course = CourseFactory.create(org='edX', number='DemoX', display_name='Demo_Course') self.credit_course = CreditCourse.objects.create( course_key=self.course.id, enabled=True) self.user.profile.name = 'Foo Bar' self.user.profile.save()
def setUp(self): super(CreditServiceTests, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments self.service = CreditService() self.course = CourseFactory.create(org='edX', number='DemoX', display_name='Demo_Course') self.credit_course = CreditCourse.objects.create( course_key=self.course.id, enabled=True) self.profile = UserProfile.objects.create(user_id=self.user.id, name='Foo Bar')
def run(): """ Executed during django startup """ django_utils_translation.patch() autostartup() add_mimetypes() if settings.FEATURES.get('USE_CUSTOM_THEME', False): enable_theme() if settings.FEATURES.get('USE_MICROSITES', False): enable_microsites() if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH', False): enable_third_party_auth() # Initialize Segment.io analytics module. Flushes first time a message is received and # every 50 messages thereafter, or if 10 seconds have passed since last flush if settings.FEATURES.get('SEGMENT_IO_LMS') and hasattr( settings, 'SEGMENT_IO_LMS_KEY'): analytics.init(settings.SEGMENT_IO_LMS_KEY, flush_at=50) # register any dependency injections that we need to support in edx_proctoring # right now edx_proctoring is dependent on the openedx.core.djangoapps.credit # as well as the instructor dashboard (for deleting student attempts) if settings.FEATURES.get('ENABLE_PROCTORED_EXAMS'): set_runtime_service('credit', CreditService()) set_runtime_service('instructor', InstructorService())
def run(): """ Executed during django startup """ django_utils_translation.patch() autostartup() add_mimetypes() if settings.FEATURES.get('USE_CUSTOM_THEME', False): enable_stanford_theme() if settings.FEATURES.get('USE_MICROSITES', False): enable_microsites() if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH', False): enable_third_party_auth() # Initialize Segment analytics module by setting the write_key. if settings.LMS_SEGMENT_KEY: analytics.write_key = settings.LMS_SEGMENT_KEY # register any dependency injections that we need to support in edx_proctoring # right now edx_proctoring is dependent on the openedx.core.djangoapps.credit # as well as the instructor dashboard (for deleting student attempts) if settings.FEATURES.get('ENABLE_PROCTORED_EXAMS'): # Import these here to avoid circular dependencies of the form: # edx-platform app --> DRF --> django translation --> edx-platform app from edx_proctoring.runtime import set_runtime_service from instructor.services import InstructorService from openedx.core.djangoapps.credit.services import CreditService set_runtime_service('credit', CreditService()) set_runtime_service('instructor', InstructorService())
def setUp(self): super(CreditServiceTests, self).setUp() self.service = CreditService() self.course = CourseFactory.create(org='edX', number='DemoX', display_name='Demo_Course') self.credit_course = CreditCourse.objects.create(course_key=self.course.id, enabled=True) self.profile = UserProfile.objects.create(user_id=self.user.id, name='Foo Bar')
def setUp(self, **kwargs): super(CreditServiceTests, self).setUp() self.service = CreditService() self.course = CourseFactory.create(org="edX", number="DemoX", display_name="Demo_Course") self.credit_course = CreditCourse.objects.create(course_key=self.course.id, enabled=True) self.profile = UserProfile.objects.create(user_id=self.user.id, name="Foo Bar")
def run(): """ Executed during django startup """ django_db_models_options.patch() # To override the settings before executing the autostartup() for python-social-auth if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH', False): enable_third_party_auth() # Comprehensive theming needs to be set up before django startup, # because modifying django template paths after startup has no effect. if is_comprehensive_theming_enabled(): enable_theming() # We currently use 2 template rendering engines, mako and django_templates, # and one of them (django templates), requires the directories be added # before the django.setup(). microsite.enable_microsites_pre_startup(log) django.setup() autostartup() add_mimetypes() # Mako requires the directories to be added after the django setup. microsite.enable_microsites(log) # Initialize Segment analytics module by setting the write_key. if settings.LMS_SEGMENT_KEY: analytics.write_key = settings.LMS_SEGMENT_KEY # register any dependency injections that we need to support in edx_proctoring # right now edx_proctoring is dependent on the openedx.core.djangoapps.credit if settings.FEATURES.get('ENABLE_SPECIAL_EXAMS'): # Import these here to avoid circular dependencies of the form: # edx-platform app --> DRF --> django translation --> edx-platform app from edx_proctoring.runtime import set_runtime_service from lms.djangoapps.instructor.services import InstructorService from openedx.core.djangoapps.credit.services import CreditService set_runtime_service('credit', CreditService()) # register InstructorService (for deleting student attempts and user staff access roles) set_runtime_service('instructor', InstructorService()) # In order to allow modules to use a handler url, we need to # monkey-patch the x_module library. # TODO: Remove this code when Runtimes are no longer created by modulestores # https://openedx.atlassian.net/wiki/display/PLAT/Convert+from+Storage-centric+runtimes+to+Application-centric+runtimes xmodule.x_module.descriptor_global_handler_url = lms_xblock.runtime.handler_url xmodule.x_module.descriptor_global_local_resource_url = lms_xblock.runtime.local_resource_url # Set the version of docs that help-tokens will go to. settings.HELP_TOKENS_LANGUAGE_CODE = settings.LANGUAGE_CODE settings.HELP_TOKENS_VERSION = doc_version() # validate configurations on startup validate_lms_config(settings)
def run(): """ Executed during django startup NOTE: DO **NOT** add additional code to this method or this file! The Platform Team is moving all startup code to more standard locations using Django best practices. """ django_db_models_options.patch() # We currently use 2 template rendering engines, mako and django_templates, # and one of them (django templates), requires the directories be added # before the django.setup(). microsite.enable_microsites_pre_startup(log) django.setup() autostartup() add_mimetypes() # Mako requires the directories to be added after the django setup. microsite.enable_microsites(log) # register any dependency injections that we need to support in edx_proctoring # right now edx_proctoring is dependent on the openedx.core.djangoapps.credit and # lms.djangoapps.grades if settings.FEATURES.get('ENABLE_SPECIAL_EXAMS'): # Import these here to avoid circular dependencies of the form: # edx-platform app --> DRF --> django translation --> edx-platform app from edx_proctoring.runtime import set_runtime_service from lms.djangoapps.instructor.services import InstructorService from openedx.core.djangoapps.credit.services import CreditService from lms.djangoapps.grades.services import GradesService set_runtime_service('credit', CreditService()) # register InstructorService (for deleting student attempts and user staff access roles) set_runtime_service('instructor', InstructorService()) set_runtime_service('grades', GradesService()) # In order to allow modules to use a handler url, we need to # monkey-patch the x_module library. # TODO: Remove this code when Runtimes are no longer created by modulestores # https://openedx.atlassian.net/wiki/display/PLAT/Convert+from+Storage-centric+runtimes+to+Application-centric+runtimes xmodule.x_module.descriptor_global_handler_url = lms_xblock.runtime.handler_url xmodule.x_module.descriptor_global_local_resource_url = lms_xblock.runtime.local_resource_url # Set the version of docs that help-tokens will go to. settings.HELP_TOKENS_LANGUAGE_CODE = settings.LANGUAGE_CODE settings.HELP_TOKENS_VERSION = doc_version() # validate configurations on startup validate_lms_config(settings)
def run(): """ Executed during django startup """ third_party_auth.patch() # To override the settings before executing the autostartup() for python-social-auth if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH', False): enable_third_party_auth() if settings.FEATURES.get('USE_MICROSITES', False): enable_microsites_pre_startup() django.setup() autostartup() add_mimetypes() if settings.FEATURES.get('USE_CUSTOM_THEME', False): enable_stanford_theme() if settings.FEATURES.get('USE_MICROSITES', False): enable_microsites() # Initialize Segment analytics module by setting the write_key. if settings.LMS_SEGMENT_KEY: analytics.write_key = settings.LMS_SEGMENT_KEY # register any dependency injections that we need to support in edx_proctoring # right now edx_proctoring is dependent on the openedx.core.djangoapps.credit if settings.FEATURES.get('ENABLE_SPECIAL_EXAMS'): # Import these here to avoid circular dependencies of the form: # edx-platform app --> DRF --> django translation --> edx-platform app from edx_proctoring.runtime import set_runtime_service from instructor.services import InstructorService from openedx.core.djangoapps.credit.services import CreditService set_runtime_service('credit', CreditService()) # register InstructorService (for deleting student attempts and user staff access roles) set_runtime_service('instructor', InstructorService()) # In order to allow modules to use a handler url, we need to # monkey-patch the x_module library. # TODO: Remove this code when Runtimes are no longer created by modulestores # https://openedx.atlassian.net/wiki/display/PLAT/Convert+from+Storage-centric+runtimes+to+Application-centric+runtimes xmodule.x_module.descriptor_global_handler_url = lms_xblock.runtime.handler_url xmodule.x_module.descriptor_global_local_resource_url = lms_xblock.runtime.local_resource_url
class CreditServiceTests(ModuleStoreTestCase): """ Tests for the Credit xBlock service """ def setUp(self, **kwargs): super(CreditServiceTests, self).setUp() self.service = CreditService() self.course = CourseFactory.create(org="edX", number="DemoX", display_name="Demo_Course") self.credit_course = CreditCourse.objects.create(course_key=self.course.id, enabled=True) self.profile = UserProfile.objects.create(user_id=self.user.id, name="Foo Bar") def test_user_not_found(self): """ Makes sure that get_credit_state returns None if user_id cannot be found """ self.assertIsNone(self.service.get_credit_state(0, self.course.id)) def test_user_not_enrolled(self): """ Makes sure that get_credit_state returns None if user_id is not enrolled in the test course """ self.assertIsNone(self.service.get_credit_state(self.user.id, self.course.id)) def test_inactive_enrollment(self): """ Makes sure that get_credit_state returns None if the user's enrollment is inactive """ enrollment = CourseEnrollment.enroll(self.user, self.course.id) enrollment.is_active = False enrollment.save() self.assertIsNone(self.service.get_credit_state(self.user.id, self.course.id)) def test_not_credit_course(self): """ Makes sure that get_credit_state returns None if the test course is not Credit eligible """ CourseEnrollment.enroll(self.user, self.course.id) self.credit_course.enabled = False self.credit_course.save() self.assertIsNone(self.service.get_credit_state(self.user.id, self.course.id)) def test_no_profile_name(self): """ Makes sure that get_credit_state returns None if the user does not have a corresponding UserProfile. This shouldn't happen in real environments """ profile = UserProfile.objects.get(user_id=self.user.id) profile.delete() self.assertIsNone(self.service.get_credit_state(self.user.id, self.course.id)) def test_get_and_set_credit_state(self): """ Happy path through the service """ CourseEnrollment.enroll(self.user, self.course.id) # set course requirements set_credit_requirements( self.course.id, [{"namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": {"min_grade": 0.8}}], ) # mark the grade as satisfied self.service.set_credit_requirement_status(self.user.id, self.course.id, "grade", "grade") credit_state = self.service.get_credit_state(self.user.id, self.course.id) self.assertIsNotNone(credit_state) self.assertEqual(credit_state["enrollment_mode"], "honor") self.assertEqual(credit_state["profile_fullname"], "Foo Bar") self.assertEqual(len(credit_state["credit_requirement_status"]), 1) self.assertEqual(credit_state["credit_requirement_status"][0]["name"], "grade") self.assertEqual(credit_state["credit_requirement_status"][0]["status"], "satisfied") def test_bad_user(self): """ Try setting requirements status with a bad user_id """ # set course requirements set_credit_requirements( self.course.id, [{"namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": {"min_grade": 0.8}}], ) # mark the grade as satisfied retval = self.service.set_credit_requirement_status(0, self.course.id, "grade", "grade") self.assertIsNone(retval) def test_course_id_string(self): """ Make sure we can pass a course_id (string) and get back correct results as well """ CourseEnrollment.enroll(self.user, self.course.id) # set course requirements set_credit_requirements( self.course.id, [{"namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": {"min_grade": 0.8}}], ) # mark the grade as satisfied self.service.set_credit_requirement_status(self.user.id, unicode(self.course.id), "grade", "grade") credit_state = self.service.get_credit_state(self.user.id, unicode(self.course.id)) self.assertIsNotNone(credit_state) self.assertEqual(credit_state["enrollment_mode"], "honor") self.assertEqual(credit_state["profile_fullname"], "Foo Bar") self.assertEqual(len(credit_state["credit_requirement_status"]), 1) self.assertEqual(credit_state["credit_requirement_status"][0]["name"], "grade") self.assertEqual(credit_state["credit_requirement_status"][0]["status"], "satisfied")
class CreditServiceTests(ModuleStoreTestCase): """ Tests for the Credit xBlock service """ def setUp(self): super().setUp() self.service = CreditService() self.course = CourseFactory.create(org='edX', number='DemoX', display_name='Demo_Course') self.credit_course = CreditCourse.objects.create( course_key=self.course.id, enabled=True) self.user.profile.name = 'Foo Bar' self.user.profile.save() def enroll(self, course_id=None, mode=CourseMode.VERIFIED): """ Enroll the test user in the given course's mode. Use course/mode if they are provided. """ if course_id is None: course_id = self.course.id return CourseEnrollment.enroll(self.user, course_id, mode=mode) def test_user_not_found(self): """ Makes sure that get_credit_state returns None if user_id cannot be found """ assert self.service.get_credit_state(0, self.course.id) is None def test_user_not_enrolled(self): """ Makes sure that get_credit_state returns None if user_id is not enrolled in the test course """ assert self.service.get_credit_state(self.user.id, self.course.id) is None def test_inactive_enrollment(self): """ Makes sure that get_credit_state returns None if the user's enrollment is inactive """ enrollment = self.enroll() enrollment.is_active = False enrollment.save() assert self.service.get_credit_state(self.user.id, self.course.id) is None def test_not_credit_course(self): """ Makes sure that get_credit_state returns None if the test course is not Credit eligible """ self.enroll() self.credit_course.enabled = False self.credit_course.save() credit_state = self.service.get_credit_state(self.user.id, self.course.id) assert credit_state is not None assert not credit_state['is_credit_course'] def test_no_profile_name(self): """ Makes sure that get_credit_state returns None if the user does not have a corresponding UserProfile. This shouldn't happen in real environments """ profile = UserProfile.objects.get(user_id=self.user.id) profile.delete() assert self.service.get_credit_state(self.user.id, self.course.id) is None def test_get_and_set_credit_state(self): """ Happy path through the service """ assert self.service.is_credit_course(self.course.id) self.enroll() # set course requirements set_credit_requirements(self.course.id, [ { "namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": { "min_grade": 0.8 }, }, ]) # mark the grade as satisfied self.service.set_credit_requirement_status(self.user.id, self.course.id, 'grade', 'grade') credit_state = self.service.get_credit_state(self.user.id, self.course.id) assert credit_state is not None assert credit_state['is_credit_course'] assert credit_state['enrollment_mode'] == 'verified' assert credit_state['profile_fullname'] == 'Foo Bar' assert len(credit_state['credit_requirement_status']) == 1 assert credit_state['credit_requirement_status'][0]['name'] == 'grade' assert credit_state['credit_requirement_status'][0][ 'status'] == 'satisfied' def test_remove_credit_requirement_status(self): """ Happy path when deleting the requirement status. """ assert self.service.is_credit_course(self.course.id) self.enroll() # set course requirements set_credit_requirements(self.course.id, [ { "namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": { "min_grade": 0.8 }, }, ]) # mark the grade as satisfied self.service.set_credit_requirement_status(self.user.id, self.course.id, 'grade', 'grade') # now the status should be "satisfied" when looking at the credit_requirement_status list credit_state = self.service.get_credit_state(self.user.id, self.course.id) assert credit_state['credit_requirement_status'][0][ 'status'] == 'satisfied' # remove the requirement status. self.service.remove_credit_requirement_status(self.user.id, self.course.id, 'grade', 'grade') # now the status should be None when looking at the credit_requirement_status list credit_state = self.service.get_credit_state(self.user.id, self.course.id) assert credit_state['credit_requirement_status'][0]['status'] is None def test_invalid_user(self): """ Try removing requirement status with a invalid user_id """ # set course requirements set_credit_requirements(self.course.id, [ { "namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": { "min_grade": 0.8 }, }, ]) # mark the grade as satisfied retval = self.service.set_credit_requirement_status( # lint-amnesty, pylint: disable=assignment-from-none self.user.id, self.course.id, 'grade', 'grade') assert retval is None # remove the requirement status with the invalid user id retval = self.service.remove_credit_requirement_status( # lint-amnesty, pylint: disable=assignment-from-none 0, self.course.id, 'grade', 'grade') assert retval is None def test_remove_status_non_credit(self): """ assert that we can still try to update a credit status but return quickly if a course is not credit eligible """ no_credit_course = CourseFactory.create(org='NoCredit', number='NoCredit', display_name='Demo_Course') assert not self.service.is_credit_course(no_credit_course.id) self.enroll(no_credit_course.id) # this should be a no-op self.service.remove_credit_requirement_status(self.user.id, no_credit_course.id, 'grade', 'grade') credit_state = self.service.get_credit_state(self.user.id, no_credit_course.id) assert credit_state is not None assert not credit_state['is_credit_course'] assert len(credit_state['credit_requirement_status']) == 0 def test_course_name(self): """ Make sure we can get back the optional course name """ self.enroll() # make sure it is not returned by default credit_state = self.service.get_credit_state(self.user.id, self.course.id) assert 'course_name' not in credit_state # now make sure it is in there when we pass in the flag credit_state = self.service.get_credit_state(self.user.id, self.course.id, return_course_info=True) assert 'course_name' in credit_state assert credit_state['course_name'] == self.course.display_name @patch("openedx.core.djangoapps.credit.services.log") def test_get_info_from_non_existent_course(self, exception_log): """ Make sure we catch the CourseOverview.DoesNotExist exception and log it instead of raising """ with patch( "openedx.core.djangoapps.content.course_overviews.models.CourseOverview.get_from_id", side_effect=CourseOverview.DoesNotExist): self.enroll() credit_state = self.service.get_credit_state( self.user.id, self.course.id, return_course_info=True) assert credit_state is not None # When exception is caught, the course_end_date should always be in past assert credit_state["course_end_date"] < datetime.now() exception_log.exception.assert_called_once_with( "Could not get name and end_date for course %s, This happened because we were unable to " "get/create CourseOverview object for the course. It's possible that the Course has been deleted.", str(self.course.id)) def test_set_status_non_credit(self): """ assert that we can still try to update a credit status but return quickly if a course is not credit eligible """ no_credit_course = CourseFactory.create(org='NoCredit', number='NoCredit', display_name='Demo_Course') assert not self.service.is_credit_course(no_credit_course.id) self.enroll(no_credit_course.id) # this should be a no-op self.service.set_credit_requirement_status(self.user.id, no_credit_course.id, 'grade', 'grade') credit_state = self.service.get_credit_state(self.user.id, no_credit_course.id) assert credit_state is not None assert not credit_state['is_credit_course'] assert len(credit_state['credit_requirement_status']) == 0 @ddt.data(CourseMode.AUDIT, CourseMode.HONOR, CourseMode.CREDIT_MODE) def test_set_status_non_verified_enrollment(self, mode): """ Test that we can still try to update a credit status but return quickly if user has non-credit eligible enrollment. """ self.enroll(mode=mode) # set course requirements set_credit_requirements(self.course.id, [ { "namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": { "min_grade": 0.8 }, }, ]) # this should be a no-op self.service.set_credit_requirement_status(self.user.id, self.course.id, 'grade', 'grade') # Verify credit requirement status for user in the course should be None. credit_state = self.service.get_credit_state(self.user.id, self.course.id) assert credit_state is not None assert credit_state['enrollment_mode'] == mode assert len(credit_state['credit_requirement_status']) == 1 assert credit_state['credit_requirement_status'][0]['status'] is None assert credit_state['credit_requirement_status'][0][ 'status_date'] is None def test_bad_user(self): """ Try setting requirements status with a bad user_id """ # set course requirements set_credit_requirements(self.course.id, [ { "namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": { "min_grade": 0.8 }, }, ]) # mark the grade as satisfied retval = self.service.set_credit_requirement_status( # lint-amnesty, pylint: disable=assignment-from-none 0, self.course.id, 'grade', 'grade') assert retval is None def test_course_id_string(self): """ Make sure we can pass a course_id (string) and get back correct results as well """ self.enroll() # set course requirements set_credit_requirements(self.course.id, [ { "namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": { "min_grade": 0.8 }, }, ]) # mark the grade as satisfied self.service.set_credit_requirement_status(self.user.id, str(self.course.id), 'grade', 'grade') credit_state = self.service.get_credit_state(self.user.id, str(self.course.id)) assert credit_state is not None assert credit_state['enrollment_mode'] == 'verified' assert credit_state['profile_fullname'] == 'Foo Bar' assert len(credit_state['credit_requirement_status']) == 1 assert credit_state['credit_requirement_status'][0]['name'] == 'grade' assert credit_state['credit_requirement_status'][0][ 'status'] == 'satisfied'
class CreditServiceTests(ModuleStoreTestCase): """ Tests for the Credit xBlock service """ def setUp(self, **kwargs): super(CreditServiceTests, self).setUp() self.service = CreditService() self.course = CourseFactory.create(org='edX', number='DemoX', display_name='Demo_Course') self.credit_course = CreditCourse.objects.create( course_key=self.course.id, enabled=True) self.profile = UserProfile.objects.create(user_id=self.user.id, name='Foo Bar') def enroll(self, course_id=None): """ Enroll the test user in the given course's honor mode, or the test course if not provided. """ if course_id is None: course_id = self.course.id return CourseEnrollment.enroll(self.user, course_id, mode='honor') def test_user_not_found(self): """ Makes sure that get_credit_state returns None if user_id cannot be found """ self.assertIsNone(self.service.get_credit_state(0, self.course.id)) def test_user_not_enrolled(self): """ Makes sure that get_credit_state returns None if user_id is not enrolled in the test course """ self.assertIsNone( self.service.get_credit_state(self.user.id, self.course.id)) def test_inactive_enrollment(self): """ Makes sure that get_credit_state returns None if the user's enrollment is inactive """ enrollment = self.enroll() enrollment.is_active = False enrollment.save() self.assertIsNone( self.service.get_credit_state(self.user.id, self.course.id)) def test_not_credit_course(self): """ Makes sure that get_credit_state returns None if the test course is not Credit eligible """ self.enroll() self.credit_course.enabled = False self.credit_course.save() credit_state = self.service.get_credit_state(self.user.id, self.course.id) self.assertIsNotNone(credit_state) self.assertFalse(credit_state['is_credit_course']) def test_no_profile_name(self): """ Makes sure that get_credit_state returns None if the user does not have a corresponding UserProfile. This shouldn't happen in real environments """ profile = UserProfile.objects.get(user_id=self.user.id) profile.delete() self.assertIsNone( self.service.get_credit_state(self.user.id, self.course.id)) def test_get_and_set_credit_state(self): """ Happy path through the service """ self.assertTrue(self.service.is_credit_course(self.course.id)) self.enroll() # set course requirements set_credit_requirements(self.course.id, [ { "namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": { "min_grade": 0.8 }, }, ]) # mark the grade as satisfied self.service.set_credit_requirement_status(self.user.id, self.course.id, 'grade', 'grade') credit_state = self.service.get_credit_state(self.user.id, self.course.id) self.assertIsNotNone(credit_state) self.assertTrue(credit_state['is_credit_course']) self.assertEqual(credit_state['enrollment_mode'], 'honor') self.assertEqual(credit_state['profile_fullname'], 'Foo Bar') self.assertEqual(len(credit_state['credit_requirement_status']), 1) self.assertEqual(credit_state['credit_requirement_status'][0]['name'], 'grade') self.assertEqual( credit_state['credit_requirement_status'][0]['status'], 'satisfied') def test_remove_credit_requirement_status(self): """ Happy path when deleting the requirement status. """ self.assertTrue(self.service.is_credit_course(self.course.id)) self.enroll() # set course requirements set_credit_requirements(self.course.id, [ { "namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": { "min_grade": 0.8 }, }, ]) # mark the grade as satisfied self.service.set_credit_requirement_status(self.user.id, self.course.id, 'grade', 'grade') # now the status should be "satisfied" when looking at the credit_requirement_status list credit_state = self.service.get_credit_state(self.user.id, self.course.id) self.assertEqual( credit_state['credit_requirement_status'][0]['status'], "satisfied") # remove the requirement status. self.service.remove_credit_requirement_status(self.user.id, self.course.id, 'grade', 'grade') # now the status should be None when looking at the credit_requirement_status list credit_state = self.service.get_credit_state(self.user.id, self.course.id) self.assertEqual( credit_state['credit_requirement_status'][0]['status'], None) def test_invalid_user(self): """ Try removing requirement status with a invalid user_id """ # set course requirements set_credit_requirements(self.course.id, [ { "namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": { "min_grade": 0.8 }, }, ]) # mark the grade as satisfied retval = self.service.set_credit_requirement_status( self.user.id, self.course.id, 'grade', 'grade') self.assertIsNone(retval) # remove the requirement status with the invalid user id retval = self.service.remove_credit_requirement_status( 0, self.course.id, 'grade', 'grade') self.assertIsNone(retval) def test_remove_status_non_credit(self): """ assert that we can still try to update a credit status but return quickly if a course is not credit eligible """ no_credit_course = CourseFactory.create(org='NoCredit', number='NoCredit', display_name='Demo_Course') self.assertFalse(self.service.is_credit_course(no_credit_course.id)) self.enroll(no_credit_course.id) # this should be a no-op self.service.remove_credit_requirement_status(self.user.id, no_credit_course.id, 'grade', 'grade') credit_state = self.service.get_credit_state(self.user.id, no_credit_course.id) self.assertIsNotNone(credit_state) self.assertFalse(credit_state['is_credit_course']) self.assertEqual(len(credit_state['credit_requirement_status']), 0) def test_course_name(self): """ Make sure we can get back the optional course name """ self.enroll() # make sure it is not returned by default credit_state = self.service.get_credit_state(self.user.id, self.course.id) self.assertNotIn('course_name', credit_state) # now make sure it is in there when we pass in the flag credit_state = self.service.get_credit_state(self.user.id, self.course.id, return_course_name=True) self.assertIn('course_name', credit_state) self.assertEqual(credit_state['course_name'], self.course.display_name) def test_set_status_non_credit(self): """ assert that we can still try to update a credit status but return quickly if a course is not credit eligible """ no_credit_course = CourseFactory.create(org='NoCredit', number='NoCredit', display_name='Demo_Course') self.assertFalse(self.service.is_credit_course(no_credit_course.id)) self.enroll(no_credit_course.id) # this should be a no-op self.service.set_credit_requirement_status(self.user.id, no_credit_course.id, 'grade', 'grade') credit_state = self.service.get_credit_state(self.user.id, no_credit_course.id) self.assertIsNotNone(credit_state) self.assertFalse(credit_state['is_credit_course']) self.assertEqual(len(credit_state['credit_requirement_status']), 0) def test_bad_user(self): """ Try setting requirements status with a bad user_id """ # set course requirements set_credit_requirements(self.course.id, [ { "namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": { "min_grade": 0.8 }, }, ]) # mark the grade as satisfied retval = self.service.set_credit_requirement_status( 0, self.course.id, 'grade', 'grade') self.assertIsNone(retval) def test_course_id_string(self): """ Make sure we can pass a course_id (string) and get back correct results as well """ self.enroll() # set course requirements set_credit_requirements(self.course.id, [ { "namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": { "min_grade": 0.8 }, }, ]) # mark the grade as satisfied self.service.set_credit_requirement_status(self.user.id, unicode(self.course.id), 'grade', 'grade') credit_state = self.service.get_credit_state(self.user.id, unicode(self.course.id)) self.assertIsNotNone(credit_state) self.assertEqual(credit_state['enrollment_mode'], 'honor') self.assertEqual(credit_state['profile_fullname'], 'Foo Bar') self.assertEqual(len(credit_state['credit_requirement_status']), 1) self.assertEqual(credit_state['credit_requirement_status'][0]['name'], 'grade') self.assertEqual( credit_state['credit_requirement_status'][0]['status'], 'satisfied')
class CreditServiceTests(ModuleStoreTestCase): """ Tests for the Credit xBlock service """ def setUp(self, **kwargs): super(CreditServiceTests, self).setUp() self.service = CreditService() self.course = CourseFactory.create(org='edX', number='DemoX', display_name='Demo_Course') self.credit_course = CreditCourse.objects.create(course_key=self.course.id, enabled=True) self.profile = UserProfile.objects.create(user_id=self.user.id, name='Foo Bar') def test_user_not_found(self): """ Makes sure that get_credit_state returns None if user_id cannot be found """ self.assertIsNone(self.service.get_credit_state(0, self.course.id)) def test_user_not_enrolled(self): """ Makes sure that get_credit_state returns None if user_id is not enrolled in the test course """ self.assertIsNone(self.service.get_credit_state(self.user.id, self.course.id)) def test_inactive_enrollment(self): """ Makes sure that get_credit_state returns None if the user's enrollment is inactive """ enrollment = CourseEnrollment.enroll(self.user, self.course.id) enrollment.is_active = False enrollment.save() self.assertIsNone(self.service.get_credit_state(self.user.id, self.course.id)) def test_not_credit_course(self): """ Makes sure that get_credit_state returns None if the test course is not Credit eligible """ CourseEnrollment.enroll(self.user, self.course.id) self.credit_course.enabled = False self.credit_course.save() self.assertIsNone(self.service.get_credit_state(self.user.id, self.course.id)) @unittest.skipIf(settings.FEATURES.get('DISABLE_CME_REGISTRATION_TESTS', False), 'This is contrived and does not work for CME') def test_no_profile_name(self): """ Makes sure that get_credit_state returns None if the user does not have a corresponding UserProfile. This shouldn't happen in real environments """ profile = UserProfile.objects.get(user_id=self.user.id) profile.delete() self.assertIsNone(self.service.get_credit_state(self.user.id, self.course.id)) def test_get_and_set_credit_state(self): """ Happy path through the service """ CourseEnrollment.enroll(self.user, self.course.id) # set course requirements set_credit_requirements( self.course.id, [ { "namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": { "min_grade": 0.8 }, }, ] ) # mark the grade as satisfied self.service.set_credit_requirement_status( self.user.id, self.course.id, 'grade', 'grade' ) credit_state = self.service.get_credit_state(self.user.id, self.course.id) self.assertIsNotNone(credit_state) self.assertEqual(credit_state['enrollment_mode'], 'honor') self.assertEqual(credit_state['profile_fullname'], 'Foo Bar') self.assertEqual(len(credit_state['credit_requirement_status']), 1) self.assertEqual(credit_state['credit_requirement_status'][0]['name'], 'grade') self.assertEqual(credit_state['credit_requirement_status'][0]['status'], 'satisfied') def test_bad_user(self): """ Try setting requirements status with a bad user_id """ # set course requirements set_credit_requirements( self.course.id, [ { "namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": { "min_grade": 0.8 }, }, ] ) # mark the grade as satisfied retval = self.service.set_credit_requirement_status( 0, self.course.id, 'grade', 'grade' ) self.assertIsNone(retval) def test_course_id_string(self): """ Make sure we can pass a course_id (string) and get back correct results as well """ CourseEnrollment.enroll(self.user, self.course.id) # set course requirements set_credit_requirements( self.course.id, [ { "namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": { "min_grade": 0.8 }, }, ] ) # mark the grade as satisfied self.service.set_credit_requirement_status( self.user.id, unicode(self.course.id), 'grade', 'grade' ) credit_state = self.service.get_credit_state(self.user.id, unicode(self.course.id)) self.assertIsNotNone(credit_state) self.assertEqual(credit_state['enrollment_mode'], 'honor') self.assertEqual(credit_state['profile_fullname'], 'Foo Bar') self.assertEqual(len(credit_state['credit_requirement_status']), 1) self.assertEqual(credit_state['credit_requirement_status'][0]['name'], 'grade') self.assertEqual(credit_state['credit_requirement_status'][0]['status'], 'satisfied')
class CreditServiceTests(ModuleStoreTestCase): """ Tests for the Credit xBlock service """ def setUp(self, **kwargs): super(CreditServiceTests, self).setUp() self.service = CreditService() self.course = CourseFactory.create(org='edX', number='DemoX', display_name='Demo_Course') self.credit_course = CreditCourse.objects.create(course_key=self.course.id, enabled=True) self.profile = UserProfile.objects.create(user_id=self.user.id, name='Foo Bar') def enroll(self, course_id=None): """ Enroll the test user in the given course's honor mode, or the test course if not provided. """ if course_id is None: course_id = self.course.id return CourseEnrollment.enroll(self.user, course_id, mode='honor') def test_user_not_found(self): """ Makes sure that get_credit_state returns None if user_id cannot be found """ self.assertIsNone(self.service.get_credit_state(0, self.course.id)) def test_user_not_enrolled(self): """ Makes sure that get_credit_state returns None if user_id is not enrolled in the test course """ self.assertIsNone(self.service.get_credit_state(self.user.id, self.course.id)) def test_inactive_enrollment(self): """ Makes sure that get_credit_state returns None if the user's enrollment is inactive """ enrollment = self.enroll() enrollment.is_active = False enrollment.save() self.assertIsNone(self.service.get_credit_state(self.user.id, self.course.id)) def test_not_credit_course(self): """ Makes sure that get_credit_state returns None if the test course is not Credit eligible """ self.enroll() self.credit_course.enabled = False self.credit_course.save() credit_state = self.service.get_credit_state(self.user.id, self.course.id) self.assertIsNotNone(credit_state) self.assertFalse(credit_state['is_credit_course']) def test_no_profile_name(self): """ Makes sure that get_credit_state returns None if the user does not have a corresponding UserProfile. This shouldn't happen in real environments """ profile = UserProfile.objects.get(user_id=self.user.id) profile.delete() self.assertIsNone(self.service.get_credit_state(self.user.id, self.course.id)) def test_get_and_set_credit_state(self): """ Happy path through the service """ self.assertTrue(self.service.is_credit_course(self.course.id)) self.enroll() # set course requirements set_credit_requirements( self.course.id, [ { "namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": { "min_grade": 0.8 }, }, ] ) # mark the grade as satisfied self.service.set_credit_requirement_status( self.user.id, self.course.id, 'grade', 'grade' ) credit_state = self.service.get_credit_state(self.user.id, self.course.id) self.assertIsNotNone(credit_state) self.assertTrue(credit_state['is_credit_course']) self.assertEqual(credit_state['enrollment_mode'], 'honor') self.assertEqual(credit_state['profile_fullname'], 'Foo Bar') self.assertEqual(len(credit_state['credit_requirement_status']), 1) self.assertEqual(credit_state['credit_requirement_status'][0]['name'], 'grade') self.assertEqual(credit_state['credit_requirement_status'][0]['status'], 'satisfied') def test_remove_credit_requirement_status(self): """ Happy path when deleting the requirement status. """ self.assertTrue(self.service.is_credit_course(self.course.id)) self.enroll() # set course requirements set_credit_requirements( self.course.id, [ { "namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": { "min_grade": 0.8 }, }, ] ) # mark the grade as satisfied self.service.set_credit_requirement_status( self.user.id, self.course.id, 'grade', 'grade' ) # now the status should be "satisfied" when looking at the credit_requirement_status list credit_state = self.service.get_credit_state(self.user.id, self.course.id) self.assertEqual(credit_state['credit_requirement_status'][0]['status'], "satisfied") # remove the requirement status. self.service.remove_credit_requirement_status( self.user.id, self.course.id, 'grade', 'grade' ) # now the status should be None when looking at the credit_requirement_status list credit_state = self.service.get_credit_state(self.user.id, self.course.id) self.assertEqual(credit_state['credit_requirement_status'][0]['status'], None) def test_invalid_user(self): """ Try removing requirement status with a invalid user_id """ # set course requirements set_credit_requirements( self.course.id, [ { "namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": { "min_grade": 0.8 }, }, ] ) # mark the grade as satisfied retval = self.service.set_credit_requirement_status( self.user.id, self.course.id, 'grade', 'grade' ) self.assertIsNone(retval) # remove the requirement status with the invalid user id retval = self.service.remove_credit_requirement_status( 0, self.course.id, 'grade', 'grade' ) self.assertIsNone(retval) def test_remove_status_non_credit(self): """ assert that we can still try to update a credit status but return quickly if a course is not credit eligible """ no_credit_course = CourseFactory.create(org='NoCredit', number='NoCredit', display_name='Demo_Course') self.assertFalse(self.service.is_credit_course(no_credit_course.id)) self.enroll(no_credit_course.id) # this should be a no-op self.service.remove_credit_requirement_status( self.user.id, no_credit_course.id, 'grade', 'grade' ) credit_state = self.service.get_credit_state(self.user.id, no_credit_course.id) self.assertIsNotNone(credit_state) self.assertFalse(credit_state['is_credit_course']) self.assertEqual(len(credit_state['credit_requirement_status']), 0) def test_course_name(self): """ Make sure we can get back the optional course name """ self.enroll() # make sure it is not returned by default credit_state = self.service.get_credit_state(self.user.id, self.course.id) self.assertNotIn('course_name', credit_state) # now make sure it is in there when we pass in the flag credit_state = self.service.get_credit_state(self.user.id, self.course.id, return_course_name=True) self.assertIn('course_name', credit_state) self.assertEqual(credit_state['course_name'], self.course.display_name) def test_set_status_non_credit(self): """ assert that we can still try to update a credit status but return quickly if a course is not credit eligible """ no_credit_course = CourseFactory.create(org='NoCredit', number='NoCredit', display_name='Demo_Course') self.assertFalse(self.service.is_credit_course(no_credit_course.id)) self.enroll(no_credit_course.id) # this should be a no-op self.service.set_credit_requirement_status( self.user.id, no_credit_course.id, 'grade', 'grade' ) credit_state = self.service.get_credit_state(self.user.id, no_credit_course.id) self.assertIsNotNone(credit_state) self.assertFalse(credit_state['is_credit_course']) self.assertEqual(len(credit_state['credit_requirement_status']), 0) def test_bad_user(self): """ Try setting requirements status with a bad user_id """ # set course requirements set_credit_requirements( self.course.id, [ { "namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": { "min_grade": 0.8 }, }, ] ) # mark the grade as satisfied retval = self.service.set_credit_requirement_status( 0, self.course.id, 'grade', 'grade' ) self.assertIsNone(retval) def test_course_id_string(self): """ Make sure we can pass a course_id (string) and get back correct results as well """ self.enroll() # set course requirements set_credit_requirements( self.course.id, [ { "namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": { "min_grade": 0.8 }, }, ] ) # mark the grade as satisfied self.service.set_credit_requirement_status( self.user.id, unicode(self.course.id), 'grade', 'grade' ) credit_state = self.service.get_credit_state(self.user.id, unicode(self.course.id)) self.assertIsNotNone(credit_state) self.assertEqual(credit_state['enrollment_mode'], 'honor') self.assertEqual(credit_state['profile_fullname'], 'Foo Bar') self.assertEqual(len(credit_state['credit_requirement_status']), 1) self.assertEqual(credit_state['credit_requirement_status'][0]['name'], 'grade') self.assertEqual(credit_state['credit_requirement_status'][0]['status'], 'satisfied')
class CreditServiceTests(ModuleStoreTestCase): """ Tests for the Credit xBlock service """ def setUp(self, **kwargs): super(CreditServiceTests, self).setUp() self.service = CreditService() self.course = CourseFactory.create(org='edX', number='DemoX', display_name='Demo_Course') self.credit_course = CreditCourse.objects.create( course_key=self.course.id, enabled=True) self.profile = UserProfile.objects.create(user_id=self.user.id, name='Foo Bar') def test_user_not_found(self): """ Makes sure that get_credit_state returns None if user_id cannot be found """ self.assertIsNone(self.service.get_credit_state(0, self.course.id)) def test_user_not_enrolled(self): """ Makes sure that get_credit_state returns None if user_id is not enrolled in the test course """ self.assertIsNone( self.service.get_credit_state(self.user.id, self.course.id)) def test_inactive_enrollment(self): """ Makes sure that get_credit_state returns None if the user's enrollment is inactive """ enrollment = CourseEnrollment.enroll(self.user, self.course.id) enrollment.is_active = False enrollment.save() self.assertIsNone( self.service.get_credit_state(self.user.id, self.course.id)) def test_not_credit_course(self): """ Makes sure that get_credit_state returns None if the test course is not Credit eligible """ CourseEnrollment.enroll(self.user, self.course.id) self.credit_course.enabled = False self.credit_course.save() self.assertIsNone( self.service.get_credit_state(self.user.id, self.course.id)) def test_no_profile_name(self): """ Makes sure that get_credit_state returns None if the user does not have a corresponding UserProfile. This shouldn't happen in real environments """ profile = UserProfile.objects.get(user_id=self.user.id) profile.delete() self.assertIsNone( self.service.get_credit_state(self.user.id, self.course.id)) def test_get_and_set_credit_state(self): """ Happy path through the service """ CourseEnrollment.enroll(self.user, self.course.id) # set course requirements set_credit_requirements(self.course.id, [ { "namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": { "min_grade": 0.8 }, }, ]) # mark the grade as satisfied self.service.set_credit_requirement_status(self.user.id, self.course.id, 'grade', 'grade') credit_state = self.service.get_credit_state(self.user.id, self.course.id) self.assertIsNotNone(credit_state) self.assertEqual(credit_state['enrollment_mode'], 'honor') self.assertEqual(credit_state['profile_fullname'], 'Foo Bar') self.assertEqual(len(credit_state['credit_requirement_status']), 1) self.assertEqual(credit_state['credit_requirement_status'][0]['name'], 'grade') self.assertEqual( credit_state['credit_requirement_status'][0]['status'], 'satisfied') def test_bad_user(self): """ Try setting requirements status with a bad user_id """ # set course requirements set_credit_requirements(self.course.id, [ { "namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": { "min_grade": 0.8 }, }, ]) # mark the grade as satisfied retval = self.service.set_credit_requirement_status( 0, self.course.id, 'grade', 'grade') self.assertIsNone(retval) def test_course_id_string(self): """ Make sure we can pass a course_id (string) and get back correct results as well """ CourseEnrollment.enroll(self.user, self.course.id) # set course requirements set_credit_requirements(self.course.id, [ { "namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": { "min_grade": 0.8 }, }, ]) # mark the grade as satisfied self.service.set_credit_requirement_status(self.user.id, unicode(self.course.id), 'grade', 'grade') credit_state = self.service.get_credit_state(self.user.id, unicode(self.course.id)) self.assertIsNotNone(credit_state) self.assertEqual(credit_state['enrollment_mode'], 'honor') self.assertEqual(credit_state['profile_fullname'], 'Foo Bar') self.assertEqual(len(credit_state['credit_requirement_status']), 1) self.assertEqual(credit_state['credit_requirement_status'][0]['name'], 'grade') self.assertEqual( credit_state['credit_requirement_status'][0]['status'], 'satisfied')
def get_module_system_for_user( user, student_data, # TODO # Arguments preceding this comment have user binding, those following don't descriptor, course_id, track_function, xqueue_callback_url_prefix, request_token, position=None, wrap_xmodule_display=True, grade_bucket_type=None, static_asset_path='', user_location=None, disable_staff_debug_info=False, course=None ): """ Helper function that returns a module system and student_data bound to a user and a descriptor. The purpose of this function is to factor out everywhere a user is implicitly bound when creating a module, to allow an existing module to be re-bound to a user. Most of the user bindings happen when creating the closures that feed the instantiation of ModuleSystem. The arguments fall into two categories: those that have explicit or implicit user binding, which are user and student_data, and those don't and are just present so that ModuleSystem can be instantiated, which are all the other arguments. Ultimately, this isn't too different than how get_module_for_descriptor_internal was before refactoring. Arguments: see arguments for get_module() request_token (str): A token unique to the request use by xblock initialization Returns: (LmsModuleSystem, KvsFieldData): (module system, student_data) bound to, primarily, the user and descriptor """ def make_xqueue_callback(dispatch='score_update'): """ Returns fully qualified callback URL for external queueing system """ relative_xqueue_callback_url = reverse( 'xqueue_callback', kwargs=dict( course_id=text_type(course_id), userid=str(user.id), mod_id=text_type(descriptor.location), dispatch=dispatch ), ) return xqueue_callback_url_prefix + relative_xqueue_callback_url # Default queuename is course-specific and is derived from the course that # contains the current module. # TODO: Queuename should be derived from 'course_settings.json' of each course xqueue_default_queuename = descriptor.location.org + '-' + descriptor.location.course xqueue = { 'interface': XQUEUE_INTERFACE, 'construct_callback': make_xqueue_callback, 'default_queuename': xqueue_default_queuename.replace(' ', '_'), 'waittime': settings.XQUEUE_WAITTIME_BETWEEN_REQUESTS } def inner_get_module(descriptor): """ Delegate to get_module_for_descriptor_internal() with all values except `descriptor` set. Because it does an access check, it may return None. """ # TODO: fix this so that make_xqueue_callback uses the descriptor passed into # inner_get_module, not the parent's callback. Add it as an argument.... return get_module_for_descriptor_internal( user=user, descriptor=descriptor, student_data=student_data, course_id=course_id, track_function=track_function, xqueue_callback_url_prefix=xqueue_callback_url_prefix, position=position, wrap_xmodule_display=wrap_xmodule_display, grade_bucket_type=grade_bucket_type, static_asset_path=static_asset_path, user_location=user_location, request_token=request_token, course=course ) def get_event_handler(event_type): """ Return an appropriate function to handle the event. Returns None if no special processing is required. """ handlers = { 'grade': handle_grade_event, } if completion_waffle.waffle().is_enabled(completion_waffle.ENABLE_COMPLETION_TRACKING): handlers.update({ 'completion': handle_completion_event, 'progress': handle_deprecated_progress_event, }) return handlers.get(event_type) def publish(block, event_type, event): """ A function that allows XModules to publish events. """ handle_event = get_event_handler(event_type) if handle_event and not is_masquerading_as_specific_student(user, course_id): handle_event(block, event) else: context = contexts.course_context_from_course_id(course_id) if block.runtime.user_id: context['user_id'] = block.runtime.user_id context['asides'] = {} for aside in block.runtime.get_asides(block): if hasattr(aside, 'get_event_context'): aside_event_info = aside.get_event_context(event_type, event) if aside_event_info is not None: context['asides'][aside.scope_ids.block_type] = aside_event_info with tracker.get_tracker().context(event_type, context): track_function(event_type, event) def handle_completion_event(block, event): """ Submit a completion object for the block. """ if not completion_waffle.waffle().is_enabled(completion_waffle.ENABLE_COMPLETION_TRACKING): raise Http404 else: BlockCompletion.objects.submit_completion( user=user, course_key=course_id, block_key=block.scope_ids.usage_id, completion=event['completion'], ) def handle_grade_event(block, event): """ Submit a grade for the block. """ SCORE_PUBLISHED.send( sender=None, block=block, user=user, raw_earned=event['value'], raw_possible=event['max_value'], only_if_higher=event.get('only_if_higher'), score_deleted=event.get('score_deleted'), grader_response=event.get('grader_response') ) def handle_deprecated_progress_event(block, event): """ DEPRECATED: Submit a completion for the block represented by the progress event. This exists to support the legacy progress extension used by edx-solutions. New XBlocks should not emit these events, but instead emit completion events directly. """ if not completion_waffle.waffle().is_enabled(completion_waffle.ENABLE_COMPLETION_TRACKING): raise Http404 else: requested_user_id = event.get('user_id', user.id) if requested_user_id != user.id: log.warning("{} tried to submit a completion on behalf of {}".format(user, requested_user_id)) return # If blocks explicitly declare support for the new completion API, # we expect them to emit 'completion' events, # and we ignore the deprecated 'progress' events # in order to avoid duplicate work and possibly conflicting semantics. if not getattr(block, 'has_custom_completion', False): BlockCompletion.objects.submit_completion( user=user, course_key=course_id, block_key=block.scope_ids.usage_id, completion=1.0, ) def rebind_noauth_module_to_user(module, real_user): """ A function that allows a module to get re-bound to a real user if it was previously bound to an AnonymousUser. Will only work within a module bound to an AnonymousUser, e.g. one that's instantiated by the noauth_handler. Arguments: module (any xblock type): the module to rebind real_user (django.contrib.auth.models.User): the user to bind to Returns: nothing (but the side effect is that module is re-bound to real_user) """ if user.is_authenticated: err_msg = ("rebind_noauth_module_to_user can only be called from a module bound to " "an anonymous user") log.error(err_msg) raise LmsModuleRenderError(err_msg) field_data_cache_real_user = FieldDataCache.cache_for_descriptor_descendents( course_id, real_user, module.descriptor, asides=XBlockAsidesConfig.possible_asides(), ) student_data_real_user = KvsFieldData(DjangoKeyValueStore(field_data_cache_real_user)) (inner_system, inner_student_data) = get_module_system_for_user( user=real_user, student_data=student_data_real_user, # These have implicit user bindings, rest of args considered not to descriptor=module.descriptor, course_id=course_id, track_function=track_function, xqueue_callback_url_prefix=xqueue_callback_url_prefix, position=position, wrap_xmodule_display=wrap_xmodule_display, grade_bucket_type=grade_bucket_type, static_asset_path=static_asset_path, user_location=user_location, request_token=request_token, course=course ) module.descriptor.bind_for_student( inner_system, real_user.id, [ partial(OverrideFieldData.wrap, real_user, course), partial(LmsFieldData, student_data=inner_student_data), ], ) module.descriptor.scope_ids = ( module.descriptor.scope_ids._replace(user_id=real_user.id) ) module.scope_ids = module.descriptor.scope_ids # this is needed b/c NamedTuples are immutable # now bind the module to the new ModuleSystem instance and vice-versa module.runtime = inner_system inner_system.xmodule_instance = module # Build a list of wrapping functions that will be applied in order # to the Fragment content coming out of the xblocks that are about to be rendered. block_wrappers = [] if is_masquerading_as_specific_student(user, course_id): block_wrappers.append(filter_displayed_blocks) if settings.FEATURES.get("LICENSING", False): block_wrappers.append(wrap_with_license) # Wrap the output display in a single div to allow for the XModule # javascript to be bound correctly if wrap_xmodule_display is True: block_wrappers.append(partial( wrap_xblock, 'LmsRuntime', extra_data={'course-id': text_type(course_id)}, usage_id_serializer=lambda usage_id: quote_slashes(text_type(usage_id)), request_token=request_token, )) # TODO (cpennington): When modules are shared between courses, the static # prefix is going to have to be specific to the module, not the directory # that the xml was loaded from # Rewrite urls beginning in /static to point to course-specific content block_wrappers.append(partial( replace_static_urls, getattr(descriptor, 'data_dir', None), course_id=course_id, static_asset_path=static_asset_path or descriptor.static_asset_path )) # Allow URLs of the form '/course/' refer to the root of multicourse directory # hierarchy of this course block_wrappers.append(partial(replace_course_urls, course_id)) # this will rewrite intra-courseware links (/jump_to_id/<id>). This format # is an improvement over the /course/... format for studio authored courses, # because it is agnostic to course-hierarchy. # NOTE: module_id is empty string here. The 'module_id' will get assigned in the replacement # function, we just need to specify something to get the reverse() to work. block_wrappers.append(partial( replace_jump_to_id_urls, course_id, reverse('jump_to_id', kwargs={'course_id': text_type(course_id), 'module_id': ''}), )) if settings.FEATURES.get('DISPLAY_DEBUG_INFO_TO_STAFF'): if is_masquerading_as_specific_student(user, course_id): # When masquerading as a specific student, we want to show the debug button # unconditionally to enable resetting the state of the student we are masquerading as. # We already know the user has staff access when masquerading is active. staff_access = True # To figure out whether the user has instructor access, we temporarily remove the # masquerade_settings from the real_user. With the masquerading settings in place, # the result would always be "False". masquerade_settings = user.real_user.masquerade_settings del user.real_user.masquerade_settings user.real_user.masquerade_settings = masquerade_settings else: staff_access = has_access(user, 'staff', descriptor, course_id) if staff_access: block_wrappers.append(partial(add_staff_markup, user, disable_staff_debug_info)) # These modules store data using the anonymous_student_id as a key. # To prevent loss of data, we will continue to provide old modules with # the per-student anonymized id (as we have in the past), # while giving selected modules a per-course anonymized id. # As we have the time to manually test more modules, we can add to the list # of modules that get the per-course anonymized id. is_pure_xblock = isinstance(descriptor, XBlock) and not isinstance(descriptor, XModuleDescriptor) module_class = getattr(descriptor, 'module_class', None) is_lti_module = not is_pure_xblock and issubclass(module_class, LTIModule) if is_pure_xblock or is_lti_module: anonymous_student_id = anonymous_id_for_user(user, course_id) else: anonymous_student_id = anonymous_id_for_user(user, None) field_data = LmsFieldData(descriptor._field_data, student_data) # pylint: disable=protected-access user_is_staff = bool(has_access(user, u'staff', descriptor.location, course_id)) system = LmsModuleSystem( track_function=track_function, render_template=render_to_string, static_url=settings.STATIC_URL, xqueue=xqueue, # TODO (cpennington): Figure out how to share info between systems filestore=descriptor.runtime.resources_fs, get_module=inner_get_module, user=user, debug=settings.DEBUG, hostname=settings.SITE_NAME, # TODO (cpennington): This should be removed when all html from # a module is coming through get_html and is therefore covered # by the replace_static_urls code below replace_urls=partial( static_replace.replace_static_urls, data_directory=getattr(descriptor, 'data_dir', None), course_id=course_id, static_asset_path=static_asset_path or descriptor.static_asset_path, ), replace_course_urls=partial( static_replace.replace_course_urls, course_key=course_id ), replace_jump_to_id_urls=partial( static_replace.replace_jump_to_id_urls, course_id=course_id, jump_to_id_base_url=reverse('jump_to_id', kwargs={'course_id': text_type(course_id), 'module_id': ''}) ), node_path=settings.NODE_PATH, publish=publish, anonymous_student_id=anonymous_student_id, course_id=course_id, cache=cache, can_execute_unsafe_code=(lambda: can_execute_unsafe_code(course_id)), get_python_lib_zip=(lambda: get_python_lib_zip(contentstore, course_id)), # TODO: When we merge the descriptor and module systems, we can stop reaching into the mixologist (cpennington) mixins=descriptor.runtime.mixologist._mixins, # pylint: disable=protected-access wrappers=block_wrappers, get_real_user=user_by_anonymous_id, services={ 'fs': FSService(), 'field-data': field_data, 'user': DjangoXBlockUserService(user, user_is_staff=user_is_staff), 'verification': XBlockVerificationService(), 'proctoring': ProctoringService(), 'milestones': milestones_helpers.get_service(), 'credit': CreditService(), 'bookmarks': BookmarksService(user=user), 'gating': GatingService(), }, get_user_role=lambda: get_user_role(user, course_id), descriptor_runtime=descriptor._runtime, # pylint: disable=protected-access rebind_noauth_module_to_user=rebind_noauth_module_to_user, user_location=user_location, request_token=request_token, ) # pass position specified in URL to module through ModuleSystem if position is not None: try: position = int(position) except (ValueError, TypeError): log.exception('Non-integer %r passed as position.', position) position = None system.set('position', position) system.set(u'user_is_staff', user_is_staff) system.set(u'user_is_admin', bool(has_access(user, u'staff', 'global'))) system.set(u'user_is_beta_tester', CourseBetaTesterRole(course_id).has_user(user)) system.set(u'days_early_for_beta', descriptor.days_early_for_beta) # make an ErrorDescriptor -- assuming that the descriptor's system is ok if has_access(user, u'staff', descriptor.location, course_id): system.error_descriptor_class = ErrorDescriptor else: system.error_descriptor_class = NonStaffErrorDescriptor return system, field_data
def get_module_system_for_user(user, student_data, # TODO # pylint: disable=too-many-statements # Arguments preceding this comment have user binding, those following don't descriptor, course_id, track_function, xqueue_callback_url_prefix, request_token, position=None, wrap_xmodule_display=True, grade_bucket_type=None, static_asset_path='', user_location=None, disable_staff_debug_info=False, course=None): """ Helper function that returns a module system and student_data bound to a user and a descriptor. The purpose of this function is to factor out everywhere a user is implicitly bound when creating a module, to allow an existing module to be re-bound to a user. Most of the user bindings happen when creating the closures that feed the instantiation of ModuleSystem. The arguments fall into two categories: those that have explicit or implicit user binding, which are user and student_data, and those don't and are just present so that ModuleSystem can be instantiated, which are all the other arguments. Ultimately, this isn't too different than how get_module_for_descriptor_internal was before refactoring. Arguments: see arguments for get_module() request_token (str): A token unique to the request use by xblock initialization Returns: (LmsModuleSystem, KvsFieldData): (module system, student_data) bound to, primarily, the user and descriptor """ def make_xqueue_callback(dispatch='score_update'): """ Returns fully qualified callback URL for external queueing system """ relative_xqueue_callback_url = reverse( 'xqueue_callback', kwargs=dict( course_id=course_id.to_deprecated_string(), userid=str(user.id), mod_id=descriptor.location.to_deprecated_string(), dispatch=dispatch ), ) return xqueue_callback_url_prefix + relative_xqueue_callback_url # Default queuename is course-specific and is derived from the course that # contains the current module. # TODO: Queuename should be derived from 'course_settings.json' of each course xqueue_default_queuename = descriptor.location.org + '-' + descriptor.location.course xqueue = { 'interface': XQUEUE_INTERFACE, 'construct_callback': make_xqueue_callback, 'default_queuename': xqueue_default_queuename.replace(' ', '_'), 'waittime': settings.XQUEUE_WAITTIME_BETWEEN_REQUESTS } def inner_get_module(descriptor): """ Delegate to get_module_for_descriptor_internal() with all values except `descriptor` set. Because it does an access check, it may return None. """ # TODO: fix this so that make_xqueue_callback uses the descriptor passed into # inner_get_module, not the parent's callback. Add it as an argument.... return get_module_for_descriptor_internal( user=user, descriptor=descriptor, student_data=student_data, course_id=course_id, track_function=track_function, xqueue_callback_url_prefix=xqueue_callback_url_prefix, position=position, wrap_xmodule_display=wrap_xmodule_display, grade_bucket_type=grade_bucket_type, static_asset_path=static_asset_path, user_location=user_location, request_token=request_token, course=course ) def _fulfill_content_milestones(user, course_key, content_key): """ Internal helper to handle milestone fulfillments for the specified content module """ # Fulfillment Use Case: Entrance Exam # If this module is part of an entrance exam, we'll need to see if the student # has reached the point at which they can collect the associated milestone if milestones_helpers.is_entrance_exams_enabled(): course = modulestore().get_course(course_key) content = modulestore().get_item(content_key) entrance_exam_enabled = getattr(course, 'entrance_exam_enabled', False) in_entrance_exam = getattr(content, 'in_entrance_exam', False) if entrance_exam_enabled and in_entrance_exam: # We don't have access to the true request object in this context, but we can use a mock request = RequestFactory().request() request.user = user exam_pct = get_entrance_exam_score(request, course) if exam_pct >= course.entrance_exam_minimum_score_pct: exam_key = UsageKey.from_string(course.entrance_exam_id) relationship_types = milestones_helpers.get_milestone_relationship_types() content_milestones = milestones_helpers.get_course_content_milestones( course_key, exam_key, relationship=relationship_types['FULFILLS'] ) # Add each milestone to the user's set... user = {'id': request.user.id} for milestone in content_milestones: milestones_helpers.add_user_milestone(user, milestone) def handle_grade_event(block, event_type, event): # pylint: disable=unused-argument """ Manages the workflow for recording and updating of student module grade state """ user_id = user.id grade = event.get('value') max_grade = event.get('max_value') set_score( user_id, descriptor.location, grade, max_grade, ) # Bin score into range and increment stats score_bucket = get_score_bucket(grade, max_grade) tags = [ u"org:{}".format(course_id.org), u"course:{}".format(course_id), u"score_bucket:{0}".format(score_bucket) ] if grade_bucket_type is not None: tags.append('type:%s' % grade_bucket_type) dog_stats_api.increment("lms.courseware.question_answered", tags=tags) # Cycle through the milestone fulfillment scenarios to see if any are now applicable # thanks to the updated grading information that was just submitted _fulfill_content_milestones( user, course_id, descriptor.location, ) # Send a signal out to any listeners who are waiting for score change # events. SCORE_CHANGED.send( sender=None, points_possible=event['max_value'], points_earned=event['value'], user_id=user_id, course_id=unicode(course_id), usage_id=unicode(descriptor.location) ) def publish(block, event_type, event): """A function that allows XModules to publish events.""" if event_type == 'grade' and not is_masquerading_as_specific_student(user, course_id): handle_grade_event(block, event_type, event) else: track_function(event_type, event) def rebind_noauth_module_to_user(module, real_user): """ A function that allows a module to get re-bound to a real user if it was previously bound to an AnonymousUser. Will only work within a module bound to an AnonymousUser, e.g. one that's instantiated by the noauth_handler. Arguments: module (any xblock type): the module to rebind real_user (django.contrib.auth.models.User): the user to bind to Returns: nothing (but the side effect is that module is re-bound to real_user) """ if user.is_authenticated(): err_msg = ("rebind_noauth_module_to_user can only be called from a module bound to " "an anonymous user") log.error(err_msg) raise LmsModuleRenderError(err_msg) field_data_cache_real_user = FieldDataCache.cache_for_descriptor_descendents( course_id, real_user, module.descriptor, asides=XBlockAsidesConfig.possible_asides(), ) student_data_real_user = KvsFieldData(DjangoKeyValueStore(field_data_cache_real_user)) (inner_system, inner_student_data) = get_module_system_for_user( user=real_user, student_data=student_data_real_user, # These have implicit user bindings, rest of args considered not to descriptor=module.descriptor, course_id=course_id, track_function=track_function, xqueue_callback_url_prefix=xqueue_callback_url_prefix, position=position, wrap_xmodule_display=wrap_xmodule_display, grade_bucket_type=grade_bucket_type, static_asset_path=static_asset_path, user_location=user_location, request_token=request_token, course=course ) module.descriptor.bind_for_student( inner_system, real_user.id, [ partial(OverrideFieldData.wrap, real_user, course), partial(LmsFieldData, student_data=inner_student_data), ], ) module.descriptor.scope_ids = ( module.descriptor.scope_ids._replace(user_id=real_user.id) ) module.scope_ids = module.descriptor.scope_ids # this is needed b/c NamedTuples are immutable # now bind the module to the new ModuleSystem instance and vice-versa module.runtime = inner_system inner_system.xmodule_instance = module # Build a list of wrapping functions that will be applied in order # to the Fragment content coming out of the xblocks that are about to be rendered. block_wrappers = [] if is_masquerading_as_specific_student(user, course_id): block_wrappers.append(filter_displayed_blocks) if settings.FEATURES.get("LICENSING", False): block_wrappers.append(wrap_with_license) # Wrap the output display in a single div to allow for the XModule # javascript to be bound correctly if wrap_xmodule_display is True: block_wrappers.append(partial( wrap_xblock, 'LmsRuntime', extra_data={'course-id': course_id.to_deprecated_string()}, usage_id_serializer=lambda usage_id: quote_slashes(usage_id.to_deprecated_string()), request_token=request_token, )) # TODO (cpennington): When modules are shared between courses, the static # prefix is going to have to be specific to the module, not the directory # that the xml was loaded from # Rewrite urls beginning in /static to point to course-specific content block_wrappers.append(partial( replace_static_urls, getattr(descriptor, 'data_dir', None), course_id=course_id, static_asset_path=static_asset_path or descriptor.static_asset_path )) # Allow URLs of the form '/course/' refer to the root of multicourse directory # hierarchy of this course block_wrappers.append(partial(replace_course_urls, course_id)) # this will rewrite intra-courseware links (/jump_to_id/<id>). This format # is an improvement over the /course/... format for studio authored courses, # because it is agnostic to course-hierarchy. # NOTE: module_id is empty string here. The 'module_id' will get assigned in the replacement # function, we just need to specify something to get the reverse() to work. block_wrappers.append(partial( replace_jump_to_id_urls, course_id, reverse('jump_to_id', kwargs={'course_id': course_id.to_deprecated_string(), 'module_id': ''}), )) if settings.FEATURES.get('DISPLAY_DEBUG_INFO_TO_STAFF'): if is_masquerading_as_specific_student(user, course_id): # When masquerading as a specific student, we want to show the debug button # unconditionally to enable resetting the state of the student we are masquerading as. # We already know the user has staff access when masquerading is active. staff_access = True # To figure out whether the user has instructor access, we temporarily remove the # masquerade_settings from the real_user. With the masquerading settings in place, # the result would always be "False". masquerade_settings = user.real_user.masquerade_settings del user.real_user.masquerade_settings instructor_access = bool(has_access(user.real_user, 'instructor', descriptor, course_id)) user.real_user.masquerade_settings = masquerade_settings else: staff_access = has_access(user, 'staff', descriptor, course_id) instructor_access = bool(has_access(user, 'instructor', descriptor, course_id)) if staff_access: block_wrappers.append(partial(add_staff_markup, user, instructor_access, disable_staff_debug_info)) # These modules store data using the anonymous_student_id as a key. # To prevent loss of data, we will continue to provide old modules with # the per-student anonymized id (as we have in the past), # while giving selected modules a per-course anonymized id. # As we have the time to manually test more modules, we can add to the list # of modules that get the per-course anonymized id. is_pure_xblock = isinstance(descriptor, XBlock) and not isinstance(descriptor, XModuleDescriptor) module_class = getattr(descriptor, 'module_class', None) is_lti_module = not is_pure_xblock and issubclass(module_class, LTIModule) if is_pure_xblock or is_lti_module: anonymous_student_id = anonymous_id_for_user(user, course_id) else: anonymous_student_id = anonymous_id_for_user(user, None) field_data = LmsFieldData(descriptor._field_data, student_data) # pylint: disable=protected-access user_is_staff = bool(has_access(user, u'staff', descriptor.location, course_id)) system = LmsModuleSystem( track_function=track_function, render_template=render_to_string, static_url=settings.STATIC_URL, xqueue=xqueue, # TODO (cpennington): Figure out how to share info between systems filestore=descriptor.runtime.resources_fs, get_module=inner_get_module, user=user, debug=settings.DEBUG, hostname=settings.SITE_NAME, # TODO (cpennington): This should be removed when all html from # a module is coming through get_html and is therefore covered # by the replace_static_urls code below replace_urls=partial( static_replace.replace_static_urls, data_directory=getattr(descriptor, 'data_dir', None), course_id=course_id, static_asset_path=static_asset_path or descriptor.static_asset_path, ), replace_course_urls=partial( static_replace.replace_course_urls, course_key=course_id ), replace_jump_to_id_urls=partial( static_replace.replace_jump_to_id_urls, course_id=course_id, jump_to_id_base_url=reverse('jump_to_id', kwargs={'course_id': course_id.to_deprecated_string(), 'module_id': ''}) ), node_path=settings.NODE_PATH, publish=publish, anonymous_student_id=anonymous_student_id, course_id=course_id, cache=cache, can_execute_unsafe_code=(lambda: can_execute_unsafe_code(course_id)), get_python_lib_zip=(lambda: get_python_lib_zip(contentstore, course_id)), # TODO: When we merge the descriptor and module systems, we can stop reaching into the mixologist (cpennington) mixins=descriptor.runtime.mixologist._mixins, # pylint: disable=protected-access wrappers=block_wrappers, get_real_user=user_by_anonymous_id, services={ 'fs': FSService(), 'field-data': field_data, 'user': DjangoXBlockUserService(user, user_is_staff=user_is_staff), "reverification": ReverificationService(), 'proctoring': ProctoringService(), 'credit': CreditService(), 'bookmarks': BookmarksService(user=user), }, get_user_role=lambda: get_user_role(user, course_id), descriptor_runtime=descriptor._runtime, # pylint: disable=protected-access rebind_noauth_module_to_user=rebind_noauth_module_to_user, user_location=user_location, request_token=request_token, ) # pass position specified in URL to module through ModuleSystem if position is not None: try: position = int(position) except (ValueError, TypeError): log.exception('Non-integer %r passed as position.', position) position = None system.set('position', position) system.set(u'user_is_staff', user_is_staff) system.set(u'user_is_admin', bool(has_access(user, u'staff', 'global'))) system.set(u'user_is_beta_tester', CourseBetaTesterRole(course_id).has_user(user)) system.set(u'days_early_for_beta', descriptor.days_early_for_beta) # make an ErrorDescriptor -- assuming that the descriptor's system is ok if has_access(user, u'staff', descriptor.location, course_id): system.error_descriptor_class = ErrorDescriptor else: system.error_descriptor_class = NonStaffErrorDescriptor return system, field_data