def test_anonymous_id_secret_key_changes_do_not_change_existing_anonymous_ids(self): """Test that a same 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) assert anonymous_id == new_anonymous_id assert self.user == user_by_anonymous_id(anonymous_id) assert self.user == user_by_anonymous_id(new_anonymous_id)
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))
def is_staff(self): cohort = get_cohort(user_by_anonymous_id( self.xmodule_runtime.anonymous_student_id), self.course_id, assign=False, use_cached=True) return super().is_staff or (cohort and cohort.name == PROFS_COHORT)
def test_roundtrip_with_unicode_course_id(self): course2 = CourseFactory.create(display_name="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) assert self.user == real_user assert anonymous_id == anonymous_id_for_user(self.user, course2.id)
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, )
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'] ]
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))
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))
def get_user_by_anonymous_id(self, uid=None): """ Returns the Django User object corresponding to the given anonymous user id. Returns None if there is no user with the given anonymous user id. If no `uid` is provided, then the current anonymous user ID is used. """ return user_by_anonymous_id(uid or self._anonymous_user_id)
def get_student_data(): """ 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) }
def test_anonymous_id_secret_key_changes_result_in_diff_values_for_same_new_user(self): """Test that a different 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) AnonymousUserId.objects.filter(user=self.user).filter(course_id=self.course.id).delete() new_anonymous_id = anonymous_id_for_user(self.user, self.course.id) assert anonymous_id != new_anonymous_id assert self.user == user_by_anonymous_id(new_anonymous_id)
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": str(record.course_id), "usage_id": str(record.module_state_key), "only_if_higher": False, "expected_modified_time": to_timestamp(record.modified), "score_deleted": False, "event_transaction_id": str(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": str(record.student_item.course_id), "usage_id": str(record.student_item.item_id), "only_if_higher": False, "expected_modified_time": to_timestamp(record.created_at), "score_deleted": False, "event_transaction_id": str(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 get_sorted_submissions(self): """returns student recent assignments sorted on date""" assignments = [] submissions = submissions_api.get_all_submissions( self.block_course_id, self.block_id, ITEM_TYPE) for submission in submissions: student = user_by_anonymous_id(submission['student_id']) sub = { 'submission_id': submission['uuid'], 'username': student.username, 'student_id': submission['student_id'], 'fullname': student.profile.name, 'timestamp': submission['submitted_at'] or submission['created_at'], 'filename': submission['answer']["filename"], 'score': json.loads(submission['answer']['score']) if 'score' in submission['answer'] else 0, 'result': json.loads(submission['answer']['result']) } if is_course_cohorted(self.course_id): group = get_cohort(student, self.course_id, assign=False, use_cached=True) sub['cohort'] = group.name if group else '(não atribuído)' assignments.append(sub) assignments.sort(key=lambda assignment: assignment['timestamp'], reverse=True) return assignments
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, )
def get_quiz_data(self): pr_class = ProblemResponses().__class__ user_id = user_by_anonymous_id( self.xmodule_runtime.anonymous_student_id).id course_key = self.course_id valid_cohorts = self.get_cohorts() usage_key = self.get_quiz_unit() if not usage_key: raise InvalidKeyError user = get_user_model().objects.get(pk=user_id) student_data = [] store = modulestore() with store.bulk_operations(course_key): try: course_blocks = get_course_blocks(user, usage_key) except: raise QuizNotFound usernames = set() for title, path, block_key in pr_class._build_problem_list( course_blocks, usage_key): # Chapter and sequential blocks are filtered out since they include state # which isn't useful for this report. if block_key.block_type != "problem": continue block = store.get_item(block_key) generated_report_data = defaultdict(list) # Blocks can implement the generate_report_data method to provide their own # human-readable formatting for user state. try: user_state_iterator = iter_all_for_block(block_key) for username, state in self.generate_report_data( block, user_state_iterator): generated_report_data[username].append(state) except NotImplementedError: pass cohorted = is_course_cohorted(self.course_id) def in_cohort(user): if cohorted: cohort = get_cohort(user, course_key, assign=False, use_cached=True) if not cohort or cohort.name not in valid_cohorts or ( self.cohort and cohort.name != self.cohort): # skip this one if not on the requested cohort or has no cohort (instructor) return False return True responses = [] for response in list_problem_responses(course_key, block_key): # A block that has a single state per user can contain multiple responses # within the same state. try: user = get_user_by_username_or_email( response['username']) except User.DoesNotExist: continue usernames.add(user.username) if not in_cohort(user): continue response['name'] = self.format_name(user.profile.name) user_states = generated_report_data.get( response['username']) response['state'] = json.loads(response['state']) response['state'].pop('input_state', None) response['state'].pop('student_answers', None) if user_states: response['user_states'] = user_states responses.append(response) enrollments = CourseEnrollment.objects.filter( course_id=self.course_id) for enr in enrollments: if enr.user.username in usernames: continue if in_cohort(enr.user): # add missing students student_data.append({ 'username': enr.user.username, 'name': self.format_name(enr.user.profile.name) }) usernames.add(enr.user.username) student_data += responses return student_data
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))
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) assert self.user == real_user assert anonymous_id == anonymous_id_for_user(self.user, self.course.id)
def test_for_unregistered_user(self): # same path as for logged out user assert anonymous_id_for_user(AnonymousUser(), self.course.id) is None assert user_by_anonymous_id(None) is None
def user_id(self): return user_by_anonymous_id( self.xmodule_runtime.anonymous_student_id).id