Exemplo n.º 1
0
def save_scores(user, puzzle_scores):
    score_responses = []
    for score in puzzle_scores:
        log.debug("score: %s", score)
        # expected keys ScoreType, PuzzleID (int),
        # BestScore (energy), CurrentScore (Energy), ScoreVersion (int)

        puzzle_id = score['PuzzleID']
        best_score = score['BestScore']
        current_score = score['CurrentScore']
        score_version = score['ScoreVersion']

        # SetPlayerPuzzleScoreResponse object
        # Score entries are unique on user/unique_user_id/puzzle_id/score_version
        try:
            obj = Score.objects.get(user=user,
                                    unique_user_id=unique_id_for_user(user),
                                    puzzle_id=puzzle_id,
                                    score_version=score_version)
            obj.current_score = current_score
            obj.best_score = best_score

        except Score.DoesNotExist:
            obj = Score(user=user,
                        unique_user_id=unique_id_for_user(user),
                        puzzle_id=puzzle_id,
                        current_score=current_score,
                        best_score=best_score,
                        score_version=score_version)
        obj.save()

        score_responses.append({'PuzzleID': puzzle_id, 'Status': 'Success'})

    return {"OperationID": "SetPlayerPuzzleScores", "Value": score_responses}
Exemplo n.º 2
0
    def setUp(self):
        self.factory = RequestFactory()
        self.url = reverse('foldit_ops')

        pwd = 'abc'
        self.user = User.objects.create_user('testuser', '*****@*****.**', pwd)
        self.user2 = User.objects.create_user('testuser2', '*****@*****.**', pwd)
        self.unique_user_id = unique_id_for_user(self.user)
        self.unique_user_id2 = unique_id_for_user(self.user2)
        now = datetime.now(UTC)
        self.tomorrow = now + timedelta(days=1)
        self.yesterday = now - timedelta(days=1)

        UserProfile.objects.create(user=self.user)
        UserProfile.objects.create(user=self.user2)
Exemplo n.º 3
0
    def _is_embargoed_by_profile_country(self, user, course_id="", course_is_embargoed=False):
        """
        Check whether the user is embargoed based on the country code in the user's profile.

        Args:
            user (User): The user attempting to access courseware.

        Keyword Args:
            course_id (unicode): The course the user is trying to access.
            course_is_embargoed (boolean): Whether the course the user is accessing has been embargoed.

        Returns:
            A unicode message if the user is embargoed, otherwise `None`

        """
        cache_key = u'user.{user_id}.profile.country'.format(user_id=user.id)
        profile_country = cache.get(cache_key)
        if profile_country is None:
            profile = getattr(user, 'profile', None)
            if profile is not None and profile.country.code is not None:
                profile_country = profile.country.code.upper()
            else:
                profile_country = ""
            cache.set(cache_key, profile_country)

        if profile_country in self._embargoed_countries:
            return self.REASONS['profile_country'].format(
                user_id=unique_id_for_user(user),
                profile_country=profile_country,
                from_course=self._from_course_msg(course_id, course_is_embargoed)
            )
        else:
            return None
Exemplo n.º 4
0
def get_problem_list(request, course_id):
    """
    Get all the problems for the given course id

    Returns a json dict with the following keys:
        success: bool

        problem_list: a list containing json dicts with the following keys:
            each dict represents a different problem in the course

            location: the location of the problem

            problem_name: the name of the problem

            num_graded: the number of responses that have been graded

            num_pending: the number of responses that are sitting in the queue

            min_for_ml: the number of responses that need to be graded before
                the ml can be run

    """
    _check_access(request.user, course_id)
    try:
        response = staff_grading_service().get_problem_list(
            course_id, unique_id_for_user(request.user))
        return HttpResponse(response,
                            mimetype="application/json")
    except GradingServiceError:
        # This is a dev_facing_error
        log.exception("Error from staff grading service in open ended grading.  server url: {0}"
                      .format(staff_grading_service().url))
        # This is a staff_facing_error
        return HttpResponse(json.dumps({'success': False,
                                        'error': STAFF_ERROR_MESSAGE}))
Exemplo n.º 5
0
def save_complete(user, puzzles_complete):
    """
    Returned list of PuzzleIDs should be in sorted order (I don't think client
    cares, but tests do)
    """
    for complete in puzzles_complete:
        log.debug("Puzzle complete: %s", complete)
        puzzle_id = complete['PuzzleID']
        puzzle_set = complete['Set']
        puzzle_subset = complete['SubSet']

        # create if not there
        PuzzleComplete.objects.get_or_create(
            user=user,
            unique_user_id=unique_id_for_user(user),
            puzzle_id=puzzle_id,
            puzzle_set=puzzle_set,
            puzzle_subset=puzzle_subset)

    # List of all puzzle ids of intro-level puzzles completed ever, including on this
    # request
    # TODO: this is just in this request...

    complete_responses = list(pc.puzzle_id
                              for pc in PuzzleComplete.objects.filter(user=user))

    return {"OperationID": "SetPuzzlesComplete", "Value": complete_responses}
def staff_grading_notifications(course, user):
    staff_gs = StaffGradingService(settings.OPEN_ENDED_GRADING_INTERFACE)
    pending_grading = False
    img_path = ""
    course_id = course.id
    student_id = unique_id_for_user(user)
    notification_type = "staff"

    success, notification_dict = get_value_from_cache(student_id, course_id, notification_type)
    if success:
        return notification_dict

    try:
        notifications = json.loads(staff_gs.get_notifications(course_id))
        if notifications["success"]:
            if notifications["staff_needs_to_grade"]:
                pending_grading = True
    except:
        # Non catastrophic error, so no real action
        notifications = {}
        # This is a dev_facing_error
        log.info(
            "Problem with getting notifications from staff grading service for course {0} user {1}.".format(
                course_id, student_id
            )
        )

    if pending_grading:
        img_path = "/static/images/grading_notification.png"

    notification_dict = {"pending_grading": pending_grading, "img_path": img_path, "response": notifications}

    set_value_in_cache(student_id, course_id, notification_type, notification_dict)

    return notification_dict
Exemplo n.º 7
0
def staff_grading_notifications(course, user):
    staff_gs = StaffGradingService(settings.OPEN_ENDED_GRADING_INTERFACE)
    pending_grading = False
    img_path = ""
    course_id = course.id
    student_id = unique_id_for_user(user)
    notification_type = "staff"

    success, notification_dict = get_value_from_cache(student_id, course_id, notification_type)
    if success:
        return notification_dict

    try:
        notifications = json.loads(staff_gs.get_notifications(course_id))
        if notifications['success']:
            if notifications['staff_needs_to_grade']:
                pending_grading = True
    except:
        #Non catastrophic error, so no real action
        notifications = {}
        #This is a dev_facing_error
        log.info(
            "Problem with getting notifications from staff grading service for course {0} user {1}.".format(course_id,
                                                                                                            student_id))

    if pending_grading:
        img_path = "/static/images/grading_notification.png"

    notification_dict = {'pending_grading': pending_grading, 'img_path': img_path, 'response': notifications}

    set_value_in_cache(student_id, course_id, notification_type, notification_dict)

    return notification_dict
Exemplo n.º 8
0
    def _is_embargoed_by_profile_country(self, user, course_id="", course_is_embargoed=False):
        """
        Check whether the user is embargoed based on the country code in the user's profile.

        Args:
            user (User): The user attempting to access courseware.

        Keyword Args:
            course_id (unicode): The course the user is trying to access.
            course_is_embargoed (boolean): Whether the course the user is accessing has been embargoed.

        Returns:
            A unicode message if the user is embargoed, otherwise `None`

        """
        cache_key = u'user.{user_id}.profile.country'.format(user_id=user.id)
        profile_country = cache.get(cache_key)
        if profile_country is None:
            profile = getattr(user, 'profile', None)
            if profile is not None:
                profile_country = profile.country.code.upper()
            else:
                profile_country = ""
            cache.set(cache_key, profile_country)

        if profile_country in self._embargoed_countries:
            return self.REASONS['profile_country'].format(
                user_id=unique_id_for_user(user),
                profile_country=profile_country,
                from_course=self._from_course_msg(course_id, course_is_embargoed)
            )
        else:
            return None
def peer_grading_notifications(course, user):
    peer_gs = peer_grading_service.PeerGradingService(settings.OPEN_ENDED_GRADING_INTERFACE, render_to_string)
    pending_grading = False
    img_path = ""
    course_id = course.id
    student_id = unique_id_for_user(user)
    notification_type = "peer"

    success, notification_dict = get_value_from_cache(student_id, course_id, notification_type)
    if success:
        return notification_dict

    try:
        notifications = json.loads(peer_gs.get_notifications(course_id, student_id))
        if notifications['success']:
            if notifications['student_needs_to_peer_grade']:
                pending_grading = True
    except:
        #Non catastrophic error, so no real action
        notifications = {}
        #This is a dev_facing_error
        log.info(
            "Problem with getting notifications from peer grading service for course {0} user {1}.".format(course_id,
                                                                                                           student_id))
    if pending_grading:
        img_path = "/static/images/grading_notification.png"

    notification_dict = {'pending_grading': pending_grading, 'img_path': img_path, 'response': notifications}

    set_value_in_cache(student_id, course_id, notification_type, notification_dict)

    return notification_dict
Exemplo n.º 10
0
def get_anon_ids(request, course_id):  # pylint: disable=W0613
    """
    Respond with 2-column CSV output of user-id, anonymized-user-id
    """
    # TODO: the User.objects query and CSV generation here could be
    # centralized into analytics. Currently analytics has similar functionality
    # but not quite what's needed.
    def csv_response(filename, header, rows):
        """Returns a CSV http response for the given header and rows (excel/utf-8)."""
        response = HttpResponse(mimetype='text/csv')
        response['Content-Disposition'] = 'attachment; filename={0}'.format(filename)
        writer = csv.writer(response, dialect='excel', quotechar='"', quoting=csv.QUOTE_ALL)
        # In practice, there should not be non-ascii data in this query,
        # but trying to do the right thing anyway.
        encoded = [unicode(s).encode('utf-8') for s in header]
        writer.writerow(encoded)
        for row in rows:
            encoded = [unicode(s).encode('utf-8') for s in row]
            writer.writerow(encoded)
        return response

    students = User.objects.filter(
        courseenrollment__course_id=course_id,
    ).order_by('id')
    header = ['User ID', 'Anonymized user ID']
    rows = [[s.id, unique_id_for_user(s)] for s in students]
    return csv_response(course_id.replace('/', '-') + '-anon-ids.csv', header, rows)
Exemplo n.º 11
0
def peer_grading_notifications(course, user):
    system = LmsModuleSystem(track_function=None, get_module=None, render_template=render_to_string, replace_urls=None)
    peer_gs = peer_grading_service.PeerGradingService(settings.OPEN_ENDED_GRADING_INTERFACE, system)
    pending_grading = False
    img_path = ""
    course_id = course.id
    student_id = unique_id_for_user(user)
    notification_type = "peer"

    success, notification_dict = get_value_from_cache(student_id, course_id, notification_type)
    if success:
        return notification_dict

    try:
        notifications = json.loads(peer_gs.get_notifications(course_id, student_id))
        if notifications["success"]:
            if notifications["student_needs_to_peer_grade"]:
                pending_grading = True
    except:
        # Non catastrophic error, so no real action
        notifications = {}
        # This is a dev_facing_error
        log.info(
            "Problem with getting notifications from peer grading service for course {0} user {1}.".format(
                course_id, student_id
            )
        )
    if pending_grading:
        img_path = "/static/images/grading_notification.png"

    notification_dict = {"pending_grading": pending_grading, "img_path": img_path, "response": notifications}

    set_value_in_cache(student_id, course_id, notification_type, notification_dict)

    return notification_dict
    def handle(self, *args, **options):
        if len(args) != 1:
            raise CommandError("Usage: unique_id_mapping %s" %
                               " ".join(("<%s>" % arg for arg in Command.args)))

        course_id = args[0]

        # Generate the output filename from the course ID.
        # Change slashes to dashes first, and then append .csv extension.
        output_filename = course_id.replace('/', '-') + ".csv"

        # Figure out which students are enrolled in the course
        students = User.objects.filter(courseenrollment__course_id=course_id)
        if len(students) == 0:
            self.stdout.write("No students enrolled in %s" % course_id)
            return

        # Write mapping to output file in CSV format with a simple header
        try:
            with open(output_filename, 'wb') as output_file:
                csv_writer = csv.writer(output_file)
                csv_writer.writerow(("User ID", "Anonymized user ID"))
                for student in students:
                    csv_writer.writerow((student.id, unique_id_for_user(student)))
        except IOError:
            raise CommandError("Error writing to file: %s" % output_filename)
Exemplo n.º 13
0
def get_blank_lti(request, course_id):  # pylint: disable=unused-argument
    """
    Respond with CSV output

    - ID
    - email
    - grade (blank)
    - max_grade (blank)
    - comments (blank)
    """
    course_id = CourseKey.from_string(course_id)
    students = User.objects.filter(
        courseenrollment__course_id=course_id, ).order_by('id')
    header = [
        'ID',
        'Anonymized User ID',
        'email',
        'grade',
        'max_grade',
        'comments',
    ]
    encoded_header = [unicode(s).encode('utf-8') for s in header]
    rows = [[
        student.id,
        unique_id_for_user(student, save=False),
        student.email,
        '',
        '',
        '',
    ] for student in students]
    csv_filename = "{course_id}-blank-grade-submission.csv".format(
        course_id=unicode(course_id).replace('/', '-'), )
    return create_csv_response(csv_filename, encoded_header, rows)
Exemplo n.º 14
0
def get_anon_ids(request, course_id):  # pylint: disable=W0613
    """
    Respond with 2-column CSV output of user-id, anonymized-user-id
    """
    # TODO: the User.objects query and CSV generation here could be
    # centralized into analytics. Currently analytics has similar functionality
    # but not quite what's needed.
    course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id)
    def csv_response(filename, header, rows):
        """Returns a CSV http response for the given header and rows (excel/utf-8)."""
        response = HttpResponse(mimetype='text/csv')
        response['Content-Disposition'] = 'attachment; filename={0}'.format(filename)
        writer = csv.writer(response, dialect='excel', quotechar='"', quoting=csv.QUOTE_ALL)
        # In practice, there should not be non-ascii data in this query,
        # but trying to do the right thing anyway.
        encoded = [unicode(s).encode('utf-8') for s in header]
        writer.writerow(encoded)
        for row in rows:
            encoded = [unicode(s).encode('utf-8') for s in row]
            writer.writerow(encoded)
        return response

    students = User.objects.filter(
        courseenrollment__course_id=course_id,
    ).order_by('id')
    header = ['User ID', 'Anonymized user ID', 'Course Specific Anonymized user ID']
    rows = [[s.id, unique_id_for_user(s), anonymous_id_for_user(s, course_id)] for s in students]
    return csv_response(course_id.to_deprecated_string().replace('/', '-') + '-anon-ids.csv', header, rows)
Exemplo n.º 15
0
    def handle(self, *args, **options):
        if len(args) != 1:
            raise CommandError("Usage: unique_id_mapping %s" % " ".join(
                ("<%s>" % arg for arg in Command.args)))

        course_id = args[0]

        # Generate the output filename from the course ID.
        # Change slashes to dashes first, and then append .csv extension.
        output_filename = course_id.replace('/', '-') + ".csv"

        # Figure out which students are enrolled in the course
        students = User.objects.filter(courseenrollment__course_id=course_id)
        if len(students) == 0:
            self.stdout.write("No students enrolled in %s" % course_id)
            return

        # Write mapping to output file in CSV format with a simple header
        try:
            with open(output_filename, 'wb') as output_file:
                csv_writer = csv.writer(output_file)
                csv_writer.writerow(("User ID", "Anonymized user ID"))
                for student in students:
                    csv_writer.writerow(
                        (student.id, unique_id_for_user(student)))
        except IOError:
            raise CommandError("Error writing to file: %s" % output_filename)
Exemplo n.º 16
0
def get_anon_ids(request, course_id):  # pylint: disable=W0613
    """
    Respond with 2-column CSV output of user-id, anonymized-user-id
    """
    # TODO: the User.objects query and CSV generation here could be
    # centralized into analytics. Currently analytics has similar functionality
    # but not quite what's needed.
    def csv_response(filename, header, rows):
        """Returns a CSV http response for the given header and rows (excel/utf-8)."""
        response = HttpResponse(mimetype="text/csv")
        response["Content-Disposition"] = "attachment; filename={0}".format(filename)
        writer = csv.writer(response, dialect="excel", quotechar='"', quoting=csv.QUOTE_ALL)
        # In practice, there should not be non-ascii data in this query,
        # but trying to do the right thing anyway.
        encoded = [unicode(s).encode("utf-8") for s in header]
        writer.writerow(encoded)
        for row in rows:
            encoded = [unicode(s).encode("utf-8") for s in row]
            writer.writerow(encoded)
        return response

    students = User.objects.filter(courseenrollment__course_id=course_id).order_by("id")
    header = ["User ID", "Anonymized user ID"]
    rows = [[s.id, unique_id_for_user(s)] for s in students]
    return csv_response(course_id.replace("/", "-") + "-anon-ids.csv", header, rows)
def get_next(request, course_id):
    """
    Get the next thing to grade for course_id and with the location specified
    in the request.

    Returns a json dict with the following keys:

    'success': bool

    'submission_id': a unique identifier for the submission, to be passed back
                     with the grade.

    'submission': the submission, rendered as read-only html for grading

    'rubric': the rubric, also rendered as html.

    'message': if there was no submission available, but nothing went wrong,
            there will be a message field.

    'error': if success is False, will have an error message with more info.
    """
    _check_access(request.user, course_id)

    required = set(["location"])
    if request.method != "POST":
        raise Http404
    actual = set(request.POST.keys())
    missing = required - actual
    if len(missing) > 0:
        return _err_response("Missing required keys {0}".format(", ".join(missing)))
    grader_id = unique_id_for_user(request.user)
    p = request.POST
    location = p["location"]

    return HttpResponse(_get_next(course_id, grader_id, location), mimetype="application/json")
Exemplo n.º 18
0
def flagged_problem_list(request, course_id):
    '''
    Show a student problem list
    '''
    course = get_course_with_access(request.user, course_id, 'staff')
    student_id = unique_id_for_user(request.user)

    # call problem list service
    success = False
    error_text = ""
    problem_list = []
    base_course_url = reverse('courses')

    # Make a service that can query edX ORA.
    controller_qs = create_controller_query_service()
    try:
        problem_list_json = controller_qs.get_flagged_problem_list(course_id)
        problem_list_dict = json.loads(problem_list_json)
        success = problem_list_dict['success']
        if 'error' in problem_list_dict:
            error_text = problem_list_dict['error']
            problem_list = []
        else:
            problem_list = problem_list_dict['flagged_submissions']

    except GradingServiceError:
        #This is a staff_facing_error
        error_text = STAFF_ERROR_MESSAGE
        #This is a dev_facing_error
        log.error(
            "Could not get flagged problem list from external grading service for open ended."
        )
        success = False
    # catch error if if the json loads fails
    except ValueError:
        #This is a staff_facing_error
        error_text = STAFF_ERROR_MESSAGE
        #This is a dev_facing_error
        log.error(
            "Could not parse problem list from external grading service response."
        )
        success = False

    ajax_url = _reverse_with_slash('open_ended_flagged_problems', course_id)
    context = {
        'course': course,
        'course_id': course_id,
        'ajax_url': ajax_url,
        'success': success,
        'problem_list': problem_list,
        'error_text': error_text,
        # Checked above
        'staff_access': True,
    }
    return render_to_response(
        'open_ended_problems/open_ended_flagged_problems.html', context)
Exemplo n.º 19
0
    def test_process_survey_link(self):
        username = "******"
        user = Mock(username=username)
        id = unique_id_for_user(user)
        link1 = "http://www.mysurvey.com"
        self.assertEqual(process_survey_link(link1, user), link1)

        link2 = "http://www.mysurvey.com?unique={UNIQUE_ID}"
        link2_expected = "http://www.mysurvey.com?unique={UNIQUE_ID}".format(UNIQUE_ID=id)
        self.assertEqual(process_survey_link(link2, user), link2_expected)
Exemplo n.º 20
0
    def test_process_survey_link(self):
        username = "******"
        user = Mock(username=username)
        user_id = unique_id_for_user(user)
        link1 = "http://www.mysurvey.com"
        self.assertEqual(process_survey_link(link1, user), link1)

        link2 = "http://www.mysurvey.com?unique={UNIQUE_ID}"
        link2_expected = "http://www.mysurvey.com?unique={UNIQUE_ID}".format(UNIQUE_ID=user_id)
        self.assertEqual(process_survey_link(link2, user), link2_expected)
Exemplo n.º 21
0
def get_problem_list(request, course_id):
    """
    Get all the problems for the given course id

    Returns a json dict with the following keys:
        success: bool

        problem_list: a list containing json dicts with the following keys:
            each dict represents a different problem in the course

            location: the location of the problem

            problem_name: the name of the problem

            num_graded: the number of responses that have been graded

            num_pending: the number of responses that are sitting in the queue

            min_for_ml: the number of responses that need to be graded before
                the ml can be run

    """
    _check_access(request.user, course_id)
    try:
        response = staff_grading_service().get_problem_list(
            course_id, unique_id_for_user(request.user))
        response = json.loads(response)
        problem_list = response['problem_list']
        valid_problem_list = []
        for i in xrange(0, len(problem_list)):
            #Needed to ensure that the 'location' key can be accessed
            try:
                problem_list[i] = json.loads(problem_list[i])
            except Exception:
                pass
            if does_location_exist(course_id, problem_list[i]['location']):
                valid_problem_list.append(problem_list[i])
        response['problem_list'] = valid_problem_list
        response = json.dumps(response)

        return HttpResponse(response, mimetype="application/json")
    except GradingServiceError:
        #This is a dev_facing_error
        log.exception("Error from staff grading service in open "
                      "ended grading.  server url: {0}".format(
                          staff_grading_service().url))
        #This is a staff_facing_error
        return HttpResponse(
            json.dumps({
                'success': False,
                'error': STAFF_ERROR_MESSAGE
            }))
Exemplo n.º 22
0
    def test_SetPlayerPuzzlesComplete_level_complete(self):  # pylint: disable=invalid-name
        """Check that the level complete function works"""

        puzzles = [{
            "PuzzleID": 13,
            "Set": 1,
            "SubSet": 2
        }, {
            "PuzzleID": 53524,
            "Set": 1,
            "SubSet": 1
        }]

        response = self.make_puzzles_complete_request(puzzles)

        self.assertEqual(response.content,
                         self.set_puzzle_complete_response([13, 53524]))

        puzzles = [{
            "PuzzleID": 14,
            "Set": 1,
            "SubSet": 3
        }, {
            "PuzzleID": 15,
            "Set": 1,
            "SubSet": 1
        }]

        response = self.make_puzzles_complete_request(puzzles)

        self.assertEqual(
            response.content,
            self.set_puzzle_complete_response([13, 14, 15, 53524]))

        is_complete = partial(PuzzleComplete.is_level_complete,
                              unique_id_for_user(self.user))

        self.assertTrue(is_complete(1, 1))
        self.assertTrue(is_complete(1, 3))
        self.assertTrue(is_complete(1, 2))
        self.assertFalse(is_complete(4, 5))

        puzzles = [{"PuzzleID": 74, "Set": 4, "SubSet": 5}]

        response = self.make_puzzles_complete_request(puzzles)

        self.assertTrue(is_complete(4, 5))

        # Now check due dates

        self.assertTrue(is_complete(1, 1, due=self.tomorrow))
        self.assertFalse(is_complete(1, 1, due=self.yesterday))
Exemplo n.º 23
0
def flagged_problem_list(request, course_id):
    '''
    Show a student problem list
    '''
    course = get_course_with_access(request.user, course_id, 'staff')
    student_id = unique_id_for_user(request.user)

    # call problem list service
    success = False
    error_text = ""
    problem_list = []
    base_course_url = reverse('courses')

    # Make a service that can query edX ORA.
    controller_qs = create_controller_query_service()
    try:
        problem_list_json = controller_qs.get_flagged_problem_list(course_id)
        problem_list_dict = json.loads(problem_list_json)
        success = problem_list_dict['success']
        if 'error' in problem_list_dict:
            error_text = problem_list_dict['error']
            problem_list = []
        else:
            problem_list = problem_list_dict['flagged_submissions']

    except GradingServiceError:
        #This is a staff_facing_error
        error_text = STAFF_ERROR_MESSAGE
        #This is a dev_facing_error
        log.error("Could not get flagged problem list from external grading service for open ended.")
        success = False
    # catch error if if the json loads fails
    except ValueError:
        #This is a staff_facing_error
        error_text = STAFF_ERROR_MESSAGE
        #This is a dev_facing_error
        log.error("Could not parse problem list from external grading service response.")
        success = False

    ajax_url = _reverse_with_slash('open_ended_flagged_problems', course_id)
    context = {
        'course': course,
        'course_id': course_id,
        'ajax_url': ajax_url,
        'success': success,
        'problem_list': problem_list,
        'error_text': error_text,
        # Checked above
        'staff_access': True,
    }
    return render_to_response('open_ended_problems/open_ended_flagged_problems.html', context)
Exemplo n.º 24
0
def save_scores(user, puzzle_scores):
    score_responses = []
    for score in puzzle_scores:
        log.debug("score: %s", score)
        # expected keys ScoreType, PuzzleID (int),
        # BestScore (energy), CurrentScore (Energy), ScoreVersion (int)

        puzzle_id = score['PuzzleID']
        best_score = score['BestScore']
        current_score = score['CurrentScore']
        score_version = score['ScoreVersion']

        # SetPlayerPuzzleScoreResponse object
        # Score entries are unique on
        # user/unique_user_id/puzzle_id/score_version
        try:
            obj = Score.objects.get(
                user=user,
                unique_user_id=unique_id_for_user(user),
                puzzle_id=puzzle_id,
                score_version=score_version)
            obj.current_score = current_score
            obj.best_score = best_score

        except Score.DoesNotExist:
            obj = Score(
                user=user,
                unique_user_id=unique_id_for_user(user),
                puzzle_id=puzzle_id,
                current_score=current_score,
                best_score=best_score,
                score_version=score_version)
        obj.save()

        score_responses.append({'PuzzleID': puzzle_id,
                                'Status': 'Success'})

    return {"OperationID": "SetPlayerPuzzleScores", "Value": score_responses}
def peer_grading_notifications(course, user):
    system = LmsModuleSystem(
        track_function=None,
        get_module=None,
        render_template=render_to_string,
        replace_urls=None,
        descriptor_runtime=None,
        services={
            'i18n': ModuleI18nService(),
        },
    )
    peer_gs = peer_grading_service.PeerGradingService(
        settings.OPEN_ENDED_GRADING_INTERFACE, system)
    pending_grading = False
    img_path = ""
    course_id = course.id
    student_id = unique_id_for_user(user)
    notification_type = "peer"

    success, notification_dict = get_value_from_cache(student_id, course_id,
                                                      notification_type)
    if success:
        return notification_dict

    try:
        notifications = json.loads(
            peer_gs.get_notifications(course_id, student_id))
        if notifications['success']:
            if notifications['student_needs_to_peer_grade']:
                pending_grading = True
    except:
        #Non catastrophic error, so no real action
        notifications = {}
        #This is a dev_facing_error
        log.info(
            "Problem with getting notifications from peer grading service for course {0} user {1}."
            .format(course_id, student_id))
    if pending_grading:
        img_path = "/static/images/grading_notification.png"

    notification_dict = {
        'pending_grading': pending_grading,
        'img_path': img_path,
        'response': notifications
    }

    set_value_in_cache(student_id, course_id, notification_type,
                       notification_dict)

    return notification_dict
Exemplo n.º 26
0
def flagged_problem_list(request, course_id):
    """
    Show a student problem list
    """
    course = get_course_with_access(request.user, course_id, "staff")
    student_id = unique_id_for_user(request.user)

    # call problem list service
    success = False
    error_text = ""
    problem_list = []
    base_course_url = reverse("courses")

    try:
        problem_list_json = controller_qs.get_flagged_problem_list(course_id)
        problem_list_dict = json.loads(problem_list_json)
        success = problem_list_dict["success"]
        if "error" in problem_list_dict:
            error_text = problem_list_dict["error"]
            problem_list = []
        else:
            problem_list = problem_list_dict["flagged_submissions"]

    except GradingServiceError:
        # This is a staff_facing_error
        error_text = STAFF_ERROR_MESSAGE
        # This is a dev_facing_error
        log.error("Could not get flagged problem list from external grading service for open ended.")
        success = False
    # catch error if if the json loads fails
    except ValueError:
        # This is a staff_facing_error
        error_text = STAFF_ERROR_MESSAGE
        # This is a dev_facing_error
        log.error("Could not parse problem list from external grading service response.")
        success = False

    ajax_url = _reverse_with_slash("open_ended_flagged_problems", course_id)
    context = {
        "course": course,
        "course_id": course_id,
        "ajax_url": ajax_url,
        "success": success,
        "problem_list": problem_list,
        "error_text": error_text,
        # Checked above
        "staff_access": True,
    }
    return render_to_response("open_ended_problems/open_ended_flagged_problems.html", context)
Exemplo n.º 27
0
    def make(svalue):
        """
        Given a User value entry `svalue`, extracts the student's email and fullname,
        and provides a unique id for the user.

        Returns a dictionary with keys 'EMAIL', 'FULLNAME', and 'EDX_ID'.
        """
        fake_user = FakeUser(svalue["user_id"], svalue["user__username"], lambda: True)

        entry = {
            "EMAIL": svalue["user__email"],
            "FULLNAME": svalue["name"].title(),
            "EDX_ID": unique_id_for_user(fake_user),
        }

        return entry
    def make(svalue):
        """
        Given a User value entry `svalue`, extracts the student's email and fullname,
        and provides a unique id for the user.

        Returns a dictionary with keys 'EMAIL', 'FULLNAME', and 'EDX_ID'.
        """
        fake_user = FakeUser(svalue['user_id'], svalue['user__username'], lambda: True)

        entry = {
            'EMAIL': svalue['user__email'],
            'FULLNAME': svalue['name'].title(),
            'EDX_ID': unique_id_for_user(fake_user)
        }

        return entry
    def make(svalue):
        """
        Given a User value entry `svalue`, extracts the student's email and fullname,
        and provides a unique id for the user.

        Returns a dictionary with keys 'EMAIL', 'FULLNAME', and 'EDX_ID'.
        """
        fake_user = FakeUser(svalue['user_id'], svalue['user__username'], lambda: True)

        entry = {
            'EMAIL': svalue['user__email'],
            'FULLNAME': svalue['name'].title(),
            'EDX_ID': unique_id_for_user(fake_user)
        }

        return entry
Exemplo n.º 30
0
def student_problem_list(request, course_id):
    """
    Show a list of problems they have attempted to a student.
    Fetch the list from the grading controller server and append some data.
    @param request: The request object for this view.
    @param course_id: The id of the course to get the problem list for.
    @return: Renders an HTML problem list table.
    """
    assert isinstance(course_id, basestring)
    course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
    # Load the course.  Don't catch any errors here, as we want them to be loud.
    course = get_course_with_access(request.user, 'load', course_key)

    # The anonymous student id is needed for communication with ORA.
    student_id = unique_id_for_user(request.user)
    base_course_url = reverse('courses')
    error_text = ""

    student_problem_list = StudentProblemList(course_key, student_id)
    # Get the problem list from ORA.
    success = student_problem_list.fetch_from_grading_service()
    # If we fetched the problem list properly, add in additional problem data.
    if success:
        # Add in links to problems.
        valid_problems = student_problem_list.add_problem_data(base_course_url)
    else:
        # Get an error message to show to the student.
        valid_problems = []
        error_text = student_problem_list.error_text

    ajax_url = _reverse_with_slash('open_ended_problems', course_key)

    context = {
        'course': course,
        'course_id': course_key.to_deprecated_string(),
        'ajax_url': ajax_url,
        'success': success,
        'problem_list': valid_problems,
        'error_text': error_text,
        # Checked above
        'staff_access': False,
    }

    return render_to_response('open_ended_problems/open_ended_problems.html',
                              context)
Exemplo n.º 31
0
def student_problem_list(request, course_id):
    """
    Show a list of problems they have attempted to a student.
    Fetch the list from the grading controller server and append some data.
    @param request: The request object for this view.
    @param course_id: The id of the course to get the problem list for.
    @return: Renders an HTML problem list table.
    """
    assert isinstance(course_id, basestring)
    course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
    # Load the course.  Don't catch any errors here, as we want them to be loud.
    course = get_course_with_access(request.user, "load", course_key)

    # The anonymous student id is needed for communication with ORA.
    student_id = unique_id_for_user(request.user)
    base_course_url = reverse("courses")
    error_text = ""

    student_problem_list = StudentProblemList(course_key, student_id)
    # Get the problem list from ORA.
    success = student_problem_list.fetch_from_grading_service()
    # If we fetched the problem list properly, add in additional problem data.
    if success:
        # Add in links to problems.
        valid_problems = student_problem_list.add_problem_data(base_course_url)
    else:
        # Get an error message to show to the student.
        valid_problems = []
        error_text = student_problem_list.error_text

    ajax_url = _reverse_with_slash("open_ended_problems", course_key)

    context = {
        "course": course,
        "course_id": course_key.to_deprecated_string(),
        "ajax_url": ajax_url,
        "success": success,
        "problem_list": valid_problems,
        "error_text": error_text,
        # Checked above
        "staff_access": False,
    }

    return render_to_response("open_ended_problems/open_ended_problems.html", context)
Exemplo n.º 32
0
def student_problem_list(request, course_id):
    """
    Show a list of problems they have attempted to a student.
    Fetch the list from the grading controller server and append some data.
    @param request: The request object for this view.
    @param course_id: The id of the course to get the problem list for.
    @return: Renders an HTML problem list table.
    """

    # Load the course.  Don't catch any errors here, as we want them to be loud.
    course = get_course_with_access(request.user, course_id, 'load')

    # The anonymous student id is needed for communication with ORA.
    student_id = unique_id_for_user(request.user)
    base_course_url = reverse('courses')
    error_text = ""

    student_problem_list = StudentProblemList(course_id, student_id)
    # Get the problem list from ORA.
    success = student_problem_list.fetch_from_grading_service()
    # If we fetched the problem list properly, add in additional problem data.
    if success:
        # Add in links to problems.
        valid_problems = student_problem_list.add_problem_data(base_course_url)
    else:
        # Get an error message to show to the student.
        valid_problems = []
        error_text = student_problem_list.error_text

    ajax_url = _reverse_with_slash('open_ended_problems', course_id)

    context = {
        'course': course,
        'course_id': course_id,
        'ajax_url': ajax_url,
        'success': success,
        'problem_list': valid_problems,
        'error_text': error_text,
        # Checked above
        'staff_access': False,
        }

    return render_to_response('open_ended_problems/open_ended_problems.html', context)
Exemplo n.º 33
0
    def test_get_problem_list(self):
        """
        Test to see if the StudentProblemList class can get and parse a problem list from ORA.
        Mock the get_grading_status_list function using StudentProblemListMockQuery.
        """
        # Initialize a StudentProblemList object.
        student_problem_list = utils.StudentProblemList(self.course.id, unique_id_for_user(self.user))
        # Get the initial problem list from ORA.
        success = student_problem_list.fetch_from_grading_service()
        # Should be successful, and we should have three problems.  See mock class for details.
        self.assertTrue(success)
        self.assertEqual(len(student_problem_list.problem_list), 3)

        # See if the problem locations are valid.
        valid_problems = student_problem_list.add_problem_data(reverse('courses'))
        # One location is invalid, so we should now have two.
        self.assertEqual(len(valid_problems), 2)
        # Ensure that human names are being set properly.
        self.assertEqual(valid_problems[0]['grader_type_display_name'], "Instructor Assessment")
Exemplo n.º 34
0
    def test_SetPlayerPuzzlesComplete_level_complete(self):  # pylint: disable=invalid-name
        """Check that the level complete function works"""

        puzzles = [
            {"PuzzleID": 13, "Set": 1, "SubSet": 2},
            {"PuzzleID": 53524, "Set": 1, "SubSet": 1}
        ]

        response = self.make_puzzles_complete_request(puzzles)

        self.assertEqual(response.content,
                         self.set_puzzle_complete_response([13, 53524]))

        puzzles = [
            {"PuzzleID": 14, "Set": 1, "SubSet": 3},
            {"PuzzleID": 15, "Set": 1, "SubSet": 1}
        ]

        response = self.make_puzzles_complete_request(puzzles)

        self.assertEqual(response.content,
                         self.set_puzzle_complete_response([13, 14, 15, 53524]))

        is_complete = partial(
            PuzzleComplete.is_level_complete, unique_id_for_user(self.user))

        self.assertTrue(is_complete(1, 1))
        self.assertTrue(is_complete(1, 3))
        self.assertTrue(is_complete(1, 2))
        self.assertFalse(is_complete(4, 5))

        puzzles = [{"PuzzleID": 74, "Set": 4, "SubSet": 5}]

        response = self.make_puzzles_complete_request(puzzles)

        self.assertTrue(is_complete(4, 5))

        # Now check due dates

        self.assertTrue(is_complete(1, 1, due=self.tomorrow))
        self.assertFalse(is_complete(1, 1, due=self.yesterday))
def get_next(request, course_id):
    """
    Get the next thing to grade for course_id and with the location specified
    in the request.

    Returns a json dict with the following keys:

    'success': bool

    'submission_id': a unique identifier for the submission, to be passed back
                     with the grade.

    'submission': the submission, rendered as read-only html for grading

    'rubric': the rubric, also rendered as html.

    'message': if there was no submission available, but nothing went wrong,
            there will be a message field.

    'error': if success is False, will have an error message with more info.
    """
    assert (isinstance(course_id, basestring))
    course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
    _check_access(request.user, course_key)

    required = set(['location'])
    if request.method != 'POST':
        raise Http404
    actual = set(request.POST.keys())
    missing = required - actual
    if len(missing) > 0:
        return _err_response('Missing required keys {0}'.format(
            ', '.join(missing)))
    grader_id = unique_id_for_user(request.user)
    p = request.POST
    location = course_key.make_usage_key_from_deprecated_string(p['location'])

    return HttpResponse(json.dumps(_get_next(course_key, grader_id, location)),
                        mimetype="application/json")
Exemplo n.º 36
0
def get_next(request, course_id):
    """
    Get the next thing to grade for course_id and with the location specified
    in the request.

    Returns a json dict with the following keys:

    'success': bool

    'submission_id': a unique identifier for the submission, to be passed back
                     with the grade.

    'submission': the submission, rendered as read-only html for grading

    'rubric': the rubric, also rendered as html.

    'message': if there was no submission available, but nothing went wrong,
            there will be a message field.

    'error': if success is False, will have an error message with more info.
    """
    assert(isinstance(course_id, basestring))
    course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
    _check_access(request.user, course_key)

    required = set(['location'])
    if request.method != 'POST':
        raise Http404
    actual = set(request.POST.keys())
    missing = required - actual
    if len(missing) > 0:
        return _err_response('Missing required keys {0}'.format(
            ', '.join(missing)))
    grader_id = unique_id_for_user(request.user)
    p = request.POST
    location = course_key.make_usage_key_from_deprecated_string(p['location'])

    return HttpResponse(json.dumps(_get_next(course_key, grader_id, location)),
                        mimetype="application/json")
Exemplo n.º 37
0
def get_module_for_descriptor_internal(user,
                                       descriptor,
                                       field_data_cache,
                                       course_id,
                                       track_function,
                                       xqueue_callback_url_prefix,
                                       position=None,
                                       wrap_xmodule_display=True,
                                       grade_bucket_type=None,
                                       static_asset_path=''):
    """
    Actually implement get_module, without requiring a request.

    See get_module() docstring for further details.
    """

    # Short circuit--if the user shouldn't have access, bail without doing any work
    if not has_access(user, descriptor, 'load', course_id):
        return None

    # Setup system context for module instance
    ajax_url = reverse(
        'modx_dispatch',
        kwargs=dict(course_id=course_id,
                    location=descriptor.location.url(),
                    dispatch=''),
    )
    # Intended use is as {ajax_url}/{dispatch_command}, so get rid of the trailing slash.
    ajax_url = ajax_url.rstrip('/')

    def make_xqueue_callback(dispatch='score_update'):
        # Fully qualified callback URL for external queueing system
        relative_xqueue_callback_url = reverse(
            'xqueue_callback',
            kwargs=dict(course_id=course_id,
                        userid=str(user.id),
                        mod_id=descriptor.location.url(),
                        dispatch=dispatch),
        )
        return xqueue_callback_url_prefix + relative_xqueue_callback_url

    # Default queuename is course-specific and is derived from the course that
    #   contains the current module.
    # TODO: Queuename should be derived from 'course_settings.json' of each course
    xqueue_default_queuename = descriptor.location.org + '-' + descriptor.location.course

    xqueue = {
        'interface': xqueue_interface,
        'construct_callback': make_xqueue_callback,
        'default_queuename': xqueue_default_queuename.replace(' ', '_'),
        'waittime': settings.XQUEUE_WAITTIME_BETWEEN_REQUESTS
    }

    # This is a hacky way to pass settings to the combined open ended xmodule
    # It needs an S3 interface to upload images to S3
    # It needs the open ended grading interface in order to get peer grading to be done
    # this first checks to see if the descriptor is the correct one, and only sends settings if it is

    # Get descriptor metadata fields indicating needs for various settings
    needs_open_ended_interface = getattr(descriptor,
                                         "needs_open_ended_interface", False)
    needs_s3_interface = getattr(descriptor, "needs_s3_interface", False)

    # Initialize interfaces to None
    open_ended_grading_interface = None
    s3_interface = None

    # Create interfaces if needed
    if needs_open_ended_interface:
        open_ended_grading_interface = settings.OPEN_ENDED_GRADING_INTERFACE
        open_ended_grading_interface[
            'mock_peer_grading'] = settings.MOCK_PEER_GRADING
        open_ended_grading_interface[
            'mock_staff_grading'] = settings.MOCK_STAFF_GRADING
    if needs_s3_interface:
        s3_interface = {
            'access_key':
            getattr(settings, 'AWS_ACCESS_KEY_ID', ''),
            'secret_access_key':
            getattr(settings, 'AWS_SECRET_ACCESS_KEY', ''),
            'storage_bucket_name':
            getattr(settings, 'AWS_STORAGE_BUCKET_NAME', 'openended')
        }

    def inner_get_module(descriptor):
        """
        Delegate to get_module_for_descriptor_internal() with all values except `descriptor` set.

        Because it does an access check, it may return None.
        """
        # TODO: fix this so that make_xqueue_callback uses the descriptor passed into
        # inner_get_module, not the parent's callback.  Add it as an argument....
        return get_module_for_descriptor_internal(
            user, descriptor, field_data_cache, course_id, track_function,
            make_xqueue_callback, position, wrap_xmodule_display,
            grade_bucket_type, static_asset_path)

    def xblock_field_data(descriptor):
        student_data = DbModel(DjangoKeyValueStore(field_data_cache))
        return lms_field_data(descriptor._field_data, student_data)

    def publish(event):
        """A function that allows XModules to publish events. This only supports grade changes right now."""
        if event.get('event_name') != 'grade':
            return

        # Construct the key for the module
        key = KeyValueStore.Key(scope=Scope.user_state,
                                student_id=user.id,
                                block_scope_id=descriptor.location,
                                field_name='grade')

        student_module = field_data_cache.find_or_create(key)
        # Update the grades
        student_module.grade = event.get('value')
        student_module.max_grade = event.get('max_value')
        # Save all changes to the underlying KeyValueStore
        student_module.save()

        # Bin score into range and increment stats
        score_bucket = get_score_bucket(student_module.grade,
                                        student_module.max_grade)
        org, course_num, run = course_id.split("/")

        tags = [
            "org:{0}".format(org), "course:{0}".format(course_num),
            "run:{0}".format(run), "score_bucket:{0}".format(score_bucket)
        ]

        if grade_bucket_type is not None:
            tags.append('type:%s' % grade_bucket_type)

        statsd.increment("lms.courseware.question_answered", tags=tags)

    # TODO (cpennington): When modules are shared between courses, the static
    # prefix is going to have to be specific to the module, not the directory
    # that the xml was loaded from

    system = ModuleSystem(
        track_function=track_function,
        render_template=render_to_string,
        ajax_url=ajax_url,
        xqueue=xqueue,
        # TODO (cpennington): Figure out how to share info between systems
        filestore=descriptor.system.resources_fs,
        get_module=inner_get_module,
        user=user,
        # TODO (cpennington): This should be removed when all html from
        # a module is coming through get_html and is therefore covered
        # by the replace_static_urls code below
        replace_urls=partial(
            static_replace.replace_static_urls,
            data_directory=getattr(descriptor, 'data_dir', None),
            course_id=course_id,
            static_asset_path=static_asset_path
            or descriptor.static_asset_path,
        ),
        replace_course_urls=partial(static_replace.replace_course_urls,
                                    course_id=course_id),
        replace_jump_to_id_urls=partial(static_replace.replace_jump_to_id_urls,
                                        course_id=course_id,
                                        jump_to_id_base_url=reverse(
                                            'jump_to_id',
                                            kwargs={
                                                'course_id': course_id,
                                                'module_id': ''
                                            })),
        node_path=settings.NODE_PATH,
        xblock_field_data=xblock_field_data,
        publish=publish,
        anonymous_student_id=unique_id_for_user(user),
        course_id=course_id,
        open_ended_grading_interface=open_ended_grading_interface,
        s3_interface=s3_interface,
        cache=cache,
        can_execute_unsafe_code=(lambda: can_execute_unsafe_code(course_id)),
        # TODO: When we merge the descriptor and module systems, we can stop reaching into the mixologist (cpennington)
        mixins=descriptor.system.mixologist._mixins,
    )

    # pass position specified in URL to module through ModuleSystem
    system.set('position', position)
    system.set('DEBUG', settings.DEBUG)
    if settings.MITX_FEATURES.get('ENABLE_PSYCHOMETRICS'):
        system.set(
            'psychometrics_handler',  # set callback for updating PsychometricsData
            make_psychometrics_data_update_handler(course_id, user,
                                                   descriptor.location.url()))

    try:
        module = descriptor.xmodule(system)
    except:
        log.exception(
            "Error creating module from descriptor {0}".format(descriptor))

        # make an ErrorDescriptor -- assuming that the descriptor's system is ok
        if has_access(user, descriptor.location, 'staff', course_id):
            err_descriptor_class = ErrorDescriptor
        else:
            err_descriptor_class = NonStaffErrorDescriptor

        err_descriptor = err_descriptor_class.from_descriptor(
            descriptor, error_msg=exc_info_to_str(sys.exc_info()))

        # Make an error module
        return err_descriptor.xmodule(system)

    system.set('user_is_staff',
               has_access(user, descriptor.location, 'staff', course_id))
    _get_html = module.get_html

    if wrap_xmodule_display is True:
        _get_html = wrap_xmodule(module.get_html, module,
                                 'xmodule_display.html')

    module.get_html = replace_static_urls(_get_html,
                                          getattr(descriptor, 'data_dir',
                                                  None),
                                          course_id=course_id,
                                          static_asset_path=static_asset_path
                                          or descriptor.static_asset_path)

    # Allow URLs of the form '/course/' refer to the root of multicourse directory
    #   hierarchy of this course
    module.get_html = replace_course_urls(module.get_html, course_id)

    # this will rewrite intra-courseware links
    # that use the shorthand /jump_to_id/<id>. This is very helpful
    # for studio authored courses (compared to the /course/... format) since it is
    # is durable with respect to moves and the author doesn't need to
    # know the hierarchy
    # NOTE: module_id is empty string here. The 'module_id' will get assigned in the replacement
    # function, we just need to specify something to get the reverse() to work
    module.get_html = replace_jump_to_id_urls(
        module.get_html, course_id,
        reverse('jump_to_id', kwargs={
            'course_id': course_id,
            'module_id': ''
        }))

    if settings.MITX_FEATURES.get('DISPLAY_HISTOGRAMS_TO_STAFF'):
        if has_access(user, module, 'staff', course_id):
            module.get_html = add_histogram(module.get_html, module, user)

    # force the module to save after rendering
    module.get_html = save_module(module.get_html, module)
    return module
Exemplo n.º 38
0
def student_problem_list(request, course_id):
    '''
    Show a student problem list to a student.  Fetch the list from the grading controller server, get some metadata,
    and then show it to the student.
    '''
    course = get_course_with_access(request.user, course_id, 'load')
    student_id = unique_id_for_user(request.user)

    # call problem list service
    success = False
    error_text = ""
    problem_list = []
    base_course_url = reverse('courses')
    list_to_remove = []

    try:
        #Get list of all open ended problems that the grading server knows about
        problem_list_json = controller_qs.get_grading_status_list(
            course_id, unique_id_for_user(request.user))
        problem_list_dict = json.loads(problem_list_json)
        success = problem_list_dict['success']
        if 'error' in problem_list_dict:
            error_text = problem_list_dict['error']
            problem_list = []
        else:
            problem_list = problem_list_dict['problem_list']

        #A list of problems to remove (problems that can't be found in the course)
        for i in xrange(0, len(problem_list)):
            try:
                #Try to load each problem in the courseware to get links to them
                problem_url_parts = search.path_to_location(
                    modulestore(), course.id, problem_list[i]['location'])
            except ItemNotFoundError:
                #If the problem cannot be found at the location received from the grading controller server, it has been deleted by the course author.
                #Continue with the rest of the location to construct the list
                error_message = "Could not find module for course {0} at location {1}".format(
                    course.id, problem_list[i]['location'])
                log.error(error_message)
                #Mark the problem for removal from the list
                list_to_remove.append(i)
                continue
            problem_url = generate_problem_url(problem_url_parts,
                                               base_course_url)
            problem_list[i].update({'actual_url': problem_url})
            eta_available = problem_list[i]['eta_available']
            if isinstance(eta_available, basestring):
                eta_available = (eta_available.lower() == "true")

            eta_string = "N/A"
            if eta_available:
                try:
                    eta_string = convert_seconds_to_human_readable(
                        int(problem_list[i]['eta']))
                except:
                    #This is a student_facing_error
                    eta_string = "Error getting ETA."
            problem_list[i].update({'eta_string': eta_string})

    except GradingServiceError:
        #This is a student_facing_error
        error_text = STUDENT_ERROR_MESSAGE
        #This is a dev facing error
        log.error("Problem contacting open ended grading service.")
        success = False
    # catch error if if the json loads fails
    except ValueError:
        #This is a student facing error
        error_text = STUDENT_ERROR_MESSAGE
        #This is a dev_facing_error
        log.error(
            "Problem with results from external grading service for open ended."
        )
        success = False

    #Remove problems that cannot be found in the courseware from the list
    problem_list = [
        problem_list[i] for i in xrange(0, len(problem_list))
        if i not in list_to_remove
    ]
    ajax_url = _reverse_with_slash('open_ended_problems', course_id)

    return render_to_response(
        'open_ended_problems/open_ended_problems.html',
        {
            'course': course,
            'course_id': course_id,
            'ajax_url': ajax_url,
            'success': success,
            'problem_list': problem_list,
            'error_text': error_text,
            # Checked above
            'staff_access': False,
        })
Exemplo n.º 39
0
def process_survey_link(survey_link, user):
    """
    If {UNIQUE_ID} appears in the link, replace it with a unique id for the user.
    Currently, this is sha1(user.username).  Otherwise, return survey_link.
    """
    return survey_link.format(UNIQUE_ID=unique_id_for_user(user))
Exemplo n.º 40
0
def combined_notifications(course, user):
    """
    Show notifications to a given user for a given course.  Get notifications from the cache if possible,
    or from the grading controller server if not.
    @param course: The course object for which we are getting notifications
    @param user: The user object for which we are getting notifications
    @return: A dictionary with boolean pending_grading (true if there is pending grading), img_path (for notification
    image), and response (actual response from grading controller server).
    """
    # Set up return values so that we can return them for error cases
    pending_grading = False
    img_path = ""
    notifications = {}
    notification_dict = {
        'pending_grading': pending_grading,
        'img_path': img_path,
        'response': notifications
    }

    # We don't want to show anonymous users anything.
    if not user.is_authenticated():
        return notification_dict

    # Define a mock modulesystem
    system = ModuleSystem(ajax_url=None,
                          track_function=None,
                          get_module=None,
                          render_template=render_to_string,
                          replace_urls=None,
                          xblock_model_data={})
    # Initialize controller query service using our mock system
    controller_qs = ControllerQueryService(
        settings.OPEN_ENDED_GRADING_INTERFACE, system)
    student_id = unique_id_for_user(user)
    user_is_staff = has_access(user, course, 'staff')
    course_id = course.id
    notification_type = "combined"

    # See if we have a stored value in the cache
    success, notification_dict = get_value_from_cache(student_id, course_id,
                                                      notification_type)
    if success:
        return notification_dict

    # Get the time of the last login of the user
    last_login = user.last_login

    # Find the modules they have seen since they logged in
    last_module_seen = StudentModule.objects.filter(
        student=user, course_id=course_id,
        modified__gt=last_login).values('modified').order_by('-modified')
    last_module_seen_count = last_module_seen.count()

    if last_module_seen_count > 0:
        # The last time they viewed an updated notification (last module seen
        # minus how long notifications are cached)
        last_time_viewed = last_module_seen[0]['modified'] - datetime.timedelta(
            seconds=(NOTIFICATION_CACHE_TIME + 60))
    else:
        # If they have not seen any modules since they logged in, then don't
        # refresh
        return {
            'pending_grading': False,
            'img_path': img_path,
            'response': notifications
        }

    try:
        # Get the notifications from the grading controller
        controller_response = controller_qs.check_combined_notifications(
            course.id, student_id, user_is_staff, last_time_viewed)
        notifications = json.loads(controller_response)
        if notifications['success']:
            if notifications['overall_need_to_check']:
                pending_grading = True
    except:
        # Non catastrophic error, so no real action
        # This is a dev_facing_error
        log.exception(
            "Problem with getting notifications from controller query service for course {0} user {1}."
            .format(course_id, student_id))

    if pending_grading:
        img_path = "/static/images/grading_notification.png"

    notification_dict = {
        'pending_grading': pending_grading,
        'img_path': img_path,
        'response': notifications
    }

    # Store the notifications in the cache
    set_value_in_cache(student_id, course_id, notification_type,
                       notification_dict)

    return notification_dict
Exemplo n.º 41
0
def combined_notifications(course, user):
    """
    Show notifications to a given user for a given course.  Get notifications from the cache if possible,
    or from the grading controller server if not.
    @param course: The course object for which we are getting notifications
    @param user: The user object for which we are getting notifications
    @return: A dictionary with boolean pending_grading (true if there is pending grading), img_path (for notification
    image), and response (actual response from grading controller server).
    """
    #Set up return values so that we can return them for error cases
    pending_grading = False
    img_path = ""
    notifications={}
    notification_dict = {'pending_grading': pending_grading, 'img_path': img_path, 'response': notifications}

    #We don't want to show anonymous users anything.
    if not user.is_authenticated():
        return notification_dict

    #Define a mock modulesystem
    system = ModuleSystem(
        ajax_url=None,
        track_function=None,
        get_module = None,
        render_template=render_to_string,
        replace_urls=None,
        xblock_model_data= {}
    )
    #Initialize controller query service using our mock system
    controller_qs = ControllerQueryService(settings.OPEN_ENDED_GRADING_INTERFACE, system)
    student_id = unique_id_for_user(user)
    user_is_staff = has_access(user, course, 'staff')
    course_id = course.id
    notification_type = "combined"

    #See if we have a stored value in the cache
    success, notification_dict = get_value_from_cache(student_id, course_id, notification_type)
    if success:
        return notification_dict

    #Get the time of the last login of the user
    last_login = user.last_login

    #Find the modules they have seen since they logged in
    last_module_seen = StudentModule.objects.filter(student=user, course_id=course_id,
                                                    modified__gt=last_login).values('modified').order_by(
        '-modified')
    last_module_seen_count = last_module_seen.count()

    if last_module_seen_count > 0:
        #The last time they viewed an updated notification (last module seen minus how long notifications are cached)
        last_time_viewed = last_module_seen[0]['modified'] - datetime.timedelta(seconds=(NOTIFICATION_CACHE_TIME + 60))
    else:
        #If they have not seen any modules since they logged in, then don't refresh
        return {'pending_grading': False, 'img_path': img_path, 'response': notifications}

    try:
        #Get the notifications from the grading controller
        controller_response = controller_qs.check_combined_notifications(course.id, student_id, user_is_staff,
                                                                         last_time_viewed)
        notifications = json.loads(controller_response)
        if notifications['success']:
            if notifications['overall_need_to_check']:
                pending_grading = True
    except:
        #Non catastrophic error, so no real action
        #This is a dev_facing_error
        log.exception(
            "Problem with getting notifications from controller query service for course {0} user {1}.".format(
                course_id, student_id))

    if pending_grading:
        img_path = "/static/images/grading_notification.png"

    notification_dict = {'pending_grading': pending_grading, 'img_path': img_path, 'response': notifications}

    #Store the notifications in the cache
    set_value_in_cache(student_id, course_id, notification_type, notification_dict)

    return notification_dict
def combined_notifications(course, user):
    """
    Show notifications to a given user for a given course.  Get notifications from the cache if possible,
    or from the grading controller server if not.
    @param course: The course object for which we are getting notifications
    @param user: The user object for which we are getting notifications
    @return: A dictionary with boolean pending_grading (true if there is pending grading), img_path (for notification
    image), and response (actual response from grading controller server).
    """
    #Set up return values so that we can return them for error cases
    pending_grading = False
    img_path = ""
    notifications = {}
    notification_dict = {
        'pending_grading': pending_grading,
        'img_path': img_path,
        'response': notifications
    }

    #We don't want to show anonymous users anything.
    if not user.is_authenticated():
        return notification_dict

    #Define a mock modulesystem
    system = LmsModuleSystem(
        static_url="/static",
        track_function=None,
        get_module=None,
        render_template=render_to_string,
        replace_urls=None,
        descriptor_runtime=None,
        services={
            'i18n': ModuleI18nService(),
        },
    )
    #Initialize controller query service using our mock system
    controller_qs = ControllerQueryService(
        settings.OPEN_ENDED_GRADING_INTERFACE, system)
    student_id = unique_id_for_user(user)
    user_is_staff = has_access(user, course, 'staff')
    course_id = course.id
    notification_type = "combined"

    #See if we have a stored value in the cache
    success, notification_dict = get_value_from_cache(student_id, course_id,
                                                      notification_type)
    if success:
        return notification_dict

    #Get the time of the last login of the user
    last_login = user.last_login
    last_time_viewed = last_login - datetime.timedelta(
        seconds=(NOTIFICATION_CACHE_TIME + 60))

    try:
        #Get the notifications from the grading controller
        controller_response = controller_qs.check_combined_notifications(
            course.id, student_id, user_is_staff, last_time_viewed)
        notifications = json.loads(controller_response)
        if notifications.get('success'):
            if (notifications.get('staff_needs_to_grade')
                    or notifications.get('student_needs_to_peer_grade')):
                pending_grading = True
    except:
        #Non catastrophic error, so no real action
        #This is a dev_facing_error
        log.exception(
            u"Problem with getting notifications from controller query service for course {0} user {1}."
            .format(course_id, student_id))

    if pending_grading:
        img_path = "/static/images/grading_notification.png"

    notification_dict = {
        'pending_grading': pending_grading,
        'img_path': img_path,
        'response': notifications
    }

    #Store the notifications in the cache
    set_value_in_cache(student_id, course_id, notification_type,
                       notification_dict)

    return notification_dict
Exemplo n.º 43
0
def get_module_for_descriptor(user,
                              request,
                              descriptor,
                              model_data_cache,
                              course_id,
                              position=None,
                              wrap_xmodule_display=True,
                              grade_bucket_type=None):
    """
    Actually implement get_module.  See docstring there for details.
    """

    # allow course staff to masquerade as student
    if has_access(user, descriptor, 'staff', course_id):
        setup_masquerade(request, True)

    # Short circuit--if the user shouldn't have access, bail without doing any work
    if not has_access(user, descriptor, 'load', course_id):
        return None

    # Setup system context for module instance
    ajax_url = reverse(
        'modx_dispatch',
        kwargs=dict(course_id=course_id,
                    location=descriptor.location.url(),
                    dispatch=''),
    )
    # Intended use is as {ajax_url}/{dispatch_command}, so get rid of the trailing slash.
    ajax_url = ajax_url.rstrip('/')

    def make_xqueue_callback(dispatch='score_update'):
        # Fully qualified callback URL for external queueing system
        xqueue_callback_url = '{proto}://{host}'.format(
            host=request.get_host(),
            proto=request.META.get('HTTP_X_FORWARDED_PROTO',
                                   'https' if request.is_secure() else 'http'))
        xqueue_callback_url = settings.XQUEUE_INTERFACE.get(
            'callback_url', xqueue_callback_url)  # allow override

        xqueue_callback_url += reverse(
            'xqueue_callback',
            kwargs=dict(course_id=course_id,
                        userid=str(user.id),
                        id=descriptor.location.url(),
                        dispatch=dispatch),
        )
        return xqueue_callback_url

    # Default queuename is course-specific and is derived from the course that
    #   contains the current module.
    # TODO: Queuename should be derived from 'course_settings.json' of each course
    xqueue_default_queuename = descriptor.location.org + '-' + descriptor.location.course

    xqueue = {
        'interface': xqueue_interface,
        'construct_callback': make_xqueue_callback,
        'default_queuename': xqueue_default_queuename.replace(' ', '_'),
        'waittime': settings.XQUEUE_WAITTIME_BETWEEN_REQUESTS
    }

    #This is a hacky way to pass settings to the combined open ended xmodule
    #It needs an S3 interface to upload images to S3
    #It needs the open ended grading interface in order to get peer grading to be done
    #this first checks to see if the descriptor is the correct one, and only sends settings if it is

    #Get descriptor metadata fields indicating needs for various settings
    needs_open_ended_interface = getattr(descriptor,
                                         "needs_open_ended_interface", False)
    needs_s3_interface = getattr(descriptor, "needs_s3_interface", False)

    #Initialize interfaces to None
    open_ended_grading_interface = None
    s3_interface = None

    #Create interfaces if needed
    if needs_open_ended_interface:
        open_ended_grading_interface = settings.OPEN_ENDED_GRADING_INTERFACE
        open_ended_grading_interface[
            'mock_peer_grading'] = settings.MOCK_PEER_GRADING
        open_ended_grading_interface[
            'mock_staff_grading'] = settings.MOCK_STAFF_GRADING
    if needs_s3_interface:
        s3_interface = {
            'access_key':
            getattr(settings, 'AWS_ACCESS_KEY_ID', ''),
            'secret_access_key':
            getattr(settings, 'AWS_SECRET_ACCESS_KEY', ''),
            'storage_bucket_name':
            getattr(settings, 'AWS_STORAGE_BUCKET_NAME', 'openended')
        }

    def inner_get_module(descriptor):
        """
        Delegate to get_module.  It does an access check, so may return None
        """
        return get_module_for_descriptor(user, request, descriptor,
                                         model_data_cache, course_id, position)

    def xblock_model_data(descriptor):
        return DbModel(
            LmsKeyValueStore(descriptor._model_data, model_data_cache),
            descriptor.module_class, user.id,
            LmsUsage(descriptor.location, descriptor.location))

    def publish(event):
        if event.get('event_name') != 'grade':
            return

        student_module, created = StudentModule.objects.get_or_create(
            course_id=course_id,
            student=user,
            module_type=descriptor.location.category,
            module_state_key=descriptor.location.url(),
            defaults={'state': '{}'},
        )
        student_module.grade = event.get('value')
        student_module.max_grade = event.get('max_value')
        student_module.save()

        #Bin score into range and increment stats
        score_bucket = get_score_bucket(student_module.grade,
                                        student_module.max_grade)
        org, course_num, run = course_id.split("/")

        tags = [
            "org:{0}".format(org), "course:{0}".format(course_num),
            "run:{0}".format(run), "score_bucket:{0}".format(score_bucket)
        ]

        if grade_bucket_type is not None:
            tags.append('type:%s' % grade_bucket_type)

        statsd.increment("lms.courseware.question_answered", tags=tags)

    def can_execute_unsafe_code():
        # To decide if we can run unsafe code, we check the course id against
        # a list of regexes configured on the server.
        for regex in settings.COURSES_WITH_UNSAFE_CODE:
            if re.match(regex, course_id):
                return True
        return False

    # TODO (cpennington): When modules are shared between courses, the static
    # prefix is going to have to be specific to the module, not the directory
    # that the xml was loaded from
    system = ModuleSystem(
        track_function=make_track_function(request),
        render_template=render_to_string,
        ajax_url=ajax_url,
        xqueue=xqueue,
        # TODO (cpennington): Figure out how to share info between systems
        filestore=descriptor.system.resources_fs,
        get_module=inner_get_module,
        user=user,
        # TODO (cpennington): This should be removed when all html from
        # a module is coming through get_html and is therefore covered
        # by the replace_static_urls code below
        replace_urls=partial(
            static_replace.replace_static_urls,
            data_directory=getattr(descriptor, 'data_dir', None),
            course_namespace=descriptor.location._replace(category=None,
                                                          name=None),
        ),
        node_path=settings.NODE_PATH,
        xblock_model_data=xblock_model_data,
        publish=publish,
        anonymous_student_id=unique_id_for_user(user),
        course_id=course_id,
        open_ended_grading_interface=open_ended_grading_interface,
        s3_interface=s3_interface,
        cache=cache,
        can_execute_unsafe_code=can_execute_unsafe_code,
    )
    # pass position specified in URL to module through ModuleSystem
    system.set('position', position)
    system.set('DEBUG', settings.DEBUG)
    if settings.MITX_FEATURES.get('ENABLE_PSYCHOMETRICS'):
        system.set(
            'psychometrics_handler',  # set callback for updating PsychometricsData
            make_psychometrics_data_update_handler(course_id, user,
                                                   descriptor.location.url()))

    try:
        module = descriptor.xmodule(system)
    except:
        log.exception(
            "Error creating module from descriptor {0}".format(descriptor))

        # make an ErrorDescriptor -- assuming that the descriptor's system is ok
        if has_access(user, descriptor.location, 'staff', course_id):
            err_descriptor_class = ErrorDescriptor
        else:
            err_descriptor_class = NonStaffErrorDescriptor

        err_descriptor = err_descriptor_class.from_xml(
            str(descriptor),
            descriptor.system,
            org=descriptor.location.org,
            course=descriptor.location.course,
            error_msg=exc_info_to_str(sys.exc_info()))

        # Make an error module
        return err_descriptor.xmodule(system)

    system.set('user_is_staff',
               has_access(user, descriptor.location, 'staff', course_id))
    _get_html = module.get_html

    if wrap_xmodule_display == True:
        _get_html = wrap_xmodule(module.get_html, module,
                                 'xmodule_display.html')

    module.get_html = replace_static_urls(
        _get_html,
        getattr(descriptor, 'data_dir', None),
        course_namespace=module.location._replace(category=None, name=None))

    # Allow URLs of the form '/course/' refer to the root of multicourse directory
    #   hierarchy of this course
    module.get_html = replace_course_urls(module.get_html, course_id)

    if settings.MITX_FEATURES.get('DISPLAY_HISTOGRAMS_TO_STAFF'):
        if has_access(user, module, 'staff', course_id):
            module.get_html = add_histogram(module.get_html, module, user)

    return module
Exemplo n.º 44
0
def get_module_for_descriptor_internal(user, descriptor, model_data_cache, course_id,
                                       track_function, xqueue_callback_url_prefix,
                                       position=None, wrap_xmodule_display=True, grade_bucket_type=None):
    """
    Actually implement get_module, without requiring a request.

    See get_module() docstring for further details.
    """

    # Short circuit--if the user shouldn't have access, bail without doing any work
    if not has_access(user, descriptor, 'load', course_id):
        return None

    # Setup system context for module instance
    ajax_url = reverse('modx_dispatch',
                       kwargs=dict(course_id=course_id,
                                   location=descriptor.location.url(),
                                   dispatch=''),
                       )
    # Intended use is as {ajax_url}/{dispatch_command}, so get rid of the trailing slash.
    ajax_url = ajax_url.rstrip('/')

    def make_xqueue_callback(dispatch='score_update'):
        # Fully qualified callback URL for external queueing system
        relative_xqueue_callback_url = reverse('xqueue_callback',
                                               kwargs=dict(course_id=course_id,
                                                           userid=str(user.id),
                                                           mod_id=descriptor.location.url(),
                                                           dispatch=dispatch),
                                       )
        return xqueue_callback_url_prefix + relative_xqueue_callback_url

    # Default queuename is course-specific and is derived from the course that
    #   contains the current module.
    # TODO: Queuename should be derived from 'course_settings.json' of each course
    xqueue_default_queuename = descriptor.location.org + '-' + descriptor.location.course

    xqueue = {'interface': xqueue_interface,
              'construct_callback': make_xqueue_callback,
              'default_queuename': xqueue_default_queuename.replace(' ', '_'),
              'waittime': settings.XQUEUE_WAITTIME_BETWEEN_REQUESTS
             }

    # This is a hacky way to pass settings to the combined open ended xmodule
    # It needs an S3 interface to upload images to S3
    # It needs the open ended grading interface in order to get peer grading to be done
    # this first checks to see if the descriptor is the correct one, and only sends settings if it is

    # Get descriptor metadata fields indicating needs for various settings
    needs_open_ended_interface = getattr(descriptor, "needs_open_ended_interface", False)
    needs_s3_interface = getattr(descriptor, "needs_s3_interface", False)

    # Initialize interfaces to None
    open_ended_grading_interface = None
    s3_interface = None

    # Create interfaces if needed
    if needs_open_ended_interface:
        open_ended_grading_interface = settings.OPEN_ENDED_GRADING_INTERFACE
        open_ended_grading_interface['mock_peer_grading'] = settings.MOCK_PEER_GRADING
        open_ended_grading_interface['mock_staff_grading'] = settings.MOCK_STAFF_GRADING
    if needs_s3_interface:
        s3_interface = {
            'access_key': getattr(settings, 'AWS_ACCESS_KEY_ID', ''),
            'secret_access_key': getattr(settings, 'AWS_SECRET_ACCESS_KEY', ''),
            'storage_bucket_name': getattr(settings, 'AWS_STORAGE_BUCKET_NAME', 'openended')
        }

    def inner_get_module(descriptor):
        """
        Delegate to get_module_for_descriptor_internal() with all values except `descriptor` set.

        Because it does an access check, it may return None.
        """
        # TODO: fix this so that make_xqueue_callback uses the descriptor passed into
        # inner_get_module, not the parent's callback.  Add it as an argument....
        return get_module_for_descriptor_internal(user, descriptor, model_data_cache, course_id,
                                                  track_function, make_xqueue_callback,
                                                  position, wrap_xmodule_display, grade_bucket_type)

    def xblock_model_data(descriptor):
        return DbModel(
            LmsKeyValueStore(descriptor._model_data, model_data_cache),
            descriptor.module_class,
            user.id,
            LmsUsage(descriptor.location, descriptor.location)
        )

    def publish(event):
        if event.get('event_name') != 'grade':
            return

        student_module, created = StudentModule.objects.get_or_create(
            course_id=course_id,
            student=user,
            module_type=descriptor.location.category,
            module_state_key=descriptor.location.url(),
            defaults={'state': '{}'},
        )
        student_module.grade = event.get('value')
        student_module.max_grade = event.get('max_value')
        student_module.save()

        # Bin score into range and increment stats
        score_bucket = get_score_bucket(student_module.grade, student_module.max_grade)
        org, course_num, run = course_id.split("/")

        tags = ["org:{0}".format(org),
                "course:{0}".format(course_num),
                "run:{0}".format(run),
                "score_bucket:{0}".format(score_bucket)]

        if grade_bucket_type is not None:
            tags.append('type:%s' % grade_bucket_type)

        statsd.increment("lms.courseware.question_answered", tags=tags)

    # TODO (cpennington): When modules are shared between courses, the static
    # prefix is going to have to be specific to the module, not the directory
    # that the xml was loaded from
    system = ModuleSystem(track_function=track_function,
                          render_template=render_to_string,
                          ajax_url=ajax_url,
                          xqueue=xqueue,
                          # TODO (cpennington): Figure out how to share info between systems
                          filestore=descriptor.system.resources_fs,
                          get_module=inner_get_module,
                          user=user,
                          # TODO (cpennington): This should be removed when all html from
                          # a module is coming through get_html and is therefore covered
                          # by the replace_static_urls code below
                          replace_urls=partial(
                              static_replace.replace_static_urls,
                              data_directory=getattr(descriptor, 'data_dir', None),
                              course_namespace=descriptor.location._replace(category=None, name=None),
                          ),
                          node_path=settings.NODE_PATH,
                          xblock_model_data=xblock_model_data,
                          publish=publish,
                          anonymous_student_id=unique_id_for_user(user),
                          course_id=course_id,
                          open_ended_grading_interface=open_ended_grading_interface,
                          s3_interface=s3_interface,
                          cache=cache,
                          can_execute_unsafe_code=(lambda: can_execute_unsafe_code(course_id)),
                          )
    # pass position specified in URL to module through ModuleSystem
    system.set('position', position)
    system.set('DEBUG', settings.DEBUG)
    if settings.MITX_FEATURES.get('ENABLE_PSYCHOMETRICS'):
        system.set('psychometrics_handler',  # set callback for updating PsychometricsData
                   make_psychometrics_data_update_handler(course_id, user, descriptor.location.url()))

    try:
        module = descriptor.xmodule(system)
    except:
        log.exception("Error creating module from descriptor {0}".format(descriptor))

        # make an ErrorDescriptor -- assuming that the descriptor's system is ok
        if has_access(user, descriptor.location, 'staff', course_id):
            err_descriptor_class = ErrorDescriptor
        else:
            err_descriptor_class = NonStaffErrorDescriptor

        err_descriptor = err_descriptor_class.from_descriptor(
            descriptor,
            error_msg=exc_info_to_str(sys.exc_info())
        )

        # Make an error module
        return err_descriptor.xmodule(system)

    system.set('user_is_staff', has_access(user, descriptor.location, 'staff', course_id))
    _get_html = module.get_html

    if wrap_xmodule_display == True:
        _get_html = wrap_xmodule(module.get_html, module, 'xmodule_display.html')

    module.get_html = replace_static_urls(
        _get_html,
        getattr(descriptor, 'data_dir', None),
        course_namespace=module.location._replace(category=None, name=None))

    # Allow URLs of the form '/course/' refer to the root of multicourse directory
    #   hierarchy of this course
    module.get_html = replace_course_urls(module.get_html, course_id)

    if settings.MITX_FEATURES.get('DISPLAY_HISTOGRAMS_TO_STAFF'):
        if has_access(user, module, 'staff', course_id):
            module.get_html = add_histogram(module.get_html, module, user)

    return module
def combined_notifications(course, user):
    """
    Show notifications to a given user for a given course.  Get notifications from the cache if possible,
    or from the grading controller server if not.
    @param course: The course object for which we are getting notifications
    @param user: The user object for which we are getting notifications
    @return: A dictionary with boolean pending_grading (true if there is pending grading), img_path (for notification
    image), and response (actual response from grading controller server).
    """
    #Set up return values so that we can return them for error cases
    pending_grading = False
    img_path = ""
    notifications = {}
    notification_dict = {'pending_grading': pending_grading, 'img_path': img_path, 'response': notifications}

    #We don't want to show anonymous users anything.
    if not user.is_authenticated():
        return notification_dict

    #Initialize controller query service using our mock system
    controller_qs = ControllerQueryService(settings.OPEN_ENDED_GRADING_INTERFACE, render_to_string)
    student_id = unique_id_for_user(user)
    user_is_staff = has_access(user, 'staff', course)
    course_id = course.id
    notification_type = "combined"

    #See if we have a stored value in the cache
    success, notification_dict = get_value_from_cache(student_id, course_id, notification_type)
    if success:
        return notification_dict

    #Get the time of the last login of the user
    last_login = user.last_login
    last_time_viewed = last_login - datetime.timedelta(seconds=(NOTIFICATION_CACHE_TIME + 60))

    try:
        #Get the notifications from the grading controller
        notifications = controller_qs.check_combined_notifications(
            course.id,
            student_id,
            user_is_staff,
            last_time_viewed,
        )
        if notifications.get('success'):
            if (notifications.get('staff_needs_to_grade') or
                    notifications.get('student_needs_to_peer_grade')):
                pending_grading = True
    except:
        #Non catastrophic error, so no real action
        #This is a dev_facing_error
        log.exception(
            u"Problem with getting notifications from controller query service for course {0} user {1}.".format(
                course_id, student_id))

    if pending_grading:
        img_path = "/static/images/grading_notification.png"

    notification_dict = {'pending_grading': pending_grading, 'img_path': img_path, 'response': notifications}

    #Store the notifications in the cache
    set_value_in_cache(student_id, course_id, notification_type, notification_dict)

    return notification_dict
def get_module_for_descriptor_internal(
    user,
    descriptor,
    field_data_cache,
    course_id,
    track_function,
    xqueue_callback_url_prefix,
    position=None,
    wrap_xmodule_display=True,
    grade_bucket_type=None,
    static_asset_path="",
):
    """
    Actually implement get_module, without requiring a request.

    See get_module() docstring for further details.
    """

    # Short circuit--if the user shouldn't have access, bail without doing any work
    if not has_access(user, descriptor, "load", course_id):
        return None

    # Setup system context for module instance
    ajax_url = reverse(
        "modx_dispatch", kwargs=dict(course_id=course_id, location=descriptor.location.url(), dispatch="")
    )
    # Intended use is as {ajax_url}/{dispatch_command}, so get rid of the trailing slash.
    ajax_url = ajax_url.rstrip("/")

    def make_xqueue_callback(dispatch="score_update"):
        # Fully qualified callback URL for external queueing system
        relative_xqueue_callback_url = reverse(
            "xqueue_callback",
            kwargs=dict(course_id=course_id, userid=str(user.id), mod_id=descriptor.location.url(), dispatch=dispatch),
        )
        return xqueue_callback_url_prefix + relative_xqueue_callback_url

    # Default queuename is course-specific and is derived from the course that
    #   contains the current module.
    # TODO: Queuename should be derived from 'course_settings.json' of each course
    xqueue_default_queuename = descriptor.location.org + "-" + descriptor.location.course

    xqueue = {
        "interface": xqueue_interface,
        "construct_callback": make_xqueue_callback,
        "default_queuename": xqueue_default_queuename.replace(" ", "_"),
        "waittime": settings.XQUEUE_WAITTIME_BETWEEN_REQUESTS,
    }

    # This is a hacky way to pass settings to the combined open ended xmodule
    # It needs an S3 interface to upload images to S3
    # It needs the open ended grading interface in order to get peer grading to be done
    # this first checks to see if the descriptor is the correct one, and only sends settings if it is

    # Get descriptor metadata fields indicating needs for various settings
    needs_open_ended_interface = getattr(descriptor, "needs_open_ended_interface", False)
    needs_s3_interface = getattr(descriptor, "needs_s3_interface", False)

    # Initialize interfaces to None
    open_ended_grading_interface = None
    s3_interface = None

    # Create interfaces if needed
    if needs_open_ended_interface:
        open_ended_grading_interface = settings.OPEN_ENDED_GRADING_INTERFACE
        open_ended_grading_interface["mock_peer_grading"] = settings.MOCK_PEER_GRADING
        open_ended_grading_interface["mock_staff_grading"] = settings.MOCK_STAFF_GRADING
    if needs_s3_interface:
        s3_interface = {
            "access_key": getattr(settings, "AWS_ACCESS_KEY_ID", ""),
            "secret_access_key": getattr(settings, "AWS_SECRET_ACCESS_KEY", ""),
            "storage_bucket_name": getattr(settings, "AWS_STORAGE_BUCKET_NAME", ""),
        }

    def inner_get_module(descriptor):
        """
        Delegate to get_module_for_descriptor_internal() with all values except `descriptor` set.

        Because it does an access check, it may return None.
        """
        # TODO: fix this so that make_xqueue_callback uses the descriptor passed into
        # inner_get_module, not the parent's callback.  Add it as an argument....
        return get_module_for_descriptor_internal(
            user,
            descriptor,
            field_data_cache,
            course_id,
            track_function,
            make_xqueue_callback,
            position,
            wrap_xmodule_display,
            grade_bucket_type,
            static_asset_path,
        )

    def xblock_field_data(descriptor):
        student_data = DbModel(DjangoKeyValueStore(field_data_cache))
        return lms_field_data(descriptor._field_data, student_data)

    def publish(event):
        """A function that allows XModules to publish events. This only supports grade changes right now."""
        if event.get("event_name") != "grade":
            return

        # Construct the key for the module
        key = KeyValueStore.Key(
            scope=Scope.user_state, student_id=user.id, block_scope_id=descriptor.location, field_name="grade"
        )

        student_module = field_data_cache.find_or_create(key)
        # Update the grades
        student_module.grade = event.get("value")
        student_module.max_grade = event.get("max_value")
        # Save all changes to the underlying KeyValueStore
        student_module.save()

        # Bin score into range and increment stats
        score_bucket = get_score_bucket(student_module.grade, student_module.max_grade)
        org, course_num, run = course_id.split("/")

        tags = [
            "org:{0}".format(org),
            "course:{0}".format(course_num),
            "run:{0}".format(run),
            "score_bucket:{0}".format(score_bucket),
        ]

        if grade_bucket_type is not None:
            tags.append("type:%s" % grade_bucket_type)

        statsd.increment("lms.courseware.question_answered", tags=tags)

    # TODO (cpennington): When modules are shared between courses, the static
    # prefix is going to have to be specific to the module, not the directory
    # that the xml was loaded from

    system = ModuleSystem(
        track_function=track_function,
        render_template=render_to_string,
        ajax_url=ajax_url,
        xqueue=xqueue,
        # TODO (cpennington): Figure out how to share info between systems
        filestore=descriptor.system.resources_fs,
        get_module=inner_get_module,
        user=user,
        # TODO (cpennington): This should be removed when all html from
        # a module is coming through get_html and is therefore covered
        # by the replace_static_urls code below
        replace_urls=partial(
            static_replace.replace_static_urls,
            data_directory=getattr(descriptor, "data_dir", None),
            course_id=course_id,
            static_asset_path=static_asset_path or descriptor.static_asset_path,
        ),
        replace_course_urls=partial(static_replace.replace_course_urls, course_id=course_id),
        replace_jump_to_id_urls=partial(
            static_replace.replace_jump_to_id_urls,
            course_id=course_id,
            jump_to_id_base_url=reverse("jump_to_id", kwargs={"course_id": course_id, "module_id": ""}),
        ),
        node_path=settings.NODE_PATH,
        xblock_field_data=xblock_field_data,
        publish=publish,
        anonymous_student_id=unique_id_for_user(user),
        course_id=course_id,
        open_ended_grading_interface=open_ended_grading_interface,
        s3_interface=s3_interface,
        cache=cache,
        can_execute_unsafe_code=(lambda: can_execute_unsafe_code(course_id)),
        # TODO: When we merge the descriptor and module systems, we can stop reaching into the mixologist (cpennington)
        mixins=descriptor.system.mixologist._mixins,
    )

    # pass position specified in URL to module through ModuleSystem
    system.set("position", position)
    system.set("DEBUG", settings.DEBUG)
    if settings.MITX_FEATURES.get("ENABLE_PSYCHOMETRICS"):
        system.set(
            "psychometrics_handler",  # set callback for updating PsychometricsData
            make_psychometrics_data_update_handler(course_id, user, descriptor.location.url()),
        )

    try:
        module = descriptor.xmodule(system)
    except:
        log.exception("Error creating module from descriptor {0}".format(descriptor))

        # make an ErrorDescriptor -- assuming that the descriptor's system is ok
        if has_access(user, descriptor.location, "staff", course_id):
            err_descriptor_class = ErrorDescriptor
        else:
            err_descriptor_class = NonStaffErrorDescriptor

        err_descriptor = err_descriptor_class.from_descriptor(descriptor, error_msg=exc_info_to_str(sys.exc_info()))

        # Make an error module
        return err_descriptor.xmodule(system)

    system.set("user_is_staff", has_access(user, descriptor.location, "staff", course_id))
    _get_html = module.get_html

    if wrap_xmodule_display is True:
        _get_html = wrap_xmodule(module.get_html, module, "xmodule_display.html")

    module.get_html = replace_static_urls(
        _get_html,
        getattr(descriptor, "data_dir", None),
        course_id=course_id,
        static_asset_path=static_asset_path or descriptor.static_asset_path,
    )

    # Allow URLs of the form '/course/' refer to the root of multicourse directory
    #   hierarchy of this course
    module.get_html = replace_course_urls(module.get_html, course_id)

    # this will rewrite intra-courseware links
    # that use the shorthand /jump_to_id/<id>. This is very helpful
    # for studio authored courses (compared to the /course/... format) since it is
    # is durable with respect to moves and the author doesn't need to
    # know the hierarchy
    # NOTE: module_id is empty string here. The 'module_id' will get assigned in the replacement
    # function, we just need to specify something to get the reverse() to work
    module.get_html = replace_jump_to_id_urls(
        module.get_html, course_id, reverse("jump_to_id", kwargs={"course_id": course_id, "module_id": ""})
    )

    if settings.MITX_FEATURES.get("DISPLAY_HISTOGRAMS_TO_STAFF"):
        if has_access(user, module, "staff", course_id):
            module.get_html = add_histogram(module.get_html, module, user)

    # force the module to save after rendering
    module.get_html = save_module(module.get_html, module)
    return module
Exemplo n.º 47
0
def save_grade(request, course_id):
    """
    Save the grade and feedback for a submission, and, if all goes well, return
    the next thing to grade.

    Expects the following POST parameters:
    'score': int
    'feedback': string
    'submission_id': int

    Returns the same thing as get_next, except that additional error messages
    are possible if something goes wrong with saving the grade.
    """
    _check_access(request.user, course_id)

    if request.method != 'POST':
        raise Http404
    p = request.POST
    required = set([
        'score', 'feedback', 'submission_id', 'location', 'submission_flagged'
    ])
    skipped = 'skipped' in p
    #If the instructor has skipped grading the submission, then there will not be any rubric scores.
    #Only add in the rubric scores if the instructor has not skipped.
    if not skipped:
        required |= set(['rubric_scores[]'])
    actual = set(p.keys())
    missing = required - actual
    if len(missing) > 0:
        return _err_response('Missing required keys {0}'.format(
            ', '.join(missing)))

    grader_id = unique_id_for_user(request.user)

    location = p['location']

    try:
        result_json = staff_grading_service().save_grade(
            course_id, grader_id,
            p['submission_id'], p['score'], p['feedback'], skipped,
            p.getlist('rubric_scores[]'), p['submission_flagged'])
    except GradingServiceError:
        #This is a dev_facing_error
        log.exception(
            "Error saving grade in the staff grading interface in open ended grading.  Request: {0} Course ID: {1}"
            .format(request, course_id))
        #This is a staff_facing_error
        return _err_response(STAFF_ERROR_MESSAGE)

    try:
        result = json.loads(result_json)
    except ValueError:
        #This is a dev_facing_error
        log.exception(
            "save_grade returned broken json in the staff grading interface in open ended grading: {0}"
            .format(result_json))
        #This is a staff_facing_error
        return _err_response(STAFF_ERROR_MESSAGE)

    if not result.get('success', False):
        #This is a dev_facing_error
        log.warning(
            'Got success=False from staff grading service in open ended grading.  Response: {0}'
            .format(result_json))
        return _err_response(STAFF_ERROR_MESSAGE)

    # Ok, save_grade seemed to work.  Get the next submission to grade.
    return HttpResponse(_get_next(course_id, grader_id, location),
                        mimetype="application/json")
Exemplo n.º 48
0
def get_problem_list(request, course_id):
    """
    Get all the problems for the given course id

    Returns a json dict with the following keys:
        success: bool

        problem_list: a list containing json dicts with the following keys:
            each dict represents a different problem in the course

            location: the location of the problem

            problem_name: the name of the problem

            num_graded: the number of responses that have been graded

            num_pending: the number of responses that are sitting in the queue

            min_for_ml: the number of responses that need to be graded before
                the ml can be run

        'error': if success is False, will have an error message with more info.
    """
    assert(isinstance(course_id, basestring))
    course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
    _check_access(request.user, course_key)
    try:
        response = staff_grading_service().get_problem_list(course_key, unique_id_for_user(request.user))

        # If 'problem_list' is in the response, then we got a list of problems from the ORA server.
        # If it is not, then ORA could not find any problems.
        if 'problem_list' in response:
            problem_list = response['problem_list']
        else:
            problem_list = []
            # Make an error messages to reflect that we could not find anything to grade.
            response['error'] = _(
                u'Cannot find any open response problems in this course. '
                u'Have you submitted answers to any open response assessment questions? '
                u'If not, please do so and return to this page.'
            )
        valid_problem_list = []
        for i in xrange(0, len(problem_list)):
            # Needed to ensure that the 'location' key can be accessed.
            try:
                problem_list[i] = json.loads(problem_list[i])
            except Exception:
                pass
            if does_location_exist(course_key.make_usage_key_from_deprecated_string(problem_list[i]['location'])):
                valid_problem_list.append(problem_list[i])
        response['problem_list'] = valid_problem_list
        response = json.dumps(response)

        return HttpResponse(response,
                            mimetype="application/json")
    except GradingServiceError:
        #This is a dev_facing_error
        log.exception(
            "Error from staff grading service in open "
            "ended grading.  server url: {0}".format(staff_grading_service().url)
        )
        #This is a staff_facing_error
        return HttpResponse(json.dumps({'success': False,
                                        'error': STAFF_ERROR_MESSAGE}))
Exemplo n.º 49
0
def process_survey_link(survey_link, user):
    """
    If {UNIQUE_ID} appears in the link, replace it with a unique id for the user.
    Currently, this is sha1(user.username).  Otherwise, return survey_link.
    """
    return survey_link.format(UNIQUE_ID=unique_id_for_user(user))
Exemplo n.º 50
0
def student_problem_list(request, course_id):
    '''
    Show a student problem list to a student.  Fetch the list from the grading controller server, get some metadata,
    and then show it to the student.
    '''
    course = get_course_with_access(request.user, course_id, 'load')
    student_id = unique_id_for_user(request.user)

    # call problem list service
    success = False
    error_text = ""
    problem_list = []
    base_course_url = reverse('courses')
    list_to_remove = []

    try:
        #Get list of all open ended problems that the grading server knows about
        problem_list_json = controller_qs.get_grading_status_list(course_id, unique_id_for_user(request.user))
        problem_list_dict = json.loads(problem_list_json)
        success = problem_list_dict['success']
        if 'error' in problem_list_dict:
            error_text = problem_list_dict['error']
            problem_list = []
        else:
            problem_list = problem_list_dict['problem_list']

        #A list of problems to remove (problems that can't be found in the course)
        for i in xrange(0, len(problem_list)):
            try:
                #Try to load each problem in the courseware to get links to them
                problem_url_parts = search.path_to_location(modulestore(), course.id, problem_list[i]['location'])
            except ItemNotFoundError:
                #If the problem cannot be found at the location received from the grading controller server, it has been deleted by the course author.
                #Continue with the rest of the location to construct the list
                error_message = "Could not find module for course {0} at location {1}".format(course.id,
                                                                                              problem_list[i][
                                                                                                  'location'])
                log.error(error_message)
                #Mark the problem for removal from the list
                list_to_remove.append(i)
                continue
            problem_url = generate_problem_url(problem_url_parts, base_course_url)
            problem_list[i].update({'actual_url': problem_url})
            eta_available = problem_list[i]['eta_available']
            if isinstance(eta_available, basestring):
                eta_available = (eta_available.lower() == "true")

            eta_string = "N/A"
            if eta_available:
                try:
                    eta_string = convert_seconds_to_human_readable(int(problem_list[i]['eta']))
                except:
                    #This is a student_facing_error
                    eta_string = "Error getting ETA."
            problem_list[i].update({'eta_string': eta_string})

    except GradingServiceError:
        #This is a student_facing_error
        error_text = STUDENT_ERROR_MESSAGE
        #This is a dev facing error
        log.error("Problem contacting open ended grading service.")
        success = False
    # catch error if if the json loads fails
    except ValueError:
        #This is a student facing error
        error_text = STUDENT_ERROR_MESSAGE
        #This is a dev_facing_error
        log.error("Problem with results from external grading service for open ended.")
        success = False

    #Remove problems that cannot be found in the courseware from the list
    problem_list = [problem_list[i] for i in xrange(0, len(problem_list)) if i not in list_to_remove]
    ajax_url = _reverse_with_slash('open_ended_problems', course_id)

    return render_to_response('open_ended_problems/open_ended_problems.html', {
        'course': course,
        'course_id': course_id,
        'ajax_url': ajax_url,
        'success': success,
        'problem_list': problem_list,
        'error_text': error_text,
        # Checked above
        'staff_access': False, })
Exemplo n.º 51
0
def get_problem_list(request, course_id):
    """
    Get all the problems for the given course id

    Returns a json dict with the following keys:
        success: bool

        problem_list: a list containing json dicts with the following keys:
            each dict represents a different problem in the course

            location: the location of the problem

            problem_name: the name of the problem

            num_graded: the number of responses that have been graded

            num_pending: the number of responses that are sitting in the queue

            min_for_ml: the number of responses that need to be graded before
                the ml can be run

        'error': if success is False, will have an error message with more info.
    """
    _check_access(request.user, course_id)
    try:
        response = staff_grading_service().get_problem_list(
            course_id, unique_id_for_user(request.user))
        response = json.loads(response)

        # If 'problem_list' is in the response, then we got a list of problems from the ORA server.
        # If it is not, then ORA could not find any problems.
        if 'problem_list' in response:
            problem_list = response['problem_list']
        else:
            problem_list = []
            # Make an error messages to reflect that we could not find anything to grade.
            response['error'] = (
                "Cannot find any open response problems in this course.  "
                "Have you submitted answers to any open response assessment questions?  "
                "If not, please do so and return to this page.")
        valid_problem_list = []
        for i in xrange(0, len(problem_list)):
            # Needed to ensure that the 'location' key can be accessed.
            try:
                problem_list[i] = json.loads(problem_list[i])
            except Exception:
                pass
            if does_location_exist(course_id, problem_list[i]['location']):
                valid_problem_list.append(problem_list[i])
        response['problem_list'] = valid_problem_list
        response = json.dumps(response)

        return HttpResponse(response, mimetype="application/json")
    except GradingServiceError:
        #This is a dev_facing_error
        log.exception("Error from staff grading service in open "
                      "ended grading.  server url: {0}".format(
                          staff_grading_service().url))
        #This is a staff_facing_error
        return HttpResponse(
            json.dumps({
                'success': False,
                'error': STAFF_ERROR_MESSAGE
            }))
Exemplo n.º 52
0
def save_grade(request, course_id):
    """
    Save the grade and feedback for a submission, and, if all goes well, return
    the next thing to grade.

    Expects the following POST parameters:
    'score': int
    'feedback': string
    'submission_id': int

    Returns the same thing as get_next, except that additional error messages
    are possible if something goes wrong with saving the grade.
    """

    course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
    _check_access(request.user, course_key)

    if request.method != 'POST':
        raise Http404
    p = request.POST
    required = set(['score', 'feedback', 'submission_id', 'location', 'submission_flagged'])
    skipped = 'skipped' in p
    #If the instructor has skipped grading the submission, then there will not be any rubric scores.
    #Only add in the rubric scores if the instructor has not skipped.
    if not skipped:
        required.add('rubric_scores[]')
    actual = set(p.keys())
    missing = required - actual
    if len(missing) > 0:
        return _err_response('Missing required keys {0}'.format(
            ', '.join(missing)))

    success, message = check_feedback_length(p)
    if not success:
        return _err_response(message)

    grader_id = unique_id_for_user(request.user)

    location = course_key.make_usage_key_from_deprecated_string(p['location'])

    try:
        result = staff_grading_service().save_grade(course_key,
                                                    grader_id,
                                                    p['submission_id'],
                                                    p['score'],
                                                    p['feedback'],
                                                    skipped,
                                                    p.getlist('rubric_scores[]'),
                                                    p['submission_flagged'])
    except GradingServiceError:
        #This is a dev_facing_error
        log.exception(
            "Error saving grade in the staff grading interface in open ended grading.  Request: {0} Course ID: {1}".format(
                request, course_id))
        #This is a staff_facing_error
        return _err_response(STAFF_ERROR_MESSAGE)
    except ValueError:
        #This is a dev_facing_error
        log.exception(
            "save_grade returned broken json in the staff grading interface in open ended grading: {0}".format(
                result_json))
        #This is a staff_facing_error
        return _err_response(STAFF_ERROR_MESSAGE)

    if not result.get('success', False):
        #This is a dev_facing_error
        log.warning(
            'Got success=False from staff grading service in open ended grading.  Response: {0}'.format(result_json))
        return _err_response(STAFF_ERROR_MESSAGE)

    # Ok, save_grade seemed to work.  Get the next submission to grade.
    return HttpResponse(json.dumps(_get_next(course_id, grader_id, location)),
                        mimetype="application/json")