def test_toc_toy_from_section(self): chapter = 'Overview' chapter_url = '%s/%s/%s' % ('/courses', self.course_name, chapter) section = 'Welcome' factory = RequestFactory() request = factory.get(chapter_url) model_data_cache = ModelDataCache.cache_for_descriptor_descendents( self.toy_course.id, self.portal_user, self.toy_course, depth=2) expected = ([{'active': True, 'sections': [{'url_name': 'Toy_Videos', 'display_name': u'Toy Videos', 'graded': True, 'format': u'Lecture Sequence', 'due': None, 'active': False}, {'url_name': 'Welcome', 'display_name': u'Welcome', 'graded': True, 'format': '', 'due': None, 'active': True}, {'url_name': 'video_123456789012', 'display_name': 'video 123456789012', 'graded': True, 'format': '', 'due': None, 'active': False}, {'url_name': 'video_4f66f493ac8f', 'display_name': 'video 4f66f493ac8f', 'graded': True, 'format': '', 'due': None, 'active': False}], 'url_name': 'Overview', 'display_name': u'Overview'}, {'active': False, 'sections': [{'url_name': 'toyvideo', 'display_name': 'toyvideo', 'graded': True, 'format': '', 'due': None, 'active': False}], 'url_name': 'secret:magic', 'display_name': 'secret:magic'}]) actual = render.toc_for_course( self.portal_user, request, self.toy_course, chapter, section, model_data_cache) self.assertEqual(expected, actual)
def setUp(self): self.desc_md = {} self.user = UserFactory.create(username='******') self.mdc = ModelDataCache( [mock_descriptor([mock_field(Scope.user_state, 'a_field')])], course_id, self.user) self.kvs = LmsKeyValueStore(self.desc_md, self.mdc)
def _get_module_instance_for_task(course_id, student, module_descriptor, xmodule_instance_args=None, grade_bucket_type=None): """ Fetches a StudentModule instance for a given `course_id`, `student` object, and `module_descriptor`. `xmodule_instance_args` is used to provide information for creating a track function and an XQueue callback. These are passed, along with `grade_bucket_type`, to get_module_for_descriptor_internal, which sidesteps the need for a Request object when instantiating an xmodule instance. """ # reconstitute the problem's corresponding XModule: model_data_cache = ModelDataCache.cache_for_descriptor_descendents(course_id, student, module_descriptor) # get request-related tracking information from args passthrough, and supplement with task-specific # information: request_info = xmodule_instance_args.get('request_info', {}) if xmodule_instance_args is not None else {} task_info = {"student": student.username, "task_id": _get_task_id_from_xmodule_args(xmodule_instance_args)} def make_track_function(): ''' Make a tracking function that logs what happened. For insertion into ModuleSystem, and used by CapaModule, which will provide the event_type (as string) and event (as dict) as arguments. The request_info and task_info (and page) are provided here. ''' return lambda event_type, event: task_track(request_info, task_info, event_type, event, page='x_module_task') xqueue_callback_url_prefix = xmodule_instance_args.get('xqueue_callback_url_prefix', '') \ if xmodule_instance_args is not None else '' return get_module_for_descriptor_internal(student, module_descriptor, model_data_cache, course_id, make_track_function(), xqueue_callback_url_prefix, grade_bucket_type=grade_bucket_type)
def test_module_render_with_jump_to_id(self): """ This test validates that the /jump_to_id/<id> shorthand for intracourse linking works assertIn expected. Note there's a HTML element in the 'toy' course with the url_name 'toyjumpto' which defines this linkage """ mock_request = MagicMock() mock_request.user = self.mock_user course = get_course_with_access(self.mock_user, self.course_id, "load") model_data_cache = ModelDataCache.cache_for_descriptor_descendents( self.course_id, self.mock_user, course, depth=2 ) module = render.get_module( self.mock_user, mock_request, ["i4x", "edX", "toy", "html", "toyjumpto"], model_data_cache, self.course_id ) # get the rendered HTML output which should have the rewritten link html = module.get_html() # See if the url got rewritten to the target link # note if the URL mapping changes then this assertion will break self.assertIn("/courses/" + self.course_id + "/jump_to_id/vertical_test", html)
def test_module_render_with_jump_to_id(self): """ This test validates that the /jump_to_id/<id> shorthand for intracourse linking works assertIn expected. Note there's a HTML element in the 'toy' course with the url_name 'toyjumpto' which defines this linkage """ mock_request = MagicMock() mock_request.user = self.mock_user course = get_course_with_access(self.mock_user, self.course_id, 'load') model_data_cache = ModelDataCache.cache_for_descriptor_descendents( self.course_id, self.mock_user, course, depth=2) module = render.get_module( self.mock_user, mock_request, ['i4x', 'edX', 'toy', 'html', 'toyjumpto'], model_data_cache, self.course_id ) # get the rendered HTML output which should have the rewritten link html = module.get_html() # See if the url got rewritten to the target link # note if the URL mapping changes then this assertion will break self.assertIn('/courses/'+self.course_id+'/jump_to_id/vertical_test', html)
def check_for_active_timelimit_module(request, course_id, course): """ Looks for a timing module for the given user and course that is currently active. If found, returns a context dict with timer-related values to enable display of time remaining. """ context = {} # TODO (cpennington): Once we can query the course structure, replace this with such a query timelimit_student_modules = StudentModule.objects.filter(student=request.user, course_id=course_id, module_type='timelimit') if timelimit_student_modules: for timelimit_student_module in timelimit_student_modules: # get the corresponding section_descriptor for the given StudentModel entry: module_state_key = timelimit_student_module.module_state_key timelimit_descriptor = modulestore().get_instance(course_id, Location(module_state_key)) timelimit_module_cache = ModelDataCache.cache_for_descriptor_descendents(course.id, request.user, timelimit_descriptor, depth=None) timelimit_module = get_module_for_descriptor(request.user, request, timelimit_descriptor, timelimit_module_cache, course.id, position=None) if timelimit_module is not None and timelimit_module.category == 'timelimit' and \ timelimit_module.has_begun and not timelimit_module.has_ended: location = timelimit_module.location # determine where to go when the timer expires: if timelimit_descriptor.time_expired_redirect_url is None: raise Http404("no time_expired_redirect_url specified at this location: {} ".format(timelimit_module.location)) context['time_expired_redirect_url'] = timelimit_descriptor.time_expired_redirect_url # Fetch the remaining time relative to the end time as stored in the module when it was started. # This value should be in milliseconds. remaining_time = timelimit_module.get_remaining_time_in_ms() context['timer_expiration_duration'] = remaining_time context['suppress_toplevel_navigation'] = timelimit_descriptor.suppress_toplevel_navigation return_url = reverse('jump_to', kwargs={'course_id': course_id, 'location': location}) context['timer_navigation_return_url'] = return_url return context
def get_course_info_section(request, course, section_key): """ This returns the snippet of html to be rendered on the course info page, given the key for the section. Valid keys: - handouts - guest_handouts - updates - guest_updates """ loc = Location(course.location.tag, course.location.org, course.location.course, 'course_info', section_key) # Use an empty cache model_data_cache = ModelDataCache([], course.id, request.user) info_module = get_module(request.user, request, loc, model_data_cache, course.id, wrap_xmodule_display=False, static_asset_path=course.lms.static_asset_path) html = '' if info_module is not None: html = info_module.runtime.render(info_module, None, 'student_view').content return html
def test_toc_toy_from_section(self): chapter = 'Overview' chapter_url = '%s/%s/%s' % ('/courses', self.course_name, chapter) section = 'Welcome' factory = RequestFactory() request = factory.get(chapter_url) model_data_cache = ModelDataCache.cache_for_descriptor_descendents( self.toy_course.id, self.portal_user, self.toy_course, depth=2) expected = ([{'active': True, 'sections': [{'url_name': 'Toy_Videos', 'display_name': u'Toy Videos', 'graded': True, 'format': u'Lecture Sequence', 'due': None, 'active': False}, {'url_name': 'Welcome', 'display_name': u'Welcome', 'graded': True, 'format': '', 'due': None, 'active': True}, {'url_name': 'video_123456789012', 'display_name': 'video 123456789012', 'graded': True, 'format': '', 'due': None, 'active': False}, {'url_name': 'video_4f66f493ac8f', 'display_name': 'video 4f66f493ac8f', 'graded': True, 'format': '', 'due': None, 'active': False}], 'url_name': 'Overview', 'display_name': u'Overview'}, {'active': False, 'sections': [{'url_name': 'toyvideo', 'display_name': 'toyvideo', 'graded': True, 'format': '', 'due': None, 'active': False}], 'url_name': 'secret:magic', 'display_name': 'secret:magic'}]) actual = render.toc_for_course(self.portal_user, request, self.toy_course, chapter, section, model_data_cache) self.assertEqual(expected, actual)
def setUp(self): field_storage = self.factory.create() if hasattr(field_storage, 'student'): self.user = field_storage.student else: self.user = UserFactory.create() self.desc_md = {} self.mdc = ModelDataCache([mock_descriptor([mock_field(self.scope, 'existing_field')])], course_id, self.user) self.kvs = LmsKeyValueStore(self.desc_md, self.mdc)
def setUp(self): self.desc_md = {} student_module = StudentModuleFactory( state=json.dumps({'a_field': 'a_value'})) self.user = student_module.student self.mdc = ModelDataCache( [mock_descriptor([mock_field(Scope.user_state, 'a_field')])], course_id, self.user) self.kvs = LmsKeyValueStore(self.desc_md, self.mdc)
def render_accordion(request, course, chapter, section, model_data_cache): """ Draws navigation bar. Takes current position in accordion as parameter. If chapter and section are '' or None, renders a default accordion. course, chapter, and section are the url_names. Returns the html string """ staff_access = has_access(request.user, course, "staff") # NOTE: To make sure impersonation by instructor works, use # student instead of request.user in the rest of the function. # The pre-fetching of groups is done to make auth checks not require an # additional DB lookup (this kills the Progress page in particular). course_id = course.id student_id = None if student_id is None or student_id == request.user.id: # always allowed to see your own profile student = request.user else: # Requesting access to a different student's profile if not staff_access: raise Http404 student = User.objects.get(id=int(student_id)) student = User.objects.prefetch_related("groups").get(id=student.id) model_data_cache = ModelDataCache.cache_for_descriptor_descendents(course_id, student, course, depth=None) courseware_summary = grades.progress_summary(student, request, course, model_data_cache) print("<-------------") print(courseware_summary) print("------------->") # grab the table of contents user = User.objects.prefetch_related("groups").get(id=request.user.id) request.user = user # keep just one instance of User toc = toc_for_course(user, request, course, chapter, section, model_data_cache) context = dict( [ ("toc", toc), ("course_id", course.id), ("csrf", csrf(request)["csrf_token"]), ("show_timezone", course.show_timezone), ("courseware_summary", courseware_summary), ] + template_imports.items() ) return render_to_string("courseware/accordion.html", context)
def get_grade_summary(self): '''calls grades.grade for current user and course''' model_data_cache = ModelDataCache.cache_for_descriptor_descendents( self.course.id, self.student_user, self.course) fake_request = self.factory.get(reverse('progress', kwargs={'course_id': self.course.id})) return grades.grade(self.student_user, fake_request, self.course, model_data_cache)
def get_grade_summary(self): '''calls grades.grade for current user and course''' model_data_cache = ModelDataCache.cache_for_descriptor_descendents( self.course.id, self.student_user, self.course) fake_request = self.factory.get( reverse('progress', kwargs={'course_id': self.course.id})) return grades.grade(self.student_user, fake_request, self.course, model_data_cache)
def xqueue_callback(request, course_id, userid, mod_id, dispatch): ''' Entry point for graded results from the queueing system. ''' data = request.POST.copy() # Test xqueue package, which we expect to be: # xpackage = {'xqueue_header': json.dumps({'lms_key':'secretkey',...}), # 'xqueue_body' : 'Message from grader'} for key in ['xqueue_header', 'xqueue_body']: if key not in data: raise Http404 header = json.loads(data['xqueue_header']) if not isinstance(header, dict) or 'lms_key' not in header: raise Http404 # Retrieve target StudentModule user = User.objects.get(id=userid) model_data_cache = ModelDataCache.cache_for_descriptor_descendents( course_id, user, modulestore().get_instance(course_id, mod_id), depth=0, select_for_update=True) instance = get_module(user, request, mod_id, model_data_cache, course_id, grade_bucket_type='xqueue') if instance is None: msg = "No module {0} for user {1}--access denied?".format(mod_id, user) log.debug(msg) raise Http404 # Transfer 'queuekey' from xqueue response header to the data. # This is required to use the interface defined by 'handle_ajax' data.update({'queuekey': header['lms_key']}) # We go through the "AJAX" path # So far, the only dispatch from xqueue will be 'score_update' try: # Can ignore the return value--not used for xqueue_callback instance.handle_ajax(dispatch, data) except: log.exception("error processing ajax call") raise return HttpResponse("")
def progress(request, course_id, student_id=None): """ User progress. We show the grade bar and every problem score. Course staff are allowed to see the progress of students in their class. """ course = get_course_with_access(request.user, course_id, 'load', depth=None) staff_access = has_access(request.user, course, 'staff') if student_id is None or student_id == request.user.id: # always allowed to see your own profile student = request.user else: # Requesting access to a different student's profile if not staff_access: raise Http404 student = User.objects.get(id=int(student_id)) # NOTE: To make sure impersonation by instructor works, use # student instead of request.user in the rest of the function. # The pre-fetching of groups is done to make auth checks not require an # additional DB lookup (this kills the Progress page in particular). student = User.objects.prefetch_related("groups").get(id=student.id) model_data_cache = ModelDataCache.cache_for_descriptor_descendents( course_id, student, course, depth=None) courseware_summary = grades.progress_summary(student, request, course, model_data_cache) grade_summary = grades.grade(student, request, course, model_data_cache) if courseware_summary is None: # This means the student didn't have access to the course (which the # instructor requested) raise Http404 context = { 'course': course, 'courseware_summary': courseware_summary, 'grade_summary': grade_summary, 'staff_access': staff_access, 'student': student, } context.update() return render_to_response('courseware/progress.html', context)
def get_static_tab_contents(request, course, tab): loc = Location(course.location.tag, course.location.org, course.location.course, 'static_tab', tab['url_slug']) model_data_cache = ModelDataCache.cache_for_descriptor_descendents(course.id, request.user, modulestore().get_instance(course.id, loc), depth=0) tab_module = get_module(request.user, request, loc, model_data_cache, course.id) logging.debug('course_module = {0}'.format(tab_module)) html = '' if tab_module is not None: html = tab_module.runtime.render(tab_module, None, 'student_view').content return html
def get_static_tab_contents(request, course, tab): loc = Location(course.location.tag, course.location.org, course.location.course, 'static_tab', tab['url_slug']) model_data_cache = ModelDataCache.cache_for_descriptor_descendents(course.id, request.user, modulestore().get_instance(course.id, loc), depth=0) tab_module = get_module(request.user, request, loc, model_data_cache, course.id) logging.debug('course_module = {0}'.format(tab_module)) html = '' if tab_module is not None: html = tab_module.get_html() return html
def _get_module_instance_for_task(course_id, student, module_descriptor, xmodule_instance_args=None, grade_bucket_type=None): """ Fetches a StudentModule instance for a given `course_id`, `student` object, and `module_descriptor`. `xmodule_instance_args` is used to provide information for creating a track function and an XQueue callback. These are passed, along with `grade_bucket_type`, to get_module_for_descriptor_internal, which sidesteps the need for a Request object when instantiating an xmodule instance. """ # reconstitute the problem's corresponding XModule: model_data_cache = ModelDataCache.cache_for_descriptor_descendents( course_id, student, module_descriptor) # get request-related tracking information from args passthrough, and supplement with task-specific # information: request_info = xmodule_instance_args.get( 'request_info', {}) if xmodule_instance_args is not None else {} task_info = { "student": student.username, "task_id": _get_task_id_from_xmodule_args(xmodule_instance_args) } def make_track_function(): ''' Make a tracking function that logs what happened. For insertion into ModuleSystem, and used by CapaModule, which will provide the event_type (as string) and event (as dict) as arguments. The request_info and task_info (and page) are provided here. ''' return lambda event_type, event: task_track( request_info, task_info, event_type, event, page='x_module_task') xqueue_callback_url_prefix = xmodule_instance_args.get('xqueue_callback_url_prefix', '') \ if xmodule_instance_args is not None else '' return get_module_for_descriptor_internal( student, module_descriptor, model_data_cache, course_id, make_track_function(), xqueue_callback_url_prefix, grade_bucket_type=grade_bucket_type)
def setUp(self): self.user = UserFactory.create() self.request = RequestFactory().get('/') self.request.user = self.user self.request.session = {} self.course = CourseFactory.create() self.content_string = '<p>This is the content<p>' self.rewrite_link = '<a href="/static/foo/content">Test rewrite</a>' self.rewrite_bad_link = '<img src="/static//file.jpg" />' self.course_link = '<a href="/course/bar/content">Test course rewrite</a>' self.descriptor = ItemFactory.create( category='html', data=self.content_string + self.rewrite_link + self.rewrite_bad_link + self.course_link) self.location = self.descriptor.location self.model_data_cache = ModelDataCache.cache_for_descriptor_descendents( self.course.id, self.user, self.descriptor)
def xqueue_callback(request, course_id, userid, mod_id, dispatch): ''' Entry point for graded results from the queueing system. ''' data = request.POST.copy() # Test xqueue package, which we expect to be: # xpackage = {'xqueue_header': json.dumps({'lms_key':'secretkey',...}), # 'xqueue_body' : 'Message from grader'} for key in ['xqueue_header', 'xqueue_body']: if key not in data: raise Http404 header = json.loads(data['xqueue_header']) if not isinstance(header, dict) or 'lms_key' not in header: raise Http404 # Retrieve target StudentModule user = User.objects.get(id=userid) model_data_cache = ModelDataCache.cache_for_descriptor_descendents( course_id, user, modulestore().get_instance(course_id, mod_id), depth=0, select_for_update=True ) instance = get_module(user, request, mod_id, model_data_cache, course_id, grade_bucket_type='xqueue') if instance is None: msg = "No module {0} for user {1}--access denied?".format(mod_id, user) log.debug(msg) raise Http404 # Transfer 'queuekey' from xqueue response header to the data. # This is required to use the interface defined by 'handle_ajax' data.update({'queuekey': header['lms_key']}) # We go through the "AJAX" path # So far, the only dispatch from xqueue will be 'score_update' try: # Can ignore the return value--not used for xqueue_callback instance.handle_ajax(dispatch, data) except: log.exception("error processing ajax call") raise return HttpResponse("")
def setUp(self): self.user = UserFactory.create() self.request = RequestFactory().get("/") self.request.user = self.user self.request.session = {} self.course = CourseFactory.create() self.content_string = "<p>This is the content<p>" self.rewrite_link = '<a href="/static/foo/content">Test rewrite</a>' self.rewrite_bad_link = '<img src="/static//file.jpg" />' self.course_link = '<a href="/course/bar/content">Test course rewrite</a>' self.descriptor = ItemFactory.create( category="html", data=self.content_string + self.rewrite_link + self.rewrite_bad_link + self.course_link ) self.location = self.descriptor.location self.model_data_cache = ModelDataCache.cache_for_descriptor_descendents( self.course.id, self.user, self.descriptor )
def find_target_student_module(request, user_id, course_id, mod_id): """ Retrieve target StudentModule """ user = User.objects.get(id=user_id) model_data_cache = ModelDataCache.cache_for_descriptor_descendents( course_id, user, modulestore().get_instance(course_id, mod_id), depth=0, select_for_update=True ) instance = get_module(user, request, mod_id, model_data_cache, course_id, grade_bucket_type='xqueue') if instance is None: msg = "No module {0} for user {1}--access denied?".format(mod_id, user) log.debug(msg) raise Http404 return instance
def progress(request, course_id, student_id=None): """ User progress. We show the grade bar and every problem score. Course staff are allowed to see the progress of students in their class. """ course = get_course_with_access(request.user, course_id, 'load', depth=None) staff_access = has_access(request.user, course, 'staff') if student_id is None or student_id == request.user.id: # always allowed to see your own profile student = request.user else: # Requesting access to a different student's profile if not staff_access: raise Http404 student = User.objects.get(id=int(student_id)) # NOTE: To make sure impersonation by instructor works, use # student instead of request.user in the rest of the function. # The pre-fetching of groups is done to make auth checks not require an # additional DB lookup (this kills the Progress page in particular). student = User.objects.prefetch_related("groups").get(id=student.id) model_data_cache = ModelDataCache.cache_for_descriptor_descendents( course_id, student, course, depth=None) courseware_summary = grades.progress_summary(student, request, course, model_data_cache) grade_summary = grades.grade(student, request, course, model_data_cache) if courseware_summary is None: #This means the student didn't have access to the course (which the instructor requested) raise Http404 context = {'course': course, 'courseware_summary': courseware_summary, 'grade_summary': grade_summary, 'staff_access': staff_access, 'student': student, } context.update() return render_to_response('courseware/progress.html', context)
def get_progress_summary(self): """ Return progress summary structure for current user and course. Returns - courseware_summary is a summary of all sections with problems in the course. It is organized as an array of chapters, each containing an array of sections, each containing an array of scores. This contains information for graded and ungraded problems, and is good for displaying a course summary with due dates, etc. """ model_data_cache = ModelDataCache.cache_for_descriptor_descendents( self.course.id, self.student_user, self.course ) fake_request = self.factory.get(reverse("progress", kwargs={"course_id": self.course.id})) progress_summary = grades.progress_summary(self.student_user, fake_request, self.course, model_data_cache) return progress_summary
def get_grade_summary(self): """ calls grades.grade for current user and course. the keywords for the returned object are - grade : A final letter grade. - percent : The final percent for the class (rounded up). - section_breakdown : A breakdown of each section that makes up the grade. (For display) - grade_breakdown : A breakdown of the major components that make up the final grade. (For display) """ model_data_cache = ModelDataCache.cache_for_descriptor_descendents( self.course.id, self.student_user, self.course ) fake_request = self.factory.get(reverse("progress", kwargs={"course_id": self.course.id})) return grades.grade(self.student_user, fake_request, self.course, model_data_cache)
def get_grade_summary(self): """ calls grades.grade for current user and course. the keywords for the returned object are - grade : A final letter grade. - percent : The final percent for the class (rounded up). - section_breakdown : A breakdown of each section that makes up the grade. (For display) - grade_breakdown : A breakdown of the major components that make up the final grade. (For display) """ model_data_cache = ModelDataCache.cache_for_descriptor_descendents( self.course.id, self.student_user, self.course) fake_request = self.factory.get( reverse('progress', kwargs={'course_id': self.course.id})) return grades.grade(self.student_user, fake_request, self.course, model_data_cache)
def get_progress_summary(self): """ Return progress summary structure for current user and course. Returns - courseware_summary is a summary of all sections with problems in the course. It is organized as an array of chapters, each containing an array of sections, each containing an array of scores. This contains information for graded and ungraded problems, and is good for displaying a course summary with due dates, etc. """ model_data_cache = ModelDataCache.cache_for_descriptor_descendents( self.course.id, self.student_user, self.course) fake_request = self.factory.get( reverse('progress', kwargs={'course_id': self.course.id})) progress_summary = grades.progress_summary(self.student_user, fake_request, self.course, model_data_cache) return progress_summary
def find_target_student_module(request, user_id, course_id, mod_id): """ Retrieve target StudentModule """ user = User.objects.get(id=user_id) model_data_cache = ModelDataCache.cache_for_descriptor_descendents( course_id, user, modulestore().get_instance(course_id, mod_id), depth=0, select_for_update=True) instance = get_module(user, request, mod_id, model_data_cache, course_id, grade_bucket_type='xqueue') if instance is None: msg = "No module {0} for user {1}--access denied?".format(mod_id, user) log.debug(msg) raise Http404 return instance
def index(request, course_id, chapter=None, section=None, position=None): """ Displays courseware accordion and associated content. If course, chapter, and section are all specified, renders the page, or returns an error if they are invalid. If section is not specified, displays the accordion opened to the right chapter. If neither chapter or section are specified, redirects to user's most recent chapter, or the first chapter if this is the user's first visit. Arguments: - request : HTTP request - course_id : course id (str: ORG/course/URL_NAME) - chapter : chapter url_name (str) - section : section url_name (str) - position : position in module, eg of <sequential> module (str) Returns: - HTTPresponse """ user = User.objects.prefetch_related("groups").get(id=request.user.id) request.user = user # keep just one instance of User course = get_course_with_access(user, course_id, 'load', depth=2) staff_access = has_access(user, course, 'staff') registered = registered_for_course(course, user) if not registered: # TODO (vshnayder): do course instructors need to be registered to see course? log.debug('User %s tried to view course %s but is not enrolled' % (user, course.location.url())) return redirect(reverse('about_course', args=[course.id])) masq = setup_masquerade(request, staff_access) try: model_data_cache = ModelDataCache.cache_for_descriptor_descendents( course.id, user, course, depth=2) course_module = get_module_for_descriptor(user, request, course, model_data_cache, course.id) if course_module is None: log.warning( 'If you see this, something went wrong: if we got this' ' far, should have gotten a course module for this user') return redirect(reverse('about_course', args=[course.id])) if chapter is None: return redirect_to_course_position(course_module) context = { 'csrf': csrf(request)['csrf_token'], 'accordion': render_accordion(request, course, chapter, section, model_data_cache), 'COURSE_TITLE': course.display_name_with_default, 'course': course, 'init': '', 'content': '', 'staff_access': staff_access, 'masquerade': masq, 'xqa_server': settings.MITX_FEATURES.get( 'USE_XQA_SERVER', 'http://*****:*****@content-qa.mitx.mit.edu/xqa') } # Only show the chat if it's enabled by the course and in the # settings. show_chat = course.show_chat and settings.MITX_FEATURES['ENABLE_CHAT'] if show_chat: context['chat'] = chat_settings(course, user) # If we couldn't load the chat settings, then don't show # the widget in the courseware. if context['chat'] is None: show_chat = False context['show_chat'] = show_chat chapter_descriptor = course.get_child_by( lambda m: m.url_name == chapter) if chapter_descriptor is not None: save_child_position(course_module, chapter) else: raise Http404( 'No chapter descriptor found with name {}'.format(chapter)) chapter_module = course_module.get_child_by( lambda m: m.url_name == chapter) if chapter_module is None: # User may be trying to access a chapter that isn't live yet if masq == 'student': # if staff is masquerading as student be kinder, don't 404 log.debug('staff masq as student: no chapter %s' % chapter) return redirect(reverse('courseware', args=[course.id])) raise Http404 if section is not None: section_descriptor = chapter_descriptor.get_child_by( lambda m: m.url_name == section) if section_descriptor is None: # Specifically asked-for section doesn't exist if masq == 'student': # if staff is masquerading as student be kinder, don't 404 log.debug('staff masq as student: no section %s' % section) return redirect(reverse('courseware', args=[course.id])) raise Http404 # cdodge: this looks silly, but let's refetch the section_descriptor with depth=None # which will prefetch the children more efficiently than doing a recursive load section_descriptor = modulestore().get_instance( course.id, section_descriptor.location, depth=None) # Load all descendants of the section, because we're going to display its # html, which in general will need all of its children section_model_data_cache = ModelDataCache.cache_for_descriptor_descendents( course_id, user, section_descriptor, depth=None) section_module = get_module(request.user, request, section_descriptor.location, section_model_data_cache, course_id, position, depth=None) if section_module is None: # User may be trying to be clever and access something # they don't have access to. raise Http404 # Save where we are in the chapter save_child_position(chapter_module, section) # check here if this section *is* a timed module. if section_module.category == 'timelimit': timer_context = update_timelimit_module( user, course_id, student_module_cache, section_descriptor, section_module) if 'timer_expiration_duration' in timer_context: context.update(timer_context) else: # if there is no expiration defined, then we know the timer has expired: return HttpResponseRedirect( timer_context['time_expired_redirect_url']) else: # check here if this page is within a course that has an active timed module running. If so, then # add in the appropriate timer information to the rendering context: context.update( check_for_active_timelimit_module(request, course_id, course)) context['content'] = section_module.runtime.render( section_module, None, 'student_view').content else: # section is none, so display a message prev_section = get_current_child(chapter_module) if prev_section is None: # Something went wrong -- perhaps this chapter has no sections visible to the user raise Http404 prev_section_url = reverse('courseware_section', kwargs={ 'course_id': course_id, 'chapter': chapter_descriptor.url_name, 'section': prev_section.url_name }) context['content'] = render_to_string( 'courseware/welcome-back.html', { 'course': course, 'chapter_module': chapter_module, 'prev_section': prev_section, 'prev_section_url': prev_section_url }) result = render_to_response('courseware/courseware.html', context) except Exception as e: if isinstance(e, Http404): # let it propagate raise # In production, don't want to let a 500 out for any reason if settings.DEBUG: raise else: log.exception("Error in index view: user={user}, course={course}," " chapter={chapter} section={section}" "position={position}".format(user=user, course=course, chapter=chapter, section=section, position=position)) try: result = render_to_response('courseware/courseware-error.html', { 'staff_access': staff_access, 'course': course }) except: # Let the exception propagate, relying on global config to at # at least return a nice error message log.exception("Error while rendering courseware-error page") raise return result
def test_center_login(request): ''' Log in students taking exams via Pearson Takes a POST request that contains the following keys: - code - a security code provided by Pearson - clientCandidateID - registrationID - exitURL - the url that we redirect to once we're done - vueExamSeriesCode - a code that indicates the exam that we're using ''' # errors are returned by navigating to the error_url, adding a query parameter named "code" # which contains the error code describing the exceptional condition. def makeErrorURL(error_url, error_code): log.error("generating error URL with error code {}".format(error_code)) return "{}?code={}".format(error_url, error_code) # get provided error URL, which will be used as a known prefix for returning error messages to the # Pearson shell. error_url = request.POST.get("errorURL") # TODO: check that the parameters have not been tampered with, by comparing the code provided by Pearson # with the code we calculate for the same parameters. if 'code' not in request.POST: return HttpResponseRedirect(makeErrorURL(error_url, "missingSecurityCode")) code = request.POST.get("code") # calculate SHA for query string # TODO: figure out how to get the original query string, so we can hash it and compare. if 'clientCandidateID' not in request.POST: return HttpResponseRedirect(makeErrorURL(error_url, "missingClientCandidateID")) client_candidate_id = request.POST.get("clientCandidateID") # TODO: check remaining parameters, and maybe at least log if they're not matching # expected values.... # registration_id = request.POST.get("registrationID") # exit_url = request.POST.get("exitURL") # find testcenter_user that matches the provided ID: try: testcenteruser = TestCenterUser.objects.get(client_candidate_id=client_candidate_id) except TestCenterUser.DoesNotExist: log.error("not able to find demographics for cand ID {}".format(client_candidate_id)) return HttpResponseRedirect(makeErrorURL(error_url, "invalidClientCandidateID")) # find testcenter_registration that matches the provided exam code: # Note that we could rely in future on either the registrationId or the exam code, # or possibly both. But for now we know what to do with an ExamSeriesCode, # while we currently have no record of RegistrationID values at all. if 'vueExamSeriesCode' not in request.POST: # we are not allowed to make up a new error code, according to Pearson, # so instead of "missingExamSeriesCode", we use a valid one that is # inaccurate but at least distinct. (Sigh.) log.error("missing exam series code for cand ID {}".format(client_candidate_id)) return HttpResponseRedirect(makeErrorURL(error_url, "missingPartnerID")) exam_series_code = request.POST.get('vueExamSeriesCode') registrations = TestCenterRegistration.objects.filter(testcenter_user=testcenteruser, exam_series_code=exam_series_code) if not registrations: log.error("not able to find exam registration for exam {} and cand ID {}".format(exam_series_code, client_candidate_id)) return HttpResponseRedirect(makeErrorURL(error_url, "noTestsAssigned")) # TODO: figure out what to do if there are more than one registrations.... # for now, just take the first... registration = registrations[0] course_id = registration.course_id course = course_from_id(course_id) # assume it will be found.... if not course: log.error("not able to find course from ID {} for cand ID {}".format(course_id, client_candidate_id)) return HttpResponseRedirect(makeErrorURL(error_url, "incorrectCandidateTests")) exam = course.get_test_center_exam(exam_series_code) if not exam: log.error("not able to find exam {} for course ID {} and cand ID {}".format(exam_series_code, course_id, client_candidate_id)) return HttpResponseRedirect(makeErrorURL(error_url, "incorrectCandidateTests")) location = exam.exam_url log.info("proceeding with test of cand {} on exam {} for course {}: URL = {}".format(client_candidate_id, exam_series_code, course_id, location)) # check if the test has already been taken timelimit_descriptor = modulestore().get_instance(course_id, Location(location)) if not timelimit_descriptor: log.error("cand {} on exam {} for course {}: descriptor not found for location {}".format(client_candidate_id, exam_series_code, course_id, location)) return HttpResponseRedirect(makeErrorURL(error_url, "missingClientProgram")) timelimit_module_cache = ModelDataCache.cache_for_descriptor_descendents(course_id, testcenteruser.user, timelimit_descriptor, depth=None) timelimit_module = get_module_for_descriptor(request.user, request, timelimit_descriptor, timelimit_module_cache, course_id, position=None) if not timelimit_module.category == 'timelimit': log.error("cand {} on exam {} for course {}: non-timelimit module at location {}".format(client_candidate_id, exam_series_code, course_id, location)) return HttpResponseRedirect(makeErrorURL(error_url, "missingClientProgram")) if timelimit_module and timelimit_module.has_ended: log.warning("cand {} on exam {} for course {}: test already over at {}".format(client_candidate_id, exam_series_code, course_id, timelimit_module.ending_at)) return HttpResponseRedirect(makeErrorURL(error_url, "allTestsTaken")) # check if we need to provide an accommodation: time_accommodation_mapping = {'ET12ET': 'ADDHALFTIME', 'ET30MN': 'ADD30MIN', 'ETDBTM': 'ADDDOUBLE', } time_accommodation_code = None for code in registration.get_accommodation_codes(): if code in time_accommodation_mapping: time_accommodation_code = time_accommodation_mapping[code] if time_accommodation_code: timelimit_module.accommodation_code = time_accommodation_code log.info("cand {} on exam {} for course {}: receiving accommodation {}".format(client_candidate_id, exam_series_code, course_id, time_accommodation_code)) # UGLY HACK!!! # Login assumes that authentication has occurred, and that there is a # backend annotation on the user object, indicating which backend # against which the user was authenticated. We're authenticating here # against the registration entry, and assuming that the request given # this information is correct, we allow the user to be logged in # without a password. This could all be formalized in a backend object # that does the above checking. # TODO: (brian) create a backend class to do this. # testcenteruser.user.backend = "%s.%s" % (backend.__module__, backend.__class__.__name__) testcenteruser.user.backend = "%s.%s" % ("TestcenterAuthenticationModule", "TestcenterAuthenticationClass") login(request, testcenteruser.user) # And start the test: return jump_to(request, course_id, location)
def index(request, course_id, chapter=None, section=None, position=None): """ Displays courseware accordion and associated content. If course, chapter, and section are all specified, renders the page, or returns an error if they are invalid. If section is not specified, displays the accordion opened to the right chapter. If neither chapter or section are specified, redirects to user's most recent chapter, or the first chapter if this is the user's first visit. Arguments: - request : HTTP request - course_id : course id (str: ORG/course/URL_NAME) - chapter : chapter url_name (str) - section : section url_name (str) - position : position in module, eg of <sequential> module (str) Returns: - HTTPresponse """ user = User.objects.prefetch_related("groups").get(id=request.user.id) request.user = user # keep just one instance of User course = get_course_with_access(user, course_id, "load", depth=2) staff_access = has_access(user, course, "staff") registered = registered_for_course(course, user) if not registered: # TODO (vshnayder): do course instructors need to be registered to see course? log.debug("User %s tried to view course %s but is not enrolled" % (user, course.location.url())) return redirect(reverse("about_course", args=[course.id])) masq = setup_masquerade(request, staff_access) try: model_data_cache = ModelDataCache.cache_for_descriptor_descendents(course.id, user, course, depth=2) course_module = get_module_for_descriptor(user, request, course, model_data_cache, course.id) if course_module is None: log.warning( "If you see this, something went wrong: if we got this" " far, should have gotten a course module for this user" ) return redirect(reverse("about_course", args=[course.id])) if chapter is None: return redirect_to_course_position(course_module) # check course constraints courses = modulestore().get_items(["i4x", None, None, "course", None]) def course_filter(course): return ( has_access(user, course, "see_exists") # TODO remove this condition when templates purged from db and course.location.course != "templates" and course.location.org != "" and course.location.course != "" and course.location.name != "" ) courses = filter(course_filter, courses) courses_by_id = dict((course.location.url(), course) for course in courses) if not is_item_unlocked(course.unlock_term, courses_by_id, lambda course: grade(user, request, course)): raise Http404 context = { "csrf": csrf(request)["csrf_token"], "accordion": render_accordion(request, course, chapter, section, model_data_cache), "COURSE_TITLE": course.display_name_with_default, "course": course, "init": "", "content": "", "staff_access": staff_access, "masquerade": masq, "xqa_server": settings.MITX_FEATURES.get("USE_XQA_SERVER", "http://*****:*****@content-qa.mitx.mit.edu/xqa"), } # Only show the chat if it's enabled by the course and in the # settings. show_chat = course.show_chat and settings.MITX_FEATURES["ENABLE_CHAT"] if show_chat: context["chat"] = chat_settings(course, user) # If we couldn't load the chat settings, then don't show # the widget in the courseware. if context["chat"] is None: show_chat = False context["show_chat"] = show_chat chapter_descriptor = course.get_child_by(lambda m: m.url_name == chapter) if chapter_descriptor is not None: save_child_position(course_module, chapter) else: raise Http404("No chapter descriptor found with name {}".format(chapter)) chapter_module = course_module.get_child_by(lambda m: m.url_name == chapter) if chapter_module is None: # User may be trying to access a chapter that isn't live yet if masq == "student": # if staff is masquerading as student be kinder, don't 404 log.debug("staff masq as student: no chapter %s" % chapter) return redirect(reverse("courseware", args=[course.id])) raise Http404 if section is not None: section_descriptor = chapter_descriptor.get_child_by(lambda m: m.url_name == section) if section_descriptor is None: # Specifically asked-for section doesn't exist if masq == "student": # if staff is masquerading as student be kinder, don't 404 log.debug("staff masq as student: no section %s" % section) return redirect(reverse("courseware", args=[course.id])) raise Http404 # cdodge: this looks silly, but let's refetch the section_descriptor with depth=None # which will prefetch the children more efficiently than doing a recursive load section_descriptor = modulestore().get_instance(course.id, section_descriptor.location, depth=None) # Load all descendants of the section, because we're going to display its # html, which in general will need all of its children section_model_data_cache = ModelDataCache.cache_for_descriptor_descendents( course_id, user, section_descriptor, depth=None ) section_module = get_module( request.user, request, section_descriptor.location, section_model_data_cache, course_id, position, depth=None, ) if section_module is None: # User may be trying to be clever and access something # they don't have access to. raise Http404 model_data_cache_for_check = ModelDataCache.cache_for_descriptor_descendents( course_id, user, course, depth=None ) courseware_summary = grades.progress_summary(user, request, course, model_data_cache_for_check) is_section_unlocked = grades.return_section_by_id(section_module.url_name, courseware_summary)["unlocked"] # Save where we are in the chapter save_child_position(chapter_module, section) # check here if this section *is* a timed module. if section_module.category == "timelimit": timer_context = update_timelimit_module( user, course_id, student_module_cache, section_descriptor, section_module ) if "timer_expiration_duration" in timer_context: context.update(timer_context) else: # if there is no expiration defined, then we know the timer has expired: return HttpResponseRedirect(timer_context["time_expired_redirect_url"]) else: # check here if this page is within a course that has an active timed module running. If so, then # add in the appropriate timer information to the rendering context: context.update(check_for_active_timelimit_module(request, course_id, course)) # context['content'] = section_module.runtime.render(section_module, None, 'student_view').content if not is_section_unlocked: context["content"] = u"Раздел вам пока не доступен" else: context["content"] = section_module.runtime.render(section_module, None, "student_view").content # context['content'] = section_module.get_html() else: # section is none, so display a message prev_section = get_current_child(chapter_module) if prev_section is None: # Something went wrong -- perhaps this chapter has no sections visible to the user raise Http404 prev_section_url = reverse( "courseware_section", kwargs={ "course_id": course_id, "chapter": chapter_descriptor.url_name, "section": prev_section.url_name, }, ) context["content"] = render_to_string( "courseware/welcome-back.html", { "course": course, "chapter_module": chapter_module, "prev_section": prev_section, "prev_section_url": prev_section_url, }, ) result = render_to_response("courseware/courseware.html", context) except Exception as e: if isinstance(e, Http404): # let it propagate raise # In production, don't want to let a 500 out for any reason if settings.DEBUG: raise else: log.exception( "Error in index view: user={user}, course={course}," " chapter={chapter} section={section}" "position={position}".format( user=user, course=course, chapter=chapter, section=section, position=position ) ) try: result = render_to_response( "courseware/courseware-error.html", {"staff_access": staff_access, "course": course} ) except: # Let the exception propagate, relying on global config to at # at least return a nice error message log.exception("Error while rendering courseware-error page") raise return result
def index(request, course_id, chapter=None, section=None, position=None): """ Displays courseware accordion and associated content. If course, chapter, and section are all specified, renders the page, or returns an error if they are invalid. If section is not specified, displays the accordion opened to the right chapter. If neither chapter or section are specified, redirects to user's most recent chapter, or the first chapter if this is the user's first visit. Arguments: - request : HTTP request - course_id : course id (str: ORG/course/URL_NAME) - chapter : chapter url_name (str) - section : section url_name (str) - position : position in module, eg of <sequential> module (str) Returns: - HTTPresponse """ user = User.objects.prefetch_related("groups").get(id=request.user.id) request.user = user # keep just one instance of User course = get_course_with_access(user, course_id, 'load', depth=2) staff_access = has_access(user, course, 'staff') registered = registered_for_course(course, user) if not registered: # TODO (vshnayder): do course instructors need to be registered to see course? log.debug('User %s tried to view course %s but is not enrolled' % (user, course.location.url())) return redirect(reverse('about_course', args=[course.id])) masq = setup_masquerade(request, staff_access) try: model_data_cache = ModelDataCache.cache_for_descriptor_descendents( course.id, user, course, depth=2) course_module = get_module_for_descriptor(user, request, course, model_data_cache, course.id) if course_module is None: log.warning('If you see this, something went wrong: if we got this' ' far, should have gotten a course module for this user') return redirect(reverse('about_course', args=[course.id])) if chapter is None: return redirect_to_course_position(course_module) context = { 'csrf': csrf(request)['csrf_token'], 'accordion': render_accordion(request, course, chapter, section, model_data_cache), 'COURSE_TITLE': course.display_name_with_default, 'course': course, 'init': '', 'content': '', 'staff_access': staff_access, 'masquerade': masq, 'xqa_server': settings.MITX_FEATURES.get('USE_XQA_SERVER', 'http://*****:*****@content-qa.mitx.mit.edu/xqa') } chapter_descriptor = course.get_child_by(lambda m: m.url_name == chapter) if chapter_descriptor is not None: save_child_position(course_module, chapter) else: raise Http404('No chapter descriptor found with name {}'.format(chapter)) chapter_module = course_module.get_child_by(lambda m: m.url_name == chapter) if chapter_module is None: # User may be trying to access a chapter that isn't live yet if masq=='student': # if staff is masquerading as student be kinder, don't 404 log.debug('staff masq as student: no chapter %s' % chapter) return redirect(reverse('courseware', args=[course.id])) raise Http404 if section is not None: section_descriptor = chapter_descriptor.get_child_by(lambda m: m.url_name == section) if section_descriptor is None: # Specifically asked-for section doesn't exist if masq=='student': # if staff is masquerading as student be kinder, don't 404 log.debug('staff masq as student: no section %s' % section) return redirect(reverse('courseware', args=[course.id])) raise Http404 # cdodge: this looks silly, but let's refetch the section_descriptor with depth=None # which will prefetch the children more efficiently than doing a recursive load section_descriptor = modulestore().get_instance(course.id, section_descriptor.location, depth=None) # Load all descendants of the section, because we're going to display its # html, which in general will need all of its children section_model_data_cache = ModelDataCache.cache_for_descriptor_descendents( course_id, user, section_descriptor, depth=None) section_module = get_module(request.user, request, section_descriptor.location, section_model_data_cache, course_id, position, depth=None) if section_module is None: # User may be trying to be clever and access something # they don't have access to. raise Http404 # Save where we are in the chapter save_child_position(chapter_module, section) # check here if this section *is* a timed module. if section_module.category == 'timelimit': timer_context = update_timelimit_module(user, course_id, student_module_cache, section_descriptor, section_module) if 'timer_expiration_duration' in timer_context: context.update(timer_context) else: # if there is no expiration defined, then we know the timer has expired: return HttpResponseRedirect(timer_context['time_expired_redirect_url']) else: # check here if this page is within a course that has an active timed module running. If so, then # add in the appropriate timer information to the rendering context: context.update(check_for_active_timelimit_module(request, course_id, course)) context['content'] = section_module.get_html() else: # section is none, so display a message prev_section = get_current_child(chapter_module) if prev_section is None: # Something went wrong -- perhaps this chapter has no sections visible to the user raise Http404 prev_section_url = reverse('courseware_section', kwargs={'course_id': course_id, 'chapter': chapter_descriptor.url_name, 'section': prev_section.url_name}) context['content'] = render_to_string('courseware/welcome-back.html', {'course': course, 'chapter_module': chapter_module, 'prev_section': prev_section, 'prev_section_url': prev_section_url}) result = render_to_response('courseware/courseware.html', context) except Exception as e: if isinstance(e, Http404): # let it propagate raise # In production, don't want to let a 500 out for any reason if settings.DEBUG: raise else: log.exception("Error in index view: user={user}, course={course}," " chapter={chapter} section={section}" "position={position}".format( user=user, course=course, chapter=chapter, section=section, position=position )) try: result = render_to_response('courseware/courseware-error.html', {'staff_access': staff_access, 'course': course}) except: # Let the exception propagate, relying on global config to at # at least return a nice error message log.exception("Error while rendering courseware-error page") raise return result
def get_course_about_section(course, section_key): """ This returns the snippet of html to be rendered on the course about page, given the key for the section. Valid keys: - overview - title - university - number - short_description - description - key_dates (includes start, end, exams, etc) - video - course_staff_short - course_staff_extended - requirements - syllabus - textbook - faq - more_info - ocw_links """ # Many of these are stored as html files instead of some semantic # markup. This can change without effecting this interface when we find a # good format for defining so many snippets of text/html. # TODO: Remove number, instructors from this list if section_key in [ 'short_description', 'description', 'key_dates', 'video', 'course_staff_short', 'course_staff_extended', 'requirements', 'syllabus', 'textbook', 'faq', 'more_info', 'number', 'instructors', 'overview', 'effort', 'end_date', 'prerequisites', 'ocw_links' ]: try: request = get_request_for_thread() loc = course.location._replace(category='about', name=section_key) # Use an empty cache model_data_cache = ModelDataCache([], course.id, request.user) about_module = get_module( request.user, request, loc, model_data_cache, course.id, not_found_ok=True, wrap_xmodule_display=False, static_asset_path=course.lms.static_asset_path) html = '' if about_module is not None: html = about_module.runtime.render(about_module, None, 'student_view').content return html except ItemNotFoundError: log.warning("Missing about section {key} in course {url}".format( key=section_key, url=course.location.url())) return None elif section_key == "title": return course.display_name_with_default elif section_key == "university": return course.display_org_with_default elif section_key == "number": return course.display_number_with_default raise KeyError("Invalid about key " + str(section_key))
def test_center_login(request): ''' Log in students taking exams via Pearson Takes a POST request that contains the following keys: - code - a security code provided by Pearson - clientCandidateID - registrationID - exitURL - the url that we redirect to once we're done - vueExamSeriesCode - a code that indicates the exam that we're using ''' # errors are returned by navigating to the error_url, adding a query parameter named "code" # which contains the error code describing the exceptional condition. def makeErrorURL(error_url, error_code): log.error("generating error URL with error code {}".format(error_code)) return "{}?code={}".format(error_url, error_code) # get provided error URL, which will be used as a known prefix for returning error messages to the # Pearson shell. error_url = request.POST.get("errorURL") # TODO: check that the parameters have not been tampered with, by comparing the code provided by Pearson # with the code we calculate for the same parameters. if 'code' not in request.POST: return HttpResponseRedirect( makeErrorURL(error_url, "missingSecurityCode")) code = request.POST.get("code") # calculate SHA for query string # TODO: figure out how to get the original query string, so we can hash it and compare. if 'clientCandidateID' not in request.POST: return HttpResponseRedirect( makeErrorURL(error_url, "missingClientCandidateID")) client_candidate_id = request.POST.get("clientCandidateID") # TODO: check remaining parameters, and maybe at least log if they're not matching # expected values.... # registration_id = request.POST.get("registrationID") # exit_url = request.POST.get("exitURL") # find testcenter_user that matches the provided ID: try: testcenteruser = TestCenterUser.objects.get( client_candidate_id=client_candidate_id) except TestCenterUser.DoesNotExist: AUDIT_LOG.error("not able to find demographics for cand ID {}".format( client_candidate_id)) return HttpResponseRedirect( makeErrorURL(error_url, "invalidClientCandidateID")) AUDIT_LOG.info( "Attempting to log in test-center user '{}' for test of cand {}". format(testcenteruser.user.username, client_candidate_id)) # find testcenter_registration that matches the provided exam code: # Note that we could rely in future on either the registrationId or the exam code, # or possibly both. But for now we know what to do with an ExamSeriesCode, # while we currently have no record of RegistrationID values at all. if 'vueExamSeriesCode' not in request.POST: # we are not allowed to make up a new error code, according to Pearson, # so instead of "missingExamSeriesCode", we use a valid one that is # inaccurate but at least distinct. (Sigh.) AUDIT_LOG.error("missing exam series code for cand ID {}".format( client_candidate_id)) return HttpResponseRedirect(makeErrorURL(error_url, "missingPartnerID")) exam_series_code = request.POST.get('vueExamSeriesCode') registrations = TestCenterRegistration.objects.filter( testcenter_user=testcenteruser, exam_series_code=exam_series_code) if not registrations: AUDIT_LOG.error( "not able to find exam registration for exam {} and cand ID {}". format(exam_series_code, client_candidate_id)) return HttpResponseRedirect(makeErrorURL(error_url, "noTestsAssigned")) # TODO: figure out what to do if there are more than one registrations.... # for now, just take the first... registration = registrations[0] course_id = registration.course_id course = course_from_id(course_id) # assume it will be found.... if not course: AUDIT_LOG.error( "not able to find course from ID {} for cand ID {}".format( course_id, client_candidate_id)) return HttpResponseRedirect( makeErrorURL(error_url, "incorrectCandidateTests")) exam = course.get_test_center_exam(exam_series_code) if not exam: AUDIT_LOG.error( "not able to find exam {} for course ID {} and cand ID {}".format( exam_series_code, course_id, client_candidate_id)) return HttpResponseRedirect( makeErrorURL(error_url, "incorrectCandidateTests")) location = exam.exam_url log.info( "Proceeding with test of cand {} on exam {} for course {}: URL = {}". format(client_candidate_id, exam_series_code, course_id, location)) # check if the test has already been taken timelimit_descriptor = modulestore().get_instance(course_id, Location(location)) if not timelimit_descriptor: log.error( "cand {} on exam {} for course {}: descriptor not found for location {}" .format(client_candidate_id, exam_series_code, course_id, location)) return HttpResponseRedirect( makeErrorURL(error_url, "missingClientProgram")) timelimit_module_cache = ModelDataCache.cache_for_descriptor_descendents( course_id, testcenteruser.user, timelimit_descriptor, depth=None) timelimit_module = get_module_for_descriptor(request.user, request, timelimit_descriptor, timelimit_module_cache, course_id, position=None) if not timelimit_module.category == 'timelimit': log.error( "cand {} on exam {} for course {}: non-timelimit module at location {}" .format(client_candidate_id, exam_series_code, course_id, location)) return HttpResponseRedirect( makeErrorURL(error_url, "missingClientProgram")) if timelimit_module and timelimit_module.has_ended: AUDIT_LOG.warning( "cand {} on exam {} for course {}: test already over at {}".format( client_candidate_id, exam_series_code, course_id, timelimit_module.ending_at)) return HttpResponseRedirect(makeErrorURL(error_url, "allTestsTaken")) # check if we need to provide an accommodation: time_accommodation_mapping = { 'ET12ET': 'ADDHALFTIME', 'ET30MN': 'ADD30MIN', 'ETDBTM': 'ADDDOUBLE', } time_accommodation_code = None for code in registration.get_accommodation_codes(): if code in time_accommodation_mapping: time_accommodation_code = time_accommodation_mapping[code] if time_accommodation_code: timelimit_module.accommodation_code = time_accommodation_code AUDIT_LOG.info( "cand {} on exam {} for course {}: receiving accommodation {}". format(client_candidate_id, exam_series_code, course_id, time_accommodation_code)) # UGLY HACK!!! # Login assumes that authentication has occurred, and that there is a # backend annotation on the user object, indicating which backend # against which the user was authenticated. We're authenticating here # against the registration entry, and assuming that the request given # this information is correct, we allow the user to be logged in # without a password. This could all be formalized in a backend object # that does the above checking. # TODO: (brian) create a backend class to do this. # testcenteruser.user.backend = "%s.%s" % (backend.__module__, backend.__class__.__name__) testcenteruser.user.backend = "%s.%s" % ("TestcenterAuthenticationModule", "TestcenterAuthenticationClass") login(request, testcenteruser.user) AUDIT_LOG.info( "Logged in user '{}' for test of cand {} on exam {} for course {}: URL = {}" .format(testcenteruser.user.username, client_candidate_id, exam_series_code, course_id, location)) # And start the test: return jump_to(request, course_id, location)
def test_toc_toy_from_section(self): chapter = "Overview" chapter_url = "%s/%s/%s" % ("/courses", self.course_name, chapter) section = "Welcome" factory = RequestFactory() request = factory.get(chapter_url) model_data_cache = ModelDataCache.cache_for_descriptor_descendents( self.toy_course.id, self.portal_user, self.toy_course, depth=2 ) expected = [ { "active": True, "sections": [ { "url_name": "Toy_Videos", "display_name": u"Toy Videos", "graded": True, "format": u"Lecture Sequence", "due": None, "active": False, }, { "url_name": "Welcome", "display_name": u"Welcome", "graded": True, "format": "", "due": None, "active": True, }, { "url_name": "video_123456789012", "display_name": "Test Video", "graded": True, "format": "", "due": None, "active": False, }, { "url_name": "video_4f66f493ac8f", "display_name": "Video", "graded": True, "format": "", "due": None, "active": False, }, ], "url_name": "Overview", "display_name": u"Overview", }, { "active": False, "sections": [ { "url_name": "toyvideo", "display_name": "toyvideo", "graded": True, "format": "", "due": None, "active": False, } ], "url_name": "secret:magic", "display_name": "secret:magic", }, ] actual = render.toc_for_course(self.portal_user, request, self.toy_course, chapter, section, model_data_cache) for toc_section in expected: self.assertIn(toc_section, actual)
def modx_dispatch(request, dispatch, location, course_id): ''' Generic view for extensions. This is where AJAX calls go. Arguments: - request -- the django request. - dispatch -- the command string to pass through to the module's handle_ajax call (e.g. 'problem_reset'). If this string contains '?', only pass through the part before the first '?'. - location -- the module location. Used to look up the XModule instance - course_id -- defines the course context for this request. Raises PermissionDenied if the user is not logged in. Raises Http404 if the location and course_id do not identify a valid module, the module is not accessible by the user, or the module raises NotFoundError. If the module raises any other error, it will escape this function. ''' # ''' (fix emacs broken parsing) # Check parameters and fail fast if there's a problem if not Location.is_valid(location): raise Http404("Invalid location") if not request.user.is_authenticated(): raise PermissionDenied # Get the submitted data data = request.POST.copy() # Get and check submitted files files = request.FILES or {} error_msg = _check_files_limits(files) if error_msg: return HttpResponse(json.dumps({'success': error_msg})) for key in files: # Merge files into to data dictionary data[key] = files.getlist(key) try: descriptor = modulestore().get_instance(course_id, location) except ItemNotFoundError: log.warn( "Invalid location for course id {course_id}: {location}".format( course_id=course_id, location=location)) raise Http404 model_data_cache = ModelDataCache.cache_for_descriptor_descendents( course_id, request.user, descriptor) instance = get_module(request.user, request, location, model_data_cache, course_id, grade_bucket_type='ajax') if instance is None: # Either permissions just changed, or someone is trying to be clever # and load something they shouldn't have access to. log.debug("No module {0} for user {1}--access denied?".format( location, request.user)) raise Http404 # Let the module handle the AJAX try: ajax_return = instance.handle_ajax(dispatch, data) # If we can't find the module, respond with a 404 except NotFoundError: log.exception("Module indicating to user that request doesn't exist") raise Http404 # For XModule-specific errors, we respond with 400 except ProcessingError: log.warning("Module encountered an error while prcessing AJAX call", exc_info=True) return HttpResponseBadRequest() # If any other error occurred, re-raise it to trigger a 500 response except: log.exception("error processing ajax call") raise # Return whatever the module wanted to return to the client/caller return HttpResponse(ajax_return)
def modx_dispatch(request, dispatch, location, course_id): ''' Generic view for extensions. This is where AJAX calls go. Arguments: - request -- the django request. - dispatch -- the command string to pass through to the module's handle_ajax call (e.g. 'problem_reset'). If this string contains '?', only pass through the part before the first '?'. - location -- the module location. Used to look up the XModule instance - course_id -- defines the course context for this request. Raises PermissionDenied if the user is not logged in. Raises Http404 if the location and course_id do not identify a valid module, the module is not accessible by the user, or the module raises NotFoundError. If the module raises any other error, it will escape this function. ''' # ''' (fix emacs broken parsing) # Check parameters and fail fast if there's a problem if not Location.is_valid(location): raise Http404("Invalid location") if not request.user.is_authenticated(): raise PermissionDenied # Get the submitted data data = request.POST.copy() # Get and check submitted files files = request.FILES or {} error_msg = _check_files_limits(files) if error_msg: return HttpResponse(json.dumps({'success': error_msg})) data.update(files) # Merge files into data dictionary try: descriptor = modulestore().get_instance(course_id, location) except ItemNotFoundError: log.warn( "Invalid location for course id {course_id}: {location}".format( course_id=course_id, location=location ) ) raise Http404 model_data_cache = ModelDataCache.cache_for_descriptor_descendents( course_id, request.user, descriptor ) instance = get_module(request.user, request, location, model_data_cache, course_id, grade_bucket_type='ajax') if instance is None: # Either permissions just changed, or someone is trying to be clever # and load something they shouldn't have access to. log.debug("No module {0} for user {1}--access denied?".format(location, request.user)) raise Http404 # Let the module handle the AJAX try: ajax_return = instance.handle_ajax(dispatch, data) # If we can't find the module, respond with a 404 except NotFoundError: log.exception("Module indicating to user that request doesn't exist") raise Http404 # For XModule-specific errors, we respond with 400 except ProcessingError: log.warning("Module encountered an error while prcessing AJAX call", exc_info=True) return HttpResponseBadRequest() # If any other error occurred, re-raise it to trigger a 500 response except: log.exception("error processing ajax call") raise # Return whatever the module wanted to return to the client/caller return HttpResponse(ajax_return)