Пример #1
0
 def test_secret_key_changes(self):
     """Test that a new anonymous id is returned when the secret key changes."""
     CourseEnrollment.enroll(self.user, self.course.id)
     anonymous_id = anonymous_id_for_user(self.user, self.course.id)
     with override_settings(SECRET_KEY='some_new_and_totally_secret_key'):
         # Recreate user object to clear cached anonymous id.
         self.user = User.objects.get(pk=self.user.id)
         new_anonymous_id = anonymous_id_for_user(self.user, self.course.id)
         self.assertNotEqual(anonymous_id, new_anonymous_id)
         self.assertEqual(self.user, user_by_anonymous_id(anonymous_id))
         self.assertEqual(self.user, user_by_anonymous_id(new_anonymous_id))
Пример #2
0
 def test_secret_key_changes(self):
     """Test that a new anonymous id is returned when the secret key changes."""
     CourseEnrollment.enroll(self.user, self.course.id)
     anonymous_id = anonymous_id_for_user(self.user, self.course.id)
     with override_settings(SECRET_KEY='some_new_and_totally_secret_key'):
         # Recreate user object to clear cached anonymous id.
         self.user = User.objects.get(pk=self.user.id)
         new_anonymous_id = anonymous_id_for_user(self.user, self.course.id)
         self.assertNotEqual(anonymous_id, new_anonymous_id)
         self.assertEqual(self.user, user_by_anonymous_id(anonymous_id))
         self.assertEqual(self.user, user_by_anonymous_id(new_anonymous_id))
Пример #3
0
def submissions_score_set_handler(sender, **kwargs):  # pylint: disable=unused-argument
    """
    Consume the score_set signal defined in the Submissions API, and convert it
    to a SCORE_CHANGED signal defined in this module. Converts the unicode keys
    for user, course and item into the standard representation for the
    SCORE_CHANGED signal.

    This method expects that the kwargs dictionary will contain the following
    entries (See the definition of score_set):
      - 'points_possible': integer,
      - 'points_earned': integer,
      - 'anonymous_user_id': unicode,
      - 'course_id': unicode,
      - 'item_id': unicode
    """
    points_possible = kwargs['points_possible']
    points_earned = kwargs['points_earned']
    course_id = kwargs['course_id']
    usage_id = kwargs['item_id']
    user = user_by_anonymous_id(kwargs['anonymous_user_id'])

    # If any of the kwargs were missing, at least one of the following values
    # will be None.
    SCORE_CHANGED.send(sender=None,
                       points_possible=points_possible,
                       points_earned=points_earned,
                       user=user,
                       course_id=course_id,
                       usage_id=usage_id)
Пример #4
0
def submissions_score_set_handler(sender, **kwargs):  # pylint: disable=unused-argument
    """
    Consume the score_set signal defined in the Submissions API, and convert it
    to a PROBLEM_SCORE_CHANGED signal defined in this module. Converts the unicode keys
    for user, course and item into the standard representation for the
    PROBLEM_SCORE_CHANGED signal.

    This method expects that the kwargs dictionary will contain the following
    entries (See the definition of score_set):
      - 'points_possible': integer,
      - 'points_earned': integer,
      - 'anonymous_user_id': unicode,
      - 'course_id': unicode,
      - 'item_id': unicode
    """
    points_possible = kwargs['points_possible']
    points_earned = kwargs['points_earned']
    course_id = kwargs['course_id']
    usage_id = kwargs['item_id']
    user = user_by_anonymous_id(kwargs['anonymous_user_id'])
    if user is None:
        return

    PROBLEM_SCORE_CHANGED.send(
        sender=None,
        points_earned=points_earned,
        points_possible=points_possible,
        user_id=user.id,
        course_id=course_id,
        usage_id=usage_id)
Пример #5
0
def submissions_score_reset_handler(sender, **kwargs):  # pylint: disable=unused-argument
    """
    Consume the score_reset signal defined in the Submissions API, and convert
    it to a SCORE_CHANGED signal indicating that the score has been set to 0/0.
    Converts the unicode keys for user, course and item into the standard
    representation for the SCORE_CHANGED signal.

    This method expects that the kwargs dictionary will contain the following
    entries (See the definition of score_reset):
      - 'anonymous_user_id': unicode,
      - 'course_id': unicode,
      - 'item_id': unicode
    """
    course_id = kwargs['course_id']
    usage_id = kwargs['item_id']
    user = user_by_anonymous_id(kwargs['anonymous_user_id'])
    if user is None:
        return

    SCORE_CHANGED.send(sender=None,
                       points_earned=0,
                       points_possible=0,
                       user_id=user.id,
                       course_id=course_id,
                       usage_id=usage_id)
Пример #6
0
def _get_username(submission, user_id):
    """
    Return username of student who provided `submission`.
    """
    # If the student ID key doesn't exist, we're dealing with a single student and know the ID already.
    student_id = submission.get("student_id", user_id)
    return user_by_anonymous_id(student_id).username
Пример #7
0
        def get_student_data():
            # pylint: disable=no-member
            """
            Returns a dict of student assignment information along with
            annotated file name, student id and module id, this
            information will be used on grading screen
            """
            # Submissions doesn't have API for this, just use model directly. 直接操作model ,这一块是和成绩挂钩的关键
            students = SubmissionsStudent.objects.filter(
                course_id=self.course_id,
                item_id=self.block_id)
            for student in students:
                submission = self.get_submission(student.student_id)
                if not submission:
                    continue
                user = user_by_anonymous_id(student.student_id)
                module, created = StudentModule.objects.get_or_create(
                    course_id=self.course_id,
                    module_state_key=self.location,
                    student=user,
                    defaults={
                        'state': '{}',
                        'module_type': self.category,
                    })
                if created:
                    log.info(
                        "Init for course:%s module:%s student:%s  ",
                        module.course_id,
                        module.module_state_key,
                        module.student.username
                    )

                state = json.loads(module.state)
                score = self.get_score(student.student_id)
                approved = score is not None
                if score is None:
                    score = state.get('staff_score')
                    needs_approval = score is not None
                else:
                    needs_approval = False
                instructor = self.is_instructor()
                yield {
                    'module_id': module.id,
                    'student_id': student.student_id,
                    'submission_id': submission['uuid'],
                    'username': module.student.username,
                    #'student_answer': self.student_answer, #?? maybe error
                    'student_answer': submission['answer']["student_answer"],
                    'fullname': module.student.profile.name,
                    'filename': submission['answer']["filename"],
                    'timestamp': submission['created_at'].strftime(
                        DateTime.DATETIME_FORMAT
                    ),
                    'score': score,
                    'approved': approved,
                    'needs_approval': instructor and needs_approval,
                    'may_grade': instructor or not approved,
                    'annotated': state.get("annotated_filename"),
                    'comment': state.get("comment", ''),
                }
Пример #8
0
def submissions_score_reset_handler(sender, **kwargs):  # pylint: disable=unused-argument
    """
    Consume the score_reset signal defined in the Submissions API, and convert
    it to a SCORE_CHANGED signal indicating that the score has been set to 0/0.
    Converts the unicode keys for user, course and item into the standard
    representation for the SCORE_CHANGED signal.

    This method expects that the kwargs dictionary will contain the following
    entries (See the definition of score_reset):
      - 'anonymous_user_id': unicode,
      - 'course_id': unicode,
      - 'item_id': unicode
    """
    course_id = kwargs.get('course_id', None)
    usage_id = kwargs.get('item_id', None)
    user = None
    if 'anonymous_user_id' in kwargs:
        user = user_by_anonymous_id(kwargs.get('anonymous_user_id'))

    # If any of the kwargs were missing, at least one of the following values
    # will be None.
    if all((user, course_id, usage_id)):
        SCORE_CHANGED.send(
            sender=None,
            points_possible=0,
            points_earned=0,
            user=user,
            course_id=course_id,
            usage_id=usage_id
        )
    else:
        log.exception(
            u"Failed to process score_reset signal from Submissions API. "
            "user: %s, course_id: %s, usage_id: %s", user, course_id, usage_id
        )
Пример #9
0
def submissions_score_reset_handler(sender, **kwargs):  # pylint: disable=unused-argument
    """
    Consume the score_reset signal defined in the Submissions API, and convert
    it to a PROBLEM_WEIGHTED_SCORE_CHANGED signal indicating that the score
    has been set to 0/0. Converts the unicode keys for user, course and item
    into the standard representation for the PROBLEM_WEIGHTED_SCORE_CHANGED signal.

    This method expects that the kwargs dictionary will contain the following
    entries (See the definition of score_reset):
      - 'anonymous_user_id': unicode,
      - 'course_id': unicode,
      - 'item_id': unicode
    """
    course_id = kwargs['course_id']
    usage_id = kwargs['item_id']
    user = user_by_anonymous_id(kwargs['anonymous_user_id'])
    if user is None:
        return

    PROBLEM_WEIGHTED_SCORE_CHANGED.send(
        sender=None,
        weighted_earned=0,
        weighted_possible=0,
        user_id=user.id,
        anonymous_user_id=kwargs['anonymous_user_id'],
        course_id=course_id,
        usage_id=usage_id,
        modified=kwargs['created_at'],
        score_deleted=True,
        score_db_table=ScoreDatabaseTableEnum.submissions,
    )
Пример #10
0
    def store_student_response(self):
        """
        Submit a student answer to the answer pool by appending the given
        answer to the end of the list.
        """
        # if the answer is wrong, do not display it
        # if self.score != Credit.full.value:
        #     return

        student_id = self.get_student_id()

        # remove any previous answers the student submitted

        for index, response in enumerate(self.displayable_answers):
            if response['student_id'] == student_id:
                del self.displayable_answers[index]
                break

        student_email = user_by_anonymous_id(student_id)

        self.displayable_answers.append({
            'student_id': student_id,
            'student_email': student_email.email,
            'answer': self.student_answer,
        })
Пример #11
0
 def test_roundtrip_with_unicode_course_id(self):
     course2 = CourseFactory.create(display_name=u"Omega Course Ω")
     CourseEnrollment.enroll(self.user, course2.id)
     anonymous_id = anonymous_id_for_user(self.user, course2.id)
     real_user = user_by_anonymous_id(anonymous_id)
     self.assertEqual(self.user, real_user)
     self.assertEqual(anonymous_id, anonymous_id_for_user(self.user, course2.id, save=False))
Пример #12
0
def _get_student_submissions(block_id, course_id, locator):
    """
    Returns valid submission file paths with the username of the student that submitted them.

    Args:
        course_id (unicode): edx course id
        block_id (unicode): edx block id
        locator (BlockUsageLocator): BlockUsageLocator for the sga module

    Returns:
        list(tuple): A list of 2-element tuples - (student username, submission file path)
    """
    submissions = submissions_api.get_all_submissions(
        course_id,
        block_id,
        ITEM_TYPE
    )
    return [
        (
            user_by_anonymous_id(submission['student_id']).username,
            get_file_storage_path(
                locator,
                submission['answer']['sha1'],
                submission['answer']['filename']
            )
        )
        for submission in submissions if submission['answer']
    ]
Пример #13
0
def submissions_score_reset_handler(sender, **kwargs):  # pylint: disable=unused-argument
    """
    Consume the score_reset signal defined in the Submissions API, and convert
    it to a PROBLEM_WEIGHTED_SCORE_CHANGED signal indicating that the score
    has been set to 0/0. Converts the unicode keys for user, course and item
    into the standard representation for the PROBLEM_WEIGHTED_SCORE_CHANGED signal.

    This method expects that the kwargs dictionary will contain the following
    entries (See the definition of score_reset):
      - 'anonymous_user_id': unicode,
      - 'course_id': unicode,
      - 'item_id': unicode
    """
    course_id = kwargs['course_id']
    usage_id = kwargs['item_id']
    user = user_by_anonymous_id(kwargs['anonymous_user_id'])
    if user is None:
        return

    PROBLEM_WEIGHTED_SCORE_CHANGED.send(
        sender=None,
        weighted_earned=0,
        weighted_possible=0,
        user_id=user.id,
        anonymous_user_id=kwargs['anonymous_user_id'],
        course_id=course_id,
        usage_id=usage_id,
        modified=kwargs['created_at'],
        score_deleted=True,
        score_db_table=ScoreDatabaseTableEnum.submissions,
    )
Пример #14
0
def submissions_score_set_handler(sender, **kwargs):  # pylint: disable=unused-argument
    """
    Consume the score_set signal defined in the Submissions API, and convert it
    to a PROBLEM_WEIGHTED_SCORE_CHANGED signal defined in this module. Converts the
    unicode keys for user, course and item into the standard representation for the
    PROBLEM_WEIGHTED_SCORE_CHANGED signal.

    This method expects that the kwargs dictionary will contain the following
    entries (See the definition of score_set):
      - 'points_possible': integer,
      - 'points_earned': integer,
      - 'anonymous_user_id': unicode,
      - 'course_id': unicode,
      - 'item_id': unicode
    """
    points_possible = kwargs['points_possible']
    points_earned = kwargs['points_earned']
    course_id = kwargs['course_id']
    usage_id = kwargs['item_id']
    user = user_by_anonymous_id(kwargs['anonymous_user_id'])
    if user is None:
        return

    PROBLEM_WEIGHTED_SCORE_CHANGED.send(sender=None,
                                        weighted_earned=points_earned,
                                        weighted_possible=points_possible,
                                        user_id=user.id,
                                        course_id=course_id,
                                        usage_id=usage_id)
Пример #15
0
def submissions_score_reset_handler(sender, **kwargs):  # pylint: disable=unused-argument
    """
    Consume the score_reset signal defined in the Submissions API, and convert
    it to a SCORE_CHANGED signal indicating that the score has been set to 0/0.
    Converts the unicode keys for user, course and item into the standard
    representation for the SCORE_CHANGED signal.

    This method expects that the kwargs dictionary will contain the following
    entries (See the definition of score_reset):
      - 'anonymous_user_id': unicode,
      - 'course_id': unicode,
      - 'item_id': unicode
    """
    course_id = kwargs.get('course_id', None)
    usage_id = kwargs.get('item_id', None)
    user = None
    if 'anonymous_user_id' in kwargs:
        user = user_by_anonymous_id(kwargs.get('anonymous_user_id'))

    # If any of the kwargs were missing, at least one of the following values
    # will be None.
    if all((user, course_id, usage_id)):
        SCORE_CHANGED.send(sender=None,
                           points_possible=0,
                           points_earned=0,
                           user=user,
                           course_id=course_id,
                           usage_id=usage_id)
    else:
        log.exception(
            u"Failed to process score_reset signal from Submissions API. "
            "user: %s, course_id: %s, usage_id: %s", user, course_id, usage_id)
Пример #16
0
def submissions_score_reset_handler(sender, **kwargs):  # pylint: disable=unused-argument
    """
    Consume the score_reset signal defined in the Submissions API, and convert
    it to a SCORE_CHANGED signal indicating that the score has been set to 0/0.
    Converts the unicode keys for user, course and item into the standard
    representation for the SCORE_CHANGED signal.

    This method expects that the kwargs dictionary will contain the following
    entries (See the definition of score_reset):
      - 'anonymous_user_id': unicode,
      - 'course_id': unicode,
      - 'item_id': unicode
    """
    course_id = kwargs['course_id']
    usage_id = kwargs['item_id']
    user = user_by_anonymous_id(kwargs['anonymous_user_id'])

    # If any of the kwargs were missing, at least one of the following values
    # will be None.
    SCORE_CHANGED.send(
        sender=None,
        points_possible=0,
        points_earned=0,
        user=user,
        course_id=course_id,
        usage_id=usage_id
    )
Пример #17
0
def _get_student_submissions(block_id, course_id, locator):
    """
    Returns valid submission file paths with the username of the student that submitted them.

    Args:
        course_id (unicode): edx course id
        block_id (unicode): edx block id
        locator (BlockUsageLocator): BlockUsageLocator for the sga module

    Returns:
        list(tuple): A list of 2-element tuples - (student username, submission file path)
    """
    submissions = submissions_api.get_all_submissions(
        course_id,
        block_id,
        ITEM_TYPE
    )
    return [
        (
            user_by_anonymous_id(submission['student_id']).username,
            get_file_storage_path(
                locator,
                submission['answer']['sha1'],
                submission['answer']['filename']
            )
        )
        for submission in submissions if submission['answer']
    ]
Пример #18
0
def _get_username(submission, user_id):
    """
    Return username of student who provided `submission`.
    """
    # If the student ID key doesn't exist, we're dealing with a single student and know the ID already.
    student_id = submission.get('student_id', user_id)
    return user_by_anonymous_id(student_id).username
Пример #19
0
 def test_roundtrip_with_unicode_course_id(self):
     course2 = CourseFactory.create(display_name=u"Omega Course Ω")
     CourseEnrollment.enroll(self.user, course2.id)
     anonymous_id = anonymous_id_for_user(self.user, course2.id)
     real_user = user_by_anonymous_id(anonymous_id)
     self.assertEqual(self.user, real_user)
     self.assertEqual(anonymous_id, anonymous_id_for_user(self.user, course2.id, save=False))
Пример #20
0
 def test_roundtrip_for_logged_user(self):
     CourseEnrollment.enroll(self.user, self.course.id)
     anonymous_id = anonymous_id_for_user(self.user, self.course.id)
     real_user = user_by_anonymous_id(anonymous_id)
     self.assertEqual(self.user, real_user)
     self.assertEqual(
         anonymous_id,
         anonymous_id_for_user(self.user, self.course.id, save=False))
Пример #21
0
        def get_student_data():
            # pylint: disable=no-member
            """
            Returns a dict of student assignment information along with
            annotated file name, student id and module id, this
            information will be used on grading screen
            """
            # Submissions doesn't have API for this, just use model directly.
            students = SubmissionsStudent.objects.filter(
                course_id=self.course_id,
                item_id=self.block_id)
            for student in students:
                submission = self.get_submission(student.student_id)
                if not submission:
                    continue
                user = user_by_anonymous_id(student.student_id)
                module, created = StudentModule.objects.get_or_create(
                    course_id=self.course_id,
                    module_state_key=self.location,
                    student=user,
                    defaults={
                        'state': '{}',
                        'module_type': self.category,
                    })
                if created:
                    log.info(
                        "Init for course:%s module:%s student:%s  ",
                        module.course_id,
                        module.module_state_key,
                        module.student.username
                    )

                state = json.loads(module.state)
                score = self.get_score(student.student_id)
                approved = score is not None
                if score is None:
                    score = state.get('staff_score')
                    needs_approval = score is not None
                else:
                    needs_approval = False
                instructor = self.is_instructor()
                yield {
                    'module_id': module.id,
                    'student_id': student.student_id,
                    'submission_id': submission['uuid'],
                    'username': module.student.username,
                    'fullname': module.student.profile.name,
                    'filename': submission['answer']["filename"],
                    'timestamp': submission['created_at'].strftime(
                        DateTime.DATETIME_FORMAT
                    ),
                    'score': score,
                    'approved': approved,
                    'needs_approval': instructor and needs_approval,
                    'may_grade': instructor or not approved,
                    'annotated': state.get("annotated_filename"),
                    'comment': state.get("comment", ''),
                }
Пример #22
0
 def filter_assigments_by_team_members(self, assignments, members):
     """This method compares the team's users with an assigments' list"""
     for member in members:
         user = get_user_by_username_or_email(member["user"]["username"])
         for assignment in assignments:
             if user == user_by_anonymous_id(assignment["student_id"]):
                 assignment["profile_image_url"] = self._user_image_url(
                     user)
                 yield assignment
Пример #23
0
 def get_student_data():
     # pylint: disable=no-member
     """
     Returns a dict of student assignment information along with
     annotated file name, student id and module id, this
     information will be used on grading screen
     """
     # Submissions doesn't have API for this, just use model directly.
     students = SubmissionsStudent.objects.filter(
         course_id=self.course_id, item_id=self.block_id)
     for student in students:
         submission = self.get_submission(student.student_id)
         if not submission:
             continue
         user = user_by_anonymous_id(student.student_id)
         student_module = self.get_or_create_student_module(user)
         state = json.loads(student_module.state)
         score = self.get_score(student.student_id)
         approved = score is not None
         if score is None:
             score = state.get('staff_score')
             needs_approval = score is not None
         else:
             needs_approval = False
         instructor = self.is_instructor()
         yield {
             'module_id':
             student_module.id,
             'student_id':
             student.student_id,
             'submission_id':
             submission['uuid'],
             'username':
             student_module.student.username,
             'fullname':
             student_module.student.profile.name,
             'answer':
             submission['answer']["text"],
             'timestamp':
             submission['created_at'].strftime(
                 DateTime.DATETIME_FORMAT),
             'score':
             score,
             'approved':
             approved,
             'needs_approval':
             instructor and needs_approval,
             'may_grade':
             instructor or not approved,
             'annotated':
             force_text(state.get("annotated_filename", '')),
             'comment':
             force_text(state.get("comment", '')),
             'finalized':
             is_finalized_submission(submission_data=submission)
         }
Пример #24
0
 def get_student_data():
     # Submissions doesn't have API for this, just use model directly
     students = SubmissionsStudent.objects.filter(
         course_id=self.course_id, item_id=self.block_id)
     for student in students:
         submission = self.get_submission(student.student_id)
         if not submission:
             continue
         user = user_by_anonymous_id(student.student_id)
         module, _ = StudentModule.objects.get_or_create(
             course_id=self.course_id,
             module_state_key=self.location,
             student=user,
             defaults={
                 'state': '{}',
                 'module_type': self.category,
             })
         state = json.loads(module.state)
         score = self.get_score(student.student_id)
         approved = score is not None
         if score is None:
             score = state.get('staff_score')
             needs_approval = score is not None
         else:
             needs_approval = False
         instructor = self.is_instructor()
         yield {
             'module_id':
             module.id,
             'student_id':
             student.student_id,
             'submission_id':
             submission['uuid'],
             'username':
             module.student.username,
             'fullname':
             module.student.profile.name,
             'filename':
             submission['answer']["filename"],
             'timestamp':
             submission['created_at'].strftime(
                 DateTime.DATETIME_FORMAT),
             'score':
             score,
             'approved':
             approved,
             'needs_approval':
             instructor and needs_approval,
             'may_grade':
             instructor or not approved,
             'annotated':
             state.get("annotated_filename"),
             'comment':
             state.get("comment", ''),
         }
Пример #25
0
 def _get_graded_students(self):
     """
     """
     students = StudentItem.objects.filter(course_id=self.course_id,
                                           item_id=str(self.location))
     result = []
     for student in students:
         user = user_by_anonymous_id(student.student_id)
         if self.get_score(user):
             result.append(user)
     return result
Пример #26
0
    def handle(self, *args, **options):
        if 'modified_start' not in options:
            raise CommandError('modified_start must be provided.')

        if 'modified_end' not in options:
            raise CommandError('modified_end must be provided.')

        modified_start = utc.localize(
            datetime.strptime(options['modified_start'], DATE_FORMAT))
        modified_end = utc.localize(
            datetime.strptime(options['modified_end'], DATE_FORMAT))
        event_transaction_id = create_new_event_transaction_id()
        set_event_transaction_type(PROBLEM_SUBMITTED_EVENT_TYPE)
        kwargs = {
            'modified__range': (modified_start, modified_end),
            'module_type': 'problem'
        }
        for record in StudentModule.objects.filter(**kwargs):
            if not record.course_id.is_course:
                # This is not a course, so we don't store subsection grades for it.
                continue
            task_args = {
                "user_id": record.student_id,
                "course_id": six.text_type(record.course_id),
                "usage_id": six.text_type(record.module_state_key),
                "only_if_higher": False,
                "expected_modified_time": to_timestamp(record.modified),
                "score_deleted": False,
                "event_transaction_id": six.text_type(event_transaction_id),
                "event_transaction_type": PROBLEM_SUBMITTED_EVENT_TYPE,
                "score_db_table":
                ScoreDatabaseTableEnum.courseware_student_module,
            }
            recalculate_subsection_grade_v3.apply_async(kwargs=task_args)

        kwargs = {'created_at__range': (modified_start, modified_end)}
        for record in Submission.objects.filter(**kwargs):
            if not record.student_item.course_id.is_course:
                # This is not a course, so ignore it
                continue
            task_args = {
                "user_id":
                user_by_anonymous_id(record.student_item.student_id).id,
                "anonymous_user_id": record.student_item.student_id,
                "course_id": six.text_type(record.student_item.course_id),
                "usage_id": six.text_type(record.student_item.item_id),
                "only_if_higher": False,
                "expected_modified_time": to_timestamp(record.created_at),
                "score_deleted": False,
                "event_transaction_id": six.text_type(event_transaction_id),
                "event_transaction_type": PROBLEM_SUBMITTED_EVENT_TYPE,
                "score_db_table": ScoreDatabaseTableEnum.submissions,
            }
            recalculate_subsection_grade_v3.apply_async(kwargs=task_args)
Пример #27
0
def _get_user_info(submission, user_id):
    """
    Return a (username, user id, user email) tuple for the student who provided `submission`.

    If the anonymous ID of the submission can't be resolved into a user,
    (student ID, 'N/A', 'N/A') is returned
    """
    # If the student ID key doesn't exist, we're dealing with a single student and know the ID already.
    student_id = submission.get('student_id', user_id)
    user = user_by_anonymous_id(student_id)
    if user is None:
        return (student_id, 'N/A', 'N/A')
    return (user.username, user.id, user.email)
Пример #28
0
def _get_username(submission, user_id):
    """
    Return username of student who provided `submission`.

    If the anonymous id of the submission can't be resolved into a username, the anonymous
    id is returned.
    """
    # If the student ID key doesn't exist, we're dealing with a single student and know the ID already.
    student_id = submission.get('student_id', user_id)
    user = user_by_anonymous_id(student_id)
    if user is None:
        return student_id
    return user.username
Пример #29
0
    def provide_context(self, context=None):
        """
        Build a context dictionary to render the student view
        """
        if self.get_student_id() != "student":
            user_is_admin = self.is_course_staff(
                user_by_anonymous_id(self.get_student_id()), self.course_id)
        else:
            user_is_admin = True

        context = context or {}
        context = dict(context)
        context.update({
            'display_name':
            self.display_name,
            'indicator_class':
            self._get_indicator_class(),
            'nodisplay_class':
            self._get_nodisplay_class(),
            'problem_progress':
            self._get_problem_progress(),
            'prompt':
            self.prompt,
            'student_answer':
            self.student_answer,
            'is_past_due':
            self.is_past_due(),
            'used_attempts_feedback':
            self._get_used_attempts_feedback(),
            'visibility_class':
            self._get_indicator_visibility_class(),
            'word_count_message':
            self._get_word_count_message(),
            'display_other_responses':
            self.display_other_student_responses,
            'other_responses':
            self.get_other_answers(),
            'user_is_admin':
            user_is_admin,
            'user_alert':
            '',
            'submitted_message':
            '',
        })
        return context
Пример #30
0
 def get_student_data():
     # Submissions doesn't have API for this, just use model directly
     students = SubmissionsStudent.objects.filter(
         course_id=self.course_id,
         item_id=self.block_id)
     for student in students:
         submission = self.get_submission(student.student_id)
         if not submission:
             continue
         user = user_by_anonymous_id(student.student_id)
         module, _ = StudentModule.objects.get_or_create(
             course_id=self.course_id,
             module_state_key=self.location,
             student=user,
             defaults={
                 'state': '{}',
                 'module_type': self.category,
             })
         state = json.loads(module.state)
         score = self.get_score(student.student_id)
         approved = score is not None
         if score is None:
             score = state.get('staff_score')
             needs_approval = score is not None
         else:
             needs_approval = False
         instructor = self.is_instructor()
         yield {
             'module_id': module.id,
             'student_id': student.student_id,
             'submission_id': submission['uuid'],
             'username': module.student.username,
             'fullname': module.student.profile.name,
             'filename': submission['answer']["filename"],
             'timestamp': submission['created_at'].strftime(
                 DateTime.DATETIME_FORMAT
             ),
             'score': score,
             'approved': approved,
             'needs_approval': instructor and needs_approval,
             'may_grade': instructor or not approved,
             'annotated': state.get("annotated_filename"),
             'comment': state.get("comment", ''),
         }
Пример #31
0
 def get_student_data():
     # pylint: disable=no-member
     """
     Returns a dict of student assignment information along with
     annotated file name, student id and module id, this
     information will be used on grading screen
     """
     # Submissions doesn't have API for this, just use model directly.
     students = SubmissionsStudent.objects.filter(
         course_id=self.course_id,
         item_id=self.block_id)
     for student in students:
         submission = self.get_submission(student.student_id)
         if not submission:
             continue
         user = user_by_anonymous_id(student.student_id)
         student_module = self.get_or_create_student_module(user)
         state = json.loads(student_module.state)
         score = self.get_score(student.student_id)
         approved = score is not None
         if score is None:
             score = state.get('staff_score')
             needs_approval = score is not None
         else:
             needs_approval = False
         instructor = self.is_instructor()
         yield {
             'module_id': student_module.id,
             'student_id': student.student_id,
             'submission_id': submission['uuid'],
             'username': student_module.student.username,
             'fullname': student_module.student.profile.name,
             'filename': submission['answer']["filename"],
             'timestamp': submission['created_at'].strftime(
                 DateTime.DATETIME_FORMAT
             ),
             'score': score,
             'approved': approved,
             'needs_approval': instructor and needs_approval,
             'may_grade': instructor or not approved,
             'annotated': force_text(state.get("annotated_filename", '')),
             'comment': force_text(state.get("comment", '')),
             'finalized': is_finalized_submission(submission_data=submission)
         }
Пример #32
0
    def get_encoded_data(self):
        """
        Collect all data needed and encode it
        Returns: string

        """
        if self.is_course_staff():
            return ""
        user = user_by_anonymous_id(self.runtime.anonymous_student_id)
        data = {
            'course_id': str(self.course_id),
            'username': user.username,
            'email': user.email,
            'fullname': "%s %s" % (user.first_name, user.last_name)

        }
        row = json.dumps(data)
        encoded = AESCipher(settings.FEATURES['REDDIT_SECRET_KEY']).encrypt(row)
        return "?data=" + urllib.quote(encoded)
    def handle(self, *args, **options):
        if 'modified_start' not in options:
            raise CommandError('modified_start must be provided.')

        if 'modified_end' not in options:
            raise CommandError('modified_end must be provided.')

        modified_start = utc.localize(datetime.strptime(options['modified_start'], DATE_FORMAT))
        modified_end = utc.localize(datetime.strptime(options['modified_end'], DATE_FORMAT))
        event_transaction_id = create_new_event_transaction_id()
        set_event_transaction_type(PROBLEM_SUBMITTED_EVENT_TYPE)
        kwargs = {'modified__range': (modified_start, modified_end), 'module_type': 'problem'}
        for record in StudentModule.objects.filter(**kwargs):
            task_args = {
                "user_id": record.student_id,
                "course_id": unicode(record.course_id),
                "usage_id": unicode(record.module_state_key),
                "only_if_higher": False,
                "expected_modified_time": to_timestamp(record.modified),
                "score_deleted": False,
                "event_transaction_id": unicode(event_transaction_id),
                "event_transaction_type": PROBLEM_SUBMITTED_EVENT_TYPE,
                "score_db_table": ScoreDatabaseTableEnum.courseware_student_module,
            }
            recalculate_subsection_grade_v3.apply_async(kwargs=task_args)

        kwargs = {'created_at__range': (modified_start, modified_end)}
        for record in Submission.objects.filter(**kwargs):
            task_args = {
                "user_id": user_by_anonymous_id(record.student_item.student_id).id,
                "anonymous_user_id": record.student_item.student_id,
                "course_id": unicode(record.student_item.course_id),
                "usage_id": unicode(record.student_item.item_id),
                "only_if_higher": False,
                "expected_modified_time": to_timestamp(record.created_at),
                "score_deleted": False,
                "event_transaction_id": unicode(event_transaction_id),
                "event_transaction_type": PROBLEM_SUBMITTED_EVENT_TYPE,
                "score_db_table": ScoreDatabaseTableEnum.submissions,
            }
            recalculate_subsection_grade_v3.apply_async(kwargs=task_args)
 def post_comment(self, data, suffix=''):
     #IT MIGHT BE BETTER TO MAINTAIN THE PRIMARY KEYS than rely on SQL auto increment since it is hard to get back the primary key.
     ajax_comment = data.get('comment')
     """
     The ajax supplied comment may have ' (apostrophe/single quote) embedded in it. These must be escaped before
     being put into the SQL query (which itself relies on single quotes when inserting strings).
     """
     safe_comment = ""
     self.xblock_login = self.student_id()
     current_user = user_by_anonymous_id(self.xblock_login)
     for char in ajax_comment:
         if (char != "'"):
             safe_comment += char
         else:
             safe_comment +="\\'" #Escaping the embedded single quote using a single \. We use \\ to escape it in python as well
             #ALSO CHECK DOUBLE QUOTES AND WILDCARD CHARACTER CASES!!!
     insert_query = ("INSERT INTO discussion_table (thread_id, user_id, user_name, comment, parent_id) VALUES (2, '" + self.xblock_login + "', '" + current_user.username + "', '" + safe_comment + "', -1)")
     ret_val = self.exec_query(insert_query,"Inserting user comment")
     if(ret_val == 0):
         return {'update_status': "Success"}
     else:
         return {'update_status': "Failure"}
Пример #35
0
    def student_state(self):
        """
        Returns a JSON serializable representation of student's state for
        rendering in client view.
        """
        if self.annotated_sha1:
            annotated = {"filename": self.annotated_filename}
        else:
            annotated = None
        if self.xmodule_runtime.anonymous_student_id:
            user = user_by_anonymous_id(
                self.xmodule_runtime.anonymous_student_id)
        score = 0
        if user:
            student_record, created = StudentModule.objects.get_or_create(
                course_id=self.course_id,
                module_state_key=self.location,
                student=user,
                defaults={
                    'state': '{}',
                    'module_type': self.category,
                    'grade': 0,
                    'max_grade': self.max_score()
                })
            score = student_record.grade
        if score is not None:
            graded = {'score': score, 'comment': self.comment}
        else:
            graded = None

        return {
            "display_name": self.display_name,
            "annotated": annotated,
            "graded": graded,
            "max_score": self.max_score(),
            "upload_allowed": self.upload_allowed(),
        }
Пример #36
0
def submissions_score_set_handler(sender, **kwargs):  # pylint: disable=unused-argument
    """
    Consume the score_set signal defined in the Submissions API, and convert it
    to a PROBLEM_WEIGHTED_SCORE_CHANGED signal defined in this module. Converts the
    unicode keys for user, course and item into the standard representation for the
    PROBLEM_WEIGHTED_SCORE_CHANGED signal.

    This method expects that the kwargs dictionary will contain the following
    entries (See the definition of score_set):
      - 'points_possible': integer,
      - 'points_earned': integer,
      - 'anonymous_user_id': unicode,
      - 'course_id': unicode,
      - 'item_id': unicode
    """
    points_possible = kwargs['points_possible']
    points_earned = kwargs['points_earned']
    course_id = kwargs['course_id']
    usage_id = kwargs['item_id']
    user = user_by_anonymous_id(kwargs['anonymous_user_id'])
    if user is None:
        return
    if points_possible == 0:
        # This scenario is known to not succeed, see TNL-6559 for details.
        return

    PROBLEM_WEIGHTED_SCORE_CHANGED.send(
        sender=None,
        weighted_earned=points_earned,
        weighted_possible=points_possible,
        user_id=user.id,
        anonymous_user_id=kwargs['anonymous_user_id'],
        course_id=course_id,
        usage_id=usage_id,
        modified=kwargs['created_at'],
        score_db_table=ScoreDatabaseTableEnum.submissions,
    )
Пример #37
0
 def test_roundtrip_for_logged_user(self):
     CourseEnrollment.enroll(self.user, self.course.id)
     anonymous_id = anonymous_id_for_user(self.user, self.course.id)
     real_user = user_by_anonymous_id(anonymous_id)
     self.assertEqual(self.user, real_user)
     self.assertEqual(anonymous_id, anonymous_id_for_user(self.user, self.course.id, save=False))
Пример #38
0
 def test_for_unregistered_user(self):  # same path as for logged out user
     self.assertEqual(None, anonymous_id_for_user(AnonymousUser(), self.course.id))
     self.assertIsNone(user_by_anonymous_id(None))
Пример #39
0
 def test_for_unregistered_user(self):  # same path as for logged out user
     self.assertEqual(None, anonymous_id_for_user(AnonymousUser(), self.course.id))
     self.assertIsNone(user_by_anonymous_id(None))
Пример #40
0
    def student_view(self, context):
        """
        Create a fragment used to display the XBlock to a student.
        `context` is a dictionary used to configure the display (unused).

        Returns a `Fragment` object specifying the HTML, CSS, and JavaScript
        to display.
        """

        attempts = None
        max_attempts = None
        score = None
        passing = False
        is_attempted = False

        # On first adding the block, the studio calls student_view instead of
        # author_view, which causes problems. Force call of the author view.
        if getattr(self.runtime, 'is_author_mode', False):
            return self.author_view()

        if EDX_FOUND:

            total_correct, total_possible = 0, 0

            target_block_id = self.graded_target_id
            course_id = target_block_id.course_key.for_branch(
                None).version_agnostic()

            target_block = self.runtime.get_block(target_block_id)

            student = user_by_anonymous_id(self.runtime.anonymous_student_id)

            count = 0

            if student:

                def create_module(descriptor):
                    return target_block.runtime.get_block(descriptor.location)

                for module_descriptor in yield_dynamic_descriptor_descendents(
                        target_block, create_module):

                    (correct, total) = get_score(course_id, student,
                                                 module_descriptor,
                                                 create_module)
                    if (correct is None and total is None) or (not total > 0):
                        continue

                    # Note we ignore the 'graded' flag since authors may wish to use a leaderboard for non-graded content
                    total_correct += correct
                    total_possible += total
                    count += 1

                    attempts = module_descriptor.problem_attempts
                    max_attempts = module_descriptor.max_attempts

                    is_attempted = module_descriptor.is_attempted
                    embed_code = module_descriptor.get_problem_html
            else:
                embed_code = "aaaa"
        else:
            embed_code = "Error: EdX not found."

        #lets do some math to see if we passed
        score = 0
        # calculate score
        passing = False
        if total_possible > 0:
            score = total_correct / total_possible * 100  #get score out of 100, not 1
        if score >= 80:
            passing = True

        if attempts > 0 or is_attempted == True:
            # student has submitted this assessment
            result_file = "assessmentendcap.html"

            if passing:
                result_file = "assessmentendcap-pass.html"
            else:
                if max_attempts and attempts < max_attempts:
                    # student can still submit this problem again
                    result_file = "assessmentendcap-tryagain.html"

                elif attempts >= max_attempts:
                    # student has submitted this problem the max number of times
                    result_file = "assessmentendcap-fail.html"

        else:
            #student has not submitted assessment. We don't need to render anything.
            result_file = "assessmentendcap-blank.html"

        return self.create_fragment(
            "static/html/" + result_file,
            context={
                'embed_code': embed_code,
                'total_correct': total_correct,
                'total_possible': total_possible,
                'score': score,
                'attempts': attempts,
                'max_attempts': max_attempts,
                'count': count,
                'is_attempted': is_attempted
            },
            javascript=["static/js/assessmentendcap.js"],
            initialize='AssessmentEndcapXBlock')
def main():
    from django.contrib.auth.models import User
    from openassessment.assessment.models import Assessment, AssessmentPart, StaffWorkflow
    from openassessment.workflow.models import AssessmentWorkflow, AssessmentWorkflowStep
    from student.models import anonymous_id_for_user, user_by_anonymous_id
    from submissions.models import Score, ScoreSummary, ScoreAnnotation, Submission

    old_scores = Score.objects.filter(submission__isnull=True, reset=False).order_by('id')
    updated_count = 0
    for score in old_scores:
        try:
            with transaction.atomic():
                # ScoreSummary is updated on Score saves but for this script we don't want that.
                # Correct way is to disconnect post_save signal, but since the receiver function
                # is defined in the class, we can't reference it. Workaround here is to just
                # prefetch the score summary and resave it to maintain its original field values.
                score_summary = ScoreSummary.objects.get(student_item=score.student_item)

                # Update old override with submission from the score preceding it.
                # If none exists, look for it in the submissions table.
                preceding_score = Score.objects.filter(
                    student_item=score.student_item,
                    created_at__lte=score.created_at,
                    submission__isnull=False,
                ).order_by('-created_at')[:1]
                if preceding_score.count():
                    submission = preceding_score.get().submission
                else:
                    submission_qset = Submission.objects.filter(student_item=score.student_item)
                    if submission_qset.count() > 1:
                        raise Exception("MULTIPLE SUBMISSIONS FOR STUDENT_ITEM {}".format(score.student_item))
                    else:
                        submission = submission_qset[:1].get()
                score.submission = submission
                score.save()

                # Offset override reset by 1 second for convenience when sorting db
                override_date = score.created_at - datetime.timedelta(seconds=1)
                # Create reset score
                Score.objects.create(
                    student_item=score.student_item,
                    submission=None,
                    points_earned=0,
                    points_possible=0,
                    created_at=override_date,
                    reset=True,
                )

                # Restore original score summary values
                score_summary.save()

                # Fetch staff id from score course for ScoreAnnotation
                course_id = CourseKey.from_string(score.student_item.course_id)
                staff = User.objects.filter(
                    courseaccessrole__role__in=['instructor', 'staff'],
                    courseaccessrole__course_id=course_id,
                )[:1].get()
                staff_id = anonymous_id_for_user(staff, course_id, save=False)

                # Create ScoreAnnotation
                score_annotation = ScoreAnnotation(
                    score=score,
                    annotation_type="staff_defined",
                    creator=staff_id,
                    reason="A staff member has defined the score for this submission",
                )
                score_annotation.save()

                # ORA2 Table Updates...
                # Fetch rubric from an existing assessment
                assessment = Assessment.objects.filter(submission_uuid=submission.uuid)[:1].get()
                rubric = assessment.rubric

                staff_assessment = Assessment.create(
                    rubric=rubric,
                    scorer_id=staff_id,
                    submission_uuid=submission.uuid,
                    score_type="ST",
                    scored_at=override_date,
                )

                # Fake criterion selections
                rubric_index = rubric.index
                assessment_parts = []

                criteria_without_options = rubric_index.find_criteria_without_options()
                criteria_with_options = set(rubric_index._criteria_index.values()) - criteria_without_options
                ordered_criteria = sorted(criteria_with_options, key=lambda criterion: criterion.order_num)
                criteria_options = [c.options.all() for c in ordered_criteria]
                # Just take the first combination of options which add up to the override point score
                for selection in itertools.product(*criteria_options):
                    total = sum(option.points for option in selection)
                    if total == score.points_earned:
                        for option in selection:
                            assessment_parts.append({
                                'criterion': option.criterion,
                                'option': option
                            })
                        break

                # Default to first option for each criteria if no matching sum found
                if not assessment_parts:
                    print "NO CLEAN SUM FOR SUBMISSION " + submission.uuid
                    for options in criteria_options:
                        assessment_parts.append({
                            'criterion': options[0].criterion,
                            'option': options[0],
                        })

                # Add feedback-only criteria
                for criterion in criteria_without_options:
                    assessment_parts.append({
                        'criterion': criterion,
                        'option': None
                    })

                AssessmentPart.objects.bulk_create([
                    AssessmentPart(
                        assessment=staff_assessment,
                        criterion=assessment_part['criterion'],
                        option=assessment_part['option'],
                        feedback=u""
                    )
                    for assessment_part in assessment_parts
                ])

                try:
                    staff_workflow = StaffWorkflow.objects.get(submission_uuid=submission.uuid)
                    staff_workflow.assessment = staff_assessment.id
                    staff_workflow.grading_completed_at = override_date
                except StaffWorkflow.DoesNotExist:
                    staff_workflow = StaffWorkflow(
                        scorer_id=staff_id,
                        course_id=score.student_item.course_id,
                        item_id=score.student_item.item_id,
                        submission_uuid=submission.uuid,
                        created_at=override_date,
                        grading_completed_at=override_date,
                        assessment=staff_assessment.id,
                    )
                staff_workflow.save()

                workflow = AssessmentWorkflow.get_by_submission_uuid(submission.uuid)
                try:
                    staff_step = workflow.steps.get(name='staff')
                    staff_step.assessment_completed_at = score.created_at
                    staff_step.submitter_completed_at = score.created_at
                    staff_step.save()
                except AssessmentWorkflowStep.DoesNotExist:
                    for step in workflow.steps.all():
                        step.assessment_completed_at = score.created_at
                        step.submitter_completed_at = score.created_at
                        step.order_num += 1
                        step.save()
                    workflow.steps.add(
                        AssessmentWorkflowStep(
                            name='staff',
                            order_num=0,
                            assessment_completed_at=score.created_at,
                            submitter_completed_at=score.created_at,
                        )
                    )

                # Update workflow status to done if it wasn't subsequently cancelled
                if workflow.status != 'cancelled':
                    workflow.status = 'done'
                    workflow.save()

            updated_count += 1
            user = user_by_anonymous_id(score.student_item.student_id)
            print(
                "Successfully updated score {} for user {} with email {} in course {} for item: {}".format(
                    score.id,
                    user.username,
                    user.email,
                    score.student_item.course_id,
                    score.student_item.item_id,
                )
            )
        except Exception as err:
            print("An error occurred updating score {}: {}".format(score.id, err))
            print("Please update this score manually and retry script.")
            break

    print("Script finished, number of scores updated: {}.".format(updated_count))
Пример #42
0
    def handle(self, *args, **options):
        if len(args) != 1:
            raise CommandError(
                "This command requires only one argument: <course_id>")

        course_id, = args
        # Check args: course_id
        try:
            course_id = CourseLocator.from_string(course_id)
        except InvalidKeyError:
            raise CommandError(
                "The course_id is not of the right format. It should be like 'org/course/run' or 'course-v1:org+course+run'"
            )
        if not modulestore().get_course(course_id):
            raise CommandError("No such course was found.")

        # S3 store
        try:
            conn = connect_s3(settings.AWS_ACCESS_KEY_ID,
                              settings.AWS_SECRET_ACCESS_KEY)
            bucket = conn.get_bucket(settings.FILE_UPLOAD_STORAGE_BUCKET_NAME)
        except Exception as e:
            print e
            raise CommandError(
                "Could not establish a connection to S3 for file upload. Check your credentials."
            )

        # Dump directory
        dump_dir = options['dump_dir'] or '/tmp/dump_oa_scores'
        if not os.path.exists(dump_dir):
            os.makedirs(dump_dir)

        # Whether to gather attachments
        with_attachments = options['with_attachments']

        # Find openassessment items
        oa_items = modulestore().get_items(
            course_id, qualifiers={'category': 'openassessment'})
        if not oa_items:
            raise CommandError("No openassessment item was found.")
        oa_items = sorted(
            oa_items,
            key=lambda item: item.start or datetime(2030, 1, 1, tzinfo=UTC()))
        print "Openassessment item(s):"
        oa_output = PrettyTable(['#', 'Item ID', 'Title'])
        oa_output.align = 'l'
        for i, oa_item in enumerate(oa_items):
            row = []
            row.append(i)
            row.append(oa_item.location)
            row.append(oa_item.title)
            oa_output.add_row(row)
        print oa_output
        while True:
            try:
                selected = raw_input(
                    "Choose an openassessment item # (empty to cancel): ")
                if selected == '':
                    print "Cancelled."
                    return
                selected_oa_item = int(selected)
                oa_item = oa_items[selected_oa_item]
                break
            except (IndexError, ValueError):
                print "WARN: Invalid number was detected. Choose again."
                continue

        item_location = oa_item.location

        # Get submissions from course_id and item_location
        submissions = get_submissions(course_id, item_location)

        header = [
            'Title', 'User name', 'Submission content',
            'Submission created at', 'Status', 'Points earned',
            'Points possible', 'Score created at', 'Grade count',
            'Being graded count', 'Scored count'
        ]
        header_extra = []
        rows = []

        for submission in submissions:
            row = []
            # 'Title'
            row.append(oa_item.title)
            print 'submission_uuid=%s' % submission.submission_uuid
            # 'User Name'
            user = user_by_anonymous_id(submission.student_id)
            row.append(user.username)
            # 'Submission Content'
            raw_answer = Submission.objects.get(
                uuid=submission.submission_uuid).raw_answer
            row.append(json.loads(raw_answer)['text'].replace('\n', '[\\n]'))
            # 'Submission created at'
            row.append(submission.created_at)
            # 'Status'
            row.append(
                get_workflow_info(submission.submission_uuid,
                                  oa_item)['status'])

            # 'Points earned', 'Points possible', 'Score created at'
            latest_score = get_latest_score(submission)
            if latest_score is not None:
                row.append(latest_score.points_earned)
                row.append(latest_score.points_possible)
                row.append(latest_score.created_at)
            else:
                row.append('')
                row.append('')
                row.append('')

            # 'Grade count'
            row.append(submission.num_peers_graded())
            # 'Being graded count'
            row.append(
                len(
                    PeerWorkflowItem.objects.filter(
                        author=submission.id,
                        submission_uuid=submission.submission_uuid,
                        assessment__isnull=False)))
            # 'Scored count'
            row.append(
                len(
                    PeerWorkflowItem.objects.filter(
                        author=submission.id,
                        submission_uuid=submission.submission_uuid,
                        assessment__isnull=False,
                        scored=True)))

            # assessments from others
            assessed_items = PeerWorkflowItem.objects.filter(
                author=submission.id,
                submission_uuid=submission.submission_uuid,
                assessment__isnull=False).order_by('assessment')
            if assessed_items:
                # 'Scored flags'
                header_extra.append('Scored flags')
                row.append(','.join([
                    str(int(assessed_item.scored))
                    for assessed_item in assessed_items
                ]))
                # 'Scorer Usernames'
                header_extra.append('Scorer usernames')
                scorer_usernames = [
                    user_by_anonymous_id(
                        assessed_item.scorer.student_id).username
                    for assessed_item in assessed_items
                ]
                row.append(','.join(scorer_usernames))

                assessed_assessments = [
                    assessed_item.assessment
                    for assessed_item in assessed_items
                ]
                scores = scores_by_criterion(assessed_assessments)
                # 'Rubric points'
                for i, score in enumerate(scores.items()):
                    header_extra.append('Rubric(%s) points' % score[0])
                    row.append(','.join(map(str, score[1])))
            else:
                pass

            rows.append(row)

        # Create csv file
        header.extend(sorted(set(header_extra), key=header_extra.index))
        csv_filename = 'oa_scores-%s-#%d.csv' % (
            course_id.to_deprecated_string().replace('/',
                                                     '.'), selected_oa_item)
        csv_filepath = os.path.join(dump_dir, csv_filename)
        write_csv(csv_filepath, header, rows)
        # Upload to S3
        upload_file_to_s3(bucket, csv_filename, csv_filepath)

        # Download images from S3
        if with_attachments:
            temp_dir = tempfile.mkdtemp()
            for submission in submissions:
                file_key = u"{prefix}/{student_id}/{course_id}/{item_id}".format(
                    prefix=settings.FILE_UPLOAD_STORAGE_PREFIX,
                    student_id=submission.student_id,
                    course_id=course_id.to_deprecated_string(),
                    item_id=oa_item.location.to_deprecated_string(),
                )
                try:
                    key = bucket.get_key(file_key)
                except:
                    print "WARN: No such file in S3 [%s]" % file_key
                    continue
                user = user_by_anonymous_id(submission.student_id)
                user_path = os.path.join(temp_dir, user.username)
                try:
                    key.get_contents_to_filename(user_path)
                except:
                    print "WARN: Could not download file from S3 [%s]" % file_key
                    continue
            # Compress and upload to S3
            tar_filename = 'oa_scores-%s-#%d.tar.gz' % (
                course_id.to_deprecated_string().replace(
                    '/', '.'), selected_oa_item)
            tar_filepath = os.path.join(dump_dir, tar_filename)
            tar = tarfile.open(tar_filepath, 'w:gz')
            tar.add(temp_dir, arcname=tar_filename)
            shutil.rmtree(temp_dir)
            upload_file_to_s3(bucket, tar_filename, tar_filepath)
Пример #43
0
    def student_view(self, context):
        """
        Create a fragment used to display the XBlock to a student.
        `context` is a dictionary used to configure the display (unused).

        Returns a `Fragment` object specifying the HTML, CSS, and JavaScript
        to display.
        """

        attempts = None
        max_attempts = None
        score = None
        passing = False
        is_attempted = False

        # On first adding the block, the studio calls student_view instead of
        # author_view, which causes problems. Force call of the author view.
        if getattr(self.runtime, 'is_author_mode', False):
            return self.author_view()

        if EDX_FOUND:


            total_correct, total_possible = 0, 0

            target_block_id = self.graded_target_id
            course_id = target_block_id.course_key.for_branch(None).version_agnostic()

            target_block = self.runtime.get_block(target_block_id)

            student = user_by_anonymous_id(self.runtime.anonymous_student_id)

            count = 0


            if student:
                def create_module(descriptor):
                    return target_block.runtime.get_block(descriptor.location)

                for module_descriptor in yield_dynamic_descriptor_descendents(target_block, create_module):

                    (correct, total) = get_score(course_id, student, module_descriptor, create_module)
                    if (correct is None and total is None) or (not total > 0):
                       continue

                    # Note we ignore the 'graded' flag since authors may wish to use a leaderboard for non-graded content
                    total_correct += correct
                    total_possible += total
                    count += 1

                    attempts = module_descriptor.problem_attempts
                    max_attempts = module_descriptor.max_attempts

                    is_attempted =  module_descriptor.is_attempted
                    embed_code = module_descriptor.get_problem_html
            else:
                embed_code = "aaaa"
        else:
            embed_code = "Error: EdX not found."

        #lets do some math to see if we passed
        score = 0
        # calculate score
        passing = False
        if total_possible > 0:
            score = total_correct / total_possible * 100 #get score out of 100, not 1
        if score >= 80:
            passing = True

        if attempts > 0 or is_attempted == True:
            # student has submitted this assessment
            result_file="assessmentendcap.html"

            if passing:
                result_file = "assessmentendcap-pass.html"
            else:
                if max_attempts and attempts < max_attempts:
                    # student can still submit this problem again
                    result_file = "assessmentendcap-tryagain.html"

                elif attempts >= max_attempts:
                    # student has submitted this problem the max number of times
                    result_file = "assessmentendcap-fail.html"

        else:
            #student has not submitted assessment. We don't need to render anything.
            result_file="assessmentendcap-blank.html"

        return self.create_fragment(
            "static/html/" + result_file,
            context={
                'embed_code': embed_code,
                'total_correct': total_correct,
                'total_possible': total_possible,
                'score': score,
                'attempts': attempts,
                'max_attempts': max_attempts,
                'count' : count,
                'is_attempted': is_attempted
            },
            javascript=["static/js/assessmentendcap.js"],
            initialize='AssessmentEndcapXBlock'
        )
Пример #44
0
    def handle(self, *args, **options):
        if len(args) != 1:
            raise CommandError("This command requires only one argument: <course_id>")

        course_id, = args
        # Check args: course_id
        try:
            course_id = CourseLocator.from_string(course_id)
        except InvalidKeyError:
            raise CommandError("The course_id is not of the right format. It should be like 'org/course/run' or 'course-v1:org+course+run'")
        if not modulestore().get_course(course_id):
            raise CommandError("No such course was found.")

        # S3 store
        try:
            conn = connect_s3(settings.AWS_ACCESS_KEY_ID, settings.AWS_SECRET_ACCESS_KEY)
            bucket = conn.get_bucket(settings.FILE_UPLOAD_STORAGE_BUCKET_NAME)
        except Exception as e:
            print e
            raise CommandError("Could not establish a connection to S3 for file upload. Check your credentials.")

        # Dump directory
        dump_dir = options['dump_dir'] or '/tmp/dump_oa_scores'
        if not os.path.exists(dump_dir):
            os.makedirs(dump_dir)

        # Whether to gather attachments
        with_attachments = options['with_attachments']

        # Find openassessment items
        oa_items = modulestore().get_items(course_id, qualifiers={'category': 'openassessment'})
        if not oa_items:
            raise CommandError("No openassessment item was found.")
        oa_items = sorted(oa_items, key=lambda item:item.start or datetime(2030, 1, 1, tzinfo=UTC()))
        print "Openassessment item(s):"
        oa_output = PrettyTable(['#', 'Item ID', 'Title'])
        oa_output.align = 'l'
        for i, oa_item in enumerate(oa_items):
            row = []
            row.append(i)
            row.append(oa_item.location)
            row.append(oa_item.title)
            oa_output.add_row(row)
        print oa_output
        while True:
            try:
                selected = raw_input("Choose an openassessment item # (empty to cancel): ")
                if selected == '':
                    print "Cancelled."
                    return
                selected_oa_item = int(selected)
                oa_item = oa_items[selected_oa_item]
                break
            except (IndexError, ValueError):
                print "WARN: Invalid number was detected. Choose again."
                continue

        item_location = oa_item.location

        # Get submissions from course_id and item_location
        submissions = get_submissions(course_id, item_location)

        header = ['Title', 'User name', 'Submission content', 'Submission created at', 'Status', 'Points earned', 'Points possible', 'Score created at', 'Grade count', 'Being graded count', 'Scored count']
        header_extra = []
        rows = []

        for submission in submissions:
            row = []
            # 'Title'
            row.append(oa_item.title)
            print 'submission_uuid=%s' % submission.submission_uuid
            # 'User Name'
            user = user_by_anonymous_id(submission.student_id)
            row.append(user.username)
            # 'Submission Content'
            raw_answer = Submission.objects.get(uuid=submission.submission_uuid).raw_answer
            row.append(json.loads(raw_answer)['text'].replace('\n', '[\\n]'))
            # 'Submission created at'
            row.append(submission.created_at)
            # 'Status'
            row.append(get_workflow_info(submission.submission_uuid, oa_item)['status'])

            # 'Points earned', 'Points possible', 'Score created at'
            latest_score = get_latest_score(submission)
            if latest_score is not None:
                row.append(latest_score.points_earned)
                row.append(latest_score.points_possible)
                row.append(latest_score.created_at)
            else:
                row.append('')
                row.append('')
                row.append('')

            # 'Grade count'
            row.append(submission.num_peers_graded())
            # 'Being graded count'
            row.append(len(PeerWorkflowItem.objects.filter(author=submission.id, submission_uuid=submission.submission_uuid, assessment__isnull=False)))
            # 'Scored count'
            row.append(len(PeerWorkflowItem.objects.filter(author=submission.id, submission_uuid=submission.submission_uuid, assessment__isnull=False, scored=True)))

            # assessments from others
            assessed_items = PeerWorkflowItem.objects.filter(author=submission.id, submission_uuid=submission.submission_uuid, assessment__isnull=False).order_by('assessment')
            if assessed_items:
                # 'Scored flags'
                header_extra.append('Scored flags')
                row.append(','.join([str(int(assessed_item.scored)) for assessed_item in assessed_items]))
                # 'Scorer Usernames'
                header_extra.append('Scorer usernames')
                scorer_usernames = [user_by_anonymous_id(assessed_item.scorer.student_id).username for assessed_item in assessed_items]
                row.append(','.join(scorer_usernames))

                assessed_assessments = [assessed_item.assessment for assessed_item in assessed_items]
                scores = scores_by_criterion(assessed_assessments)
                # 'Rubric points'
                for i, score in enumerate(scores.items()):
                    header_extra.append('Rubric(%s) points' % score[0])
                    row.append(','.join(map(str, score[1])))
            else:
                pass

            rows.append(row)

        # Create csv file
        header.extend(sorted(set(header_extra), key=header_extra.index))
        csv_filename = 'oa_scores-%s-#%d.csv' % (course_id.to_deprecated_string().replace('/', '.'), selected_oa_item)
        csv_filepath = os.path.join(dump_dir, csv_filename)
        write_csv(csv_filepath, header, rows)
        # Upload to S3
        upload_file_to_s3(bucket, csv_filename, csv_filepath)

        # Download images from S3
        if with_attachments:
            temp_dir = tempfile.mkdtemp()
            for submission in submissions:
                file_key = u"{prefix}/{student_id}/{course_id}/{item_id}".format(
                    prefix=settings.FILE_UPLOAD_STORAGE_PREFIX,
                    student_id=submission.student_id,
                    course_id=course_id.to_deprecated_string(),
                    item_id=oa_item.location.to_deprecated_string(),
                )
                try:
                    key = bucket.get_key(file_key)
                except:
                    print "WARN: No such file in S3 [%s]" % file_key
                    continue
                user = user_by_anonymous_id(submission.student_id)
                user_path = os.path.join(temp_dir, user.username)
                try:
                    key.get_contents_to_filename(user_path)
                except:
                    print "WARN: Could not download file from S3 [%s]" % file_key
                    continue
            # Compress and upload to S3
            tar_filename = 'oa_scores-%s-#%d.tar.gz' % (course_id.to_deprecated_string().replace('/', '.'), selected_oa_item)
            tar_filepath = os.path.join(dump_dir, tar_filename)
            tar = tarfile.open(tar_filepath, 'w:gz')
            tar.add(temp_dir, arcname=tar_filename)
            shutil.rmtree(temp_dir)
            upload_file_to_s3(bucket, tar_filename, tar_filepath)
Пример #45
0
def get_user_by_anonymous_id(*args, **kwargs):
    """ Returns the user_by_anonymous_id method. """
    return user_by_anonymous_id(*args, **kwargs)