예제 #1
0
    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)
예제 #2
0
 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)
예제 #3
0
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)
예제 #5
0
    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)
예제 #6
0
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
예제 #7
0
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
예제 #8
0
    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)
예제 #9
0
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
예제 #10
0
 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)
예제 #11
0
 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)
예제 #12
0
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)
예제 #13
0
    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)
예제 #14
0
    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)
예제 #15
0
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("")
예제 #16
0
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)
예제 #17
0
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
예제 #18
0
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
예제 #19
0
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)
예제 #21
0
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
예제 #24
0
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)
예제 #27
0
    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)
예제 #28
0
    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
예제 #29
0
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
예제 #30
0
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
예제 #31
0
파일: views.py 프로젝트: 2bj/edx-platform
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)
예제 #32
0
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
예제 #33
0
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
예제 #34
0
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))
예제 #35
0
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)
예제 #37
0
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)
예제 #38
0
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)