def get(self): user_data = UserData.current() if user_data: user_data_override = self.request_user_data("coach_email") if user_util.is_current_user_developer() and user_data_override: user_data = user_data_override invalid_student = self.request_bool("invalid_student", default=False) coach_requests = [ x.student_requested_data.email for x in CoachRequest.get_for_coach(user_data) if x.student_requested_data ] student_lists_models = StudentList.get_for_coach(user_data.key()) student_lists_list = [] for student_list in student_lists_models: student_lists_list.append({ 'key': str(student_list.key()), 'name': student_list.name, }) student_lists_dict = dict( (g['key'], g) for g in student_lists_list) students_data = user_data.get_students_data() students = map( lambda s: { 'key': str(s.key()), 'email': s.email, 'nickname': s.nickname, 'student_lists': [ l for l in [ student_lists_dict.get(str(list_id)) for list_id in s.student_lists ] if l ], }, students_data) students.sort(key=lambda s: s['nickname']) template_values = { "students": students, "students_json": json.dumps(students), "student_lists": student_lists_list, "student_lists_json": json.dumps(student_lists_list), "invalid_student": invalid_student, "coach_requests": coach_requests, "coach_requests_json": json.dumps(coach_requests), 'selected_nav_link': 'coach' } self.render_jinja2_template('viewstudentlists.html', template_values) else: self.redirect(util.create_login_url(self.request.uri))
def get(self): user_data = UserData.current() user_data_override = self.request_user_data("coach_email") if user_util.is_current_user_developer() and user_data_override: user_data = user_data_override invalid_student = self.request_bool("invalid_student", default=False) coach_requests = [req.student_requested_identifier for req in CoachRequest.get_for_coach(user_data) if req.student_requested_data] student_lists_models = StudentList.get_for_coach(user_data.key()) student_lists_list = [] for student_list in student_lists_models: student_lists_list.append({ 'key': str(student_list.key()), 'name': student_list.name, }) student_lists_dict = dict((g['key'], g) for g in student_lists_list) def student_to_dict(s): """Convert the UserData s to a dictionary for rendering.""" return { 'key': str(s.key()), 'email': s.email, 'username': s.username, # Note that child users don't have an email. 'identifier': s.username or s.email, 'nickname': s.nickname, 'profile_root': s.profile_root, 'can_modify_coaches': s.can_modify_coaches(), 'studentLists': [l for l in [student_lists_dict.get(str(list_id)) for list_id in s.student_lists] if l], } students_data = user_data.get_students_data() students = map(student_to_dict, students_data) students.sort(key=lambda s: s['nickname']) child_accounts = map(student_to_dict, user_data.get_child_users()) template_values = { 'students': students, 'child_accounts': child_accounts, 'child_account_keyset': set([c['key'] for c in child_accounts]), 'students_json': json.dumps(students), 'student_lists': student_lists_list, 'student_lists_json': json.dumps(student_lists_list), 'invalid_student': invalid_student, 'coach_requests': coach_requests, 'coach_requests_json': json.dumps(coach_requests), 'selected_nav_link': 'coach', 'email': user_data.email, } self.render_jinja2_template('viewstudentlists.html', template_values)
def get(self): if not user_util.is_current_user_developer(): if App.dashboard_secret and self.request_string("x", default=None) != App.dashboard_secret: self.redirect(users.create_login_url(self.request.uri)) return kinds = self.request_string("kinds", Dashboard.DEFAULT_KINDS).split(',') graphs = [] for kind in kinds: d = daily_graph_context(EntityStatistic(kind), "data") d['kind_name'] = kind d['title'] = "%s created per day" % kind graphs.append(d) self.render_jinja2_template("dashboard/dashboard.html", {'graphs': graphs})
def get(self): user_data = UserData.current() if user_data: user_data_override = self.request_user_data("coach_email") if user_util.is_current_user_developer() and user_data_override: user_data = user_data_override invalid_student = self.request_bool("invalid_student", default = False) coach_requests = [x.student_requested_data.email for x in CoachRequest.get_for_coach(user_data) if x.student_requested_data] student_lists_models = StudentList.get_for_coach(user_data.key()) student_lists_list = []; for student_list in student_lists_models: student_lists_list.append({ 'key': str(student_list.key()), 'name': student_list.name, }) student_lists_dict = dict((g['key'], g) for g in student_lists_list) students_data = user_data.get_students_data() students = map(lambda s: { 'key': str(s.key()), 'email': s.email, 'nickname': s.nickname, 'profile_root': s.profile_root, 'studentLists': [l for l in [student_lists_dict.get(str(list_id)) for list_id in s.student_lists] if l], }, students_data) students.sort(key=lambda s: s['nickname']) template_values = { "students": students, "students_json": json.dumps(students), "student_lists": student_lists_list, "student_lists_json": json.dumps(student_lists_list), "invalid_student": invalid_student, "coach_requests": coach_requests, "coach_requests_json": json.dumps(coach_requests), 'selected_nav_link': 'coach' } self.render_jinja2_template('viewstudentlists.html', template_values) else: self.redirect(util.create_login_url(self.request.uri))
def attempt_problem(user_data, user_exercise, problem_number, attempt_number, attempt_content, sha1, seed, completed, count_hints, time_taken, review_mode, exercise_non_summative, problem_type, ip_address): if user_exercise and user_exercise.belongs_to(user_data): dt_now = datetime.datetime.now() exercise = user_exercise.exercise_model old_graph = user_exercise.get_user_exercise_graph() user_exercise.last_done = dt_now user_exercise.seconds_per_fast_problem = exercise.seconds_per_fast_problem user_exercise.summative = exercise.summative user_data.record_activity(user_exercise.last_done) # If a non-admin tries to answer a problem out-of-order, just ignore it if problem_number != user_exercise.total_done + 1 and not user_util.is_current_user_developer(): # Only admins can answer problems out of order. raise QuietException("Problem number out of order (%s vs %s) for user_id: %s submitting attempt content: %s with seed: %s" % (problem_number, user_exercise.total_done + 1, user_data.user_id, attempt_content, seed)) if len(sha1) <= 0: raise Exception("Missing sha1 hash of problem content.") if len(seed) <= 0: raise Exception("Missing seed for problem content.") if len(attempt_content) > 500: raise Exception("Attempt content exceeded maximum length.") # Build up problem log for deferred put problem_log = models.ProblemLog( key_name=models.ProblemLog.key_for(user_data, user_exercise.exercise, problem_number), user=user_data.user, exercise=user_exercise.exercise, problem_number=problem_number, time_taken=time_taken, time_done=dt_now, count_hints=count_hints, hint_used=count_hints > 0, correct=completed and not count_hints and (attempt_number == 1), sha1=sha1, seed=seed, problem_type=problem_type, count_attempts=attempt_number, attempts=[attempt_content], ip_address=ip_address, review_mode=review_mode, ) if exercise.summative: problem_log.exercise_non_summative = exercise_non_summative first_response = (attempt_number == 1 and count_hints == 0) or (count_hints == 1 and attempt_number == 0) if user_exercise.total_done > 0 and user_exercise.streak == 0 and first_response: bingo('hints_keep_going_after_wrong') just_earned_proficiency = False # Users can only attempt problems for themselves, so the experiment # bucket always corresponds to the one for this current user struggling_model = StrugglingExperiment.get_alternative_for_user( user_data, current_user=True) or StrugglingExperiment.DEFAULT if completed: user_exercise.total_done += 1 if problem_log.correct: proficient = user_data.is_proficient_at(user_exercise.exercise) explicitly_proficient = user_data.is_explicitly_proficient_at(user_exercise.exercise) suggested = user_data.is_suggested(user_exercise.exercise) problem_log.suggested = suggested problem_log.points_earned = points.ExercisePointCalculator(user_exercise, suggested, proficient) user_data.add_points(problem_log.points_earned) # Streak only increments if problem was solved correctly (on first attempt) user_exercise.total_correct += 1 user_exercise.streak += 1 user_exercise.longest_streak = max(user_exercise.longest_streak, user_exercise.streak) user_exercise.update_proficiency_model(correct=True) bingo([ 'struggling_problems_correct', 'suggested_activity_problems_correct', ]) if user_exercise.progress >= 1.0 and not explicitly_proficient: bingo([ 'hints_gained_proficiency_all', 'struggling_gained_proficiency_all', 'suggested_activity_gained_proficiency_all', ]) if not user_exercise.has_been_proficient(): bingo('hints_gained_new_proficiency') if user_exercise.history_indicates_struggling(struggling_model): bingo('struggling_gained_proficiency_post_struggling') user_exercise.set_proficient(user_data) user_data.reassess_if_necessary() just_earned_proficiency = True problem_log.earned_proficiency = True util_badges.update_with_user_exercise( user_data, user_exercise, include_other_badges=True, action_cache=last_action_cache.LastActionCache.get_cache_and_push_problem_log(user_data, problem_log)) # Update phantom user notifications util_notify.update(user_data, user_exercise) bingo([ 'hints_problems_done', 'struggling_problems_done', 'suggested_activity_problems_done', ]) else: # Only count wrong answer at most once per problem if first_response: user_exercise.update_proficiency_model(correct=False) bingo([ 'hints_wrong_problems', 'struggling_problems_wrong', 'suggested_activity_problems_wrong', ]) if user_exercise.is_struggling(struggling_model): bingo('struggling_struggled_binary') # If this is the first attempt, update review schedule appropriately if attempt_number == 1: user_exercise.schedule_review(completed) user_exercise_graph = models.UserExerciseGraph.get_and_update(user_data, user_exercise) goals_updated = GoalList.update_goals(user_data, lambda goal: goal.just_did_exercise(user_data, user_exercise, just_earned_proficiency)) user_data.uservideocss_version += 1 if user_exercise.progress >= 1.0: UserVideoCss.set_completed(user_data.key(), exercise.key(), user_data.uservideocss_version) else: UserVideoCss.set_started(user_data.key(), exercise.key(), user_data.uservideocss_version) # Bulk put db.put([user_data, user_exercise, user_exercise_graph.cache]) # Defer the put of ProblemLog for now, as we think it might be causing hot tablets # and want to shift it off to an automatically-retrying task queue. # http://ikaisays.com/2011/01/25/app-engine-datastore-tip-monotonically-increasing-values-are-bad/ deferred.defer(models.commit_problem_log, problem_log, _queue="problem-log-queue") if user_data is not None and user_data.coaches: # Making a separate queue for the log summaries so we can clearly see how much they are getting used deferred.defer(models.commit_log_summary_coaches, problem_log, user_data.coaches, _queue="log-summary-queue", ) return user_exercise, user_exercise_graph, goals_updated
def attempt_problem(user_data, user_exercise, problem_number, attempt_number, attempt_content, sha1, seed, completed, count_hints, time_taken, exercise_non_summative, problem_type, ip_address): if user_exercise and user_exercise.belongs_to(user_data): dt_now = datetime.datetime.now() exercise = user_exercise.exercise_model prev_last_done = user_exercise.last_done user_exercise.last_done = dt_now user_exercise.seconds_per_fast_problem = exercise.seconds_per_fast_problem user_exercise.summative = exercise.summative user_data.record_activity(user_exercise.last_done) # If a non-admin tries to answer a problem out-of-order, just ignore it if problem_number != user_exercise.total_done + 1 and not user_util.is_current_user_developer(): # Only admins can answer problems out of order. raise Exception("Problem number out of order (%s vs %s) for user_id: %s submitting attempt content: %s with seed: %s" % (problem_number, user_exercise.total_done + 1, user_data.user_id, attempt_content, seed)) if len(sha1) <= 0: raise Exception("Missing sha1 hash of problem content.") if len(seed) <= 0: raise Exception("Missing seed for problem content.") if len(attempt_content) > 500: raise Exception("Attempt content exceeded maximum length.") # Build up problem log for deferred put problem_log = models.ProblemLog( key_name = "problemlog_%s_%s_%s" % (user_data.key_email, user_exercise.exercise, problem_number), user = user_data.user, exercise = user_exercise.exercise, problem_number = problem_number, time_taken = time_taken, time_done = dt_now, count_hints = count_hints, hint_used = count_hints > 0, correct = completed and not count_hints and (attempt_number == 1), sha1 = sha1, seed = seed, problem_type = problem_type, count_attempts = attempt_number, attempts = [attempt_content], ip_address = ip_address, ) if exercise.summative: problem_log.exercise_non_summative = exercise_non_summative first_response = (attempt_number == 1 and count_hints == 0) or (count_hints == 1 and attempt_number == 0) if user_exercise.total_done == 0 and first_response: user_exercise.bingo_proficiency_model('prof_new_exercises_attempted') if user_exercise.total_done > 0 and user_exercise.streak == 0 and first_response: user_exercise.bingo_proficiency_model('prof_keep_going_after_wrong') bingo('hints_keep_going_after_wrong') first_problem_after_proficiency = prev_last_done and user_exercise.proficient_date and ( abs(prev_last_done - user_exercise.proficient_date) <= datetime.timedelta(seconds=1)) if first_problem_after_proficiency: user_exercise.bingo_proficiency_model('prof_does_problem_just_after_proficiency') if completed: user_exercise.total_done += 1 if problem_log.correct: proficient = user_data.is_proficient_at(user_exercise.exercise) explicitly_proficient = user_data.is_explicitly_proficient_at(user_exercise.exercise) suggested = user_data.is_suggested(user_exercise.exercise) problem_log.suggested = suggested problem_log.points_earned = points.ExercisePointCalculator(user_exercise, suggested, proficient) user_data.add_points(problem_log.points_earned) # Streak only increments if problem was solved correctly (on first attempt) user_exercise.total_correct += 1 user_exercise.streak += 1 user_exercise.longest_streak = max(user_exercise.longest_streak, user_exercise.streak) user_exercise.update_proficiency_model(correct=True) if user_exercise.summative and user_exercise.streak % consts.CHALLENGE_STREAK_BARRIER == 0: user_exercise.streak_start = 0.0 if user_exercise.progress >= 1.0 and not explicitly_proficient: bingo("hints_gained_proficiency_all") user_exercise.set_proficient(True, user_data) user_data.reassess_if_necessary() problem_log.earned_proficiency = True if first_problem_after_proficiency: user_exercise.bingo_proficiency_model('prof_problem_correct_just_after_proficiency') util_badges.update_with_user_exercise( user_data, user_exercise, include_other_badges = True, action_cache=last_action_cache.LastActionCache.get_cache_and_push_problem_log(user_data, problem_log)) # Update phantom user notifications util_notify.update(user_data, user_exercise) user_exercise.bingo_proficiency_model('prof_problems_done') bingo('hints_problems_done') else: if user_exercise.streak == 0: # 2+ in a row wrong -> not proficient user_exercise.set_proficient(False, user_data) # Only count wrong answer at most once per problem if first_response: user_exercise.update_proficiency_model(correct=False) user_exercise.bingo_proficiency_model('prof_wrong_problems') bingo('hints_wrong_problems') # If this is the first attempt, update review schedule appropriately if attempt_number == 1: user_exercise.schedule_review(completed) user_exercise_graph = models.UserExerciseGraph.get_and_update(user_data, user_exercise) # Bulk put db.put([user_data, user_exercise, user_exercise_graph.cache]) # Defer the put of ProblemLog for now, as we think it might be causing hot tablets # and want to shift it off to an automatically-retrying task queue. # http://ikaisays.com/2011/01/25/app-engine-datastore-tip-monotonically-increasing-values-are-bad/ deferred.defer(models.commit_problem_log, problem_log, _queue="problem-log-queue", _url="/_ah/queue/deferred_problemlog") if user_data is not None and user_data.coaches: # Making a separate queue for the log summaries so we can clearly see how much they are getting used deferred.defer(models.commit_log_summary_coaches, problem_log, user_data.coaches, _queue = "log-summary-queue", _url = "/_ah/queue/deferred_log_summary") return user_exercise, user_exercise_graph
def get(self): user_data = UserData.current() user_data_override = self.request_user_data("coach_email") if user_util.is_current_user_developer() and user_data_override: user_data = user_data_override invalid_student = self.request_bool("invalid_student", default=False) coach_requests = [ req.student_requested_identifier for req in CoachRequest.get_for_coach(user_data) if req.student_requested_data ] student_lists_models = StudentList.get_for_coach(user_data.key()) student_lists_list = [] for student_list in student_lists_models: student_lists_list.append({ 'key': str(student_list.key()), 'name': student_list.name, }) student_lists_dict = dict((g['key'], g) for g in student_lists_list) def student_to_dict(s): """Convert the UserData s to a dictionary for rendering.""" return { 'key': str(s.key()), 'email': s.email, 'username': s.username, # Note that child users don't have an email. 'identifier': s.username or s.email, 'nickname': s.nickname, 'profile_root': s.profile_root, 'can_modify_coaches': s.can_modify_coaches(), 'studentLists': [ l for l in [ student_lists_dict.get(str(list_id)) for list_id in s.student_lists ] if l ], } students_data = user_data.get_students_data() students = map(student_to_dict, students_data) students.sort(key=lambda s: s['nickname']) child_accounts = map(student_to_dict, user_data.get_child_users()) template_values = { 'students': students, 'child_accounts': child_accounts, 'child_account_keyset': set([c['key'] for c in child_accounts]), 'students_json': json.dumps(students), 'student_lists': student_lists_list, 'student_lists_json': json.dumps(student_lists_list), 'invalid_student': invalid_student, 'coach_requests': coach_requests, 'coach_requests_json': json.dumps(coach_requests), 'selected_nav_link': 'coach', 'email': user_data.email, } self.render_jinja2_template('viewstudentlists.html', template_values)
def attempt_problem(user_data, user_exercise, problem_number, attempt_number, attempt_content, sha1, seed, completed, count_hints, time_taken, exercise_non_summative, problem_type, ip_address): if user_exercise and user_exercise.belongs_to(user_data): dt_now = datetime.datetime.now() exercise = user_exercise.exercise_model prev_last_done = user_exercise.last_done user_exercise.last_done = dt_now user_exercise.seconds_per_fast_problem = exercise.seconds_per_fast_problem user_exercise.summative = exercise.summative user_data.record_activity(user_exercise.last_done) # If a non-admin tries to answer a problem out-of-order, just ignore it if problem_number != user_exercise.total_done + 1 and not user_util.is_current_user_developer( ): # Only admins can answer problems out of order. raise Exception( "Problem number out of order (%s vs %s) for user_id: %s submitting attempt content: %s with seed: %s" % (problem_number, user_exercise.total_done + 1, user_data.user_id, attempt_content, seed)) if len(sha1) <= 0: raise Exception("Missing sha1 hash of problem content.") if len(seed) <= 0: raise Exception("Missing seed for problem content.") if len(attempt_content) > 500: raise Exception("Attempt content exceeded maximum length.") # Build up problem log for deferred put problem_log = models.ProblemLog( key_name="problemlog_%s_%s_%s" % (user_data.key_email, user_exercise.exercise, problem_number), user=user_data.user, exercise=user_exercise.exercise, problem_number=problem_number, time_taken=time_taken, time_done=dt_now, count_hints=count_hints, hint_used=count_hints > 0, correct=completed and not count_hints and (attempt_number == 1), sha1=sha1, seed=seed, problem_type=problem_type, count_attempts=attempt_number, attempts=[attempt_content], ip_address=ip_address, ) if exercise.summative: problem_log.exercise_non_summative = exercise_non_summative first_response = (attempt_number == 1 and count_hints == 0) or (count_hints == 1 and attempt_number == 0) if user_exercise.total_done == 0 and first_response: user_exercise.bingo_proficiency_model( 'prof_new_exercises_attempted') if user_exercise.total_done > 0 and user_exercise.streak == 0 and first_response: user_exercise.bingo_proficiency_model( 'prof_keep_going_after_wrong') bingo('hints_keep_going_after_wrong') first_problem_after_proficiency = prev_last_done and user_exercise.proficient_date and ( abs(prev_last_done - user_exercise.proficient_date) <= datetime.timedelta(seconds=1)) if first_problem_after_proficiency: user_exercise.bingo_proficiency_model( 'prof_does_problem_just_after_proficiency') if completed: user_exercise.total_done += 1 if problem_log.correct: proficient = user_data.is_proficient_at(user_exercise.exercise) explicitly_proficient = user_data.is_explicitly_proficient_at( user_exercise.exercise) suggested = user_data.is_suggested(user_exercise.exercise) problem_log.suggested = suggested problem_log.points_earned = points.ExercisePointCalculator( user_exercise, suggested, proficient) user_data.add_points(problem_log.points_earned) # Streak only increments if problem was solved correctly (on first attempt) user_exercise.total_correct += 1 user_exercise.streak += 1 user_exercise.longest_streak = max( user_exercise.longest_streak, user_exercise.streak) user_exercise.update_proficiency_model(correct=True) if user_exercise.summative and user_exercise.streak % consts.CHALLENGE_STREAK_BARRIER == 0: user_exercise.streak_start = 0.0 if user_exercise.progress >= 1.0 and not explicitly_proficient: bingo("hints_gained_proficiency_all") user_exercise.set_proficient(True, user_data) user_data.reassess_if_necessary() problem_log.earned_proficiency = True if first_problem_after_proficiency: user_exercise.bingo_proficiency_model( 'prof_problem_correct_just_after_proficiency') util_badges.update_with_user_exercise( user_data, user_exercise, include_other_badges=True, action_cache=last_action_cache.LastActionCache. get_cache_and_push_problem_log(user_data, problem_log)) # Update phantom user notifications util_notify.update(user_data, user_exercise) user_exercise.bingo_proficiency_model('prof_problems_done') bingo('hints_problems_done') else: if user_exercise.streak == 0: # 2+ in a row wrong -> not proficient user_exercise.set_proficient(False, user_data) # Only count wrong answer at most once per problem if first_response: user_exercise.update_proficiency_model(correct=False) user_exercise.bingo_proficiency_model('prof_wrong_problems') bingo('hints_wrong_problems') # If this is the first attempt, update review schedule appropriately if attempt_number == 1: user_exercise.schedule_review(completed) user_exercise_graph = models.UserExerciseGraph.get_and_update( user_data, user_exercise) # Bulk put db.put([user_data, user_exercise, user_exercise_graph.cache]) # Defer the put of ProblemLog for now, as we think it might be causing hot tablets # and want to shift it off to an automatically-retrying task queue. # http://ikaisays.com/2011/01/25/app-engine-datastore-tip-monotonically-increasing-values-are-bad/ deferred.defer(models.commit_problem_log, problem_log, _queue="problem-log-queue", _url="/_ah/queue/deferred_problemlog") if user_data is not None and user_data.coaches: # Making a separate queue for the log summaries so we can clearly see how much they are getting used deferred.defer(models.commit_log_summary_coaches, problem_log, user_data.coaches, _queue="log-summary-queue", _url="/_ah/queue/deferred_log_summary") return user_exercise, user_exercise_graph
def has_privilege(user_data, points_required): return user_util.is_current_user_developer() or \ user_data.moderator or \ user_data.points >= points_required
def is_allowed(self): return len(self.purge()) < self.hourly_limit or \ user_util.is_current_user_developer() or \ self.user_data.moderator
def attempt_problem(user_data, user_exercise, problem_number, attempt_number, attempt_content, sha1, seed, completed, count_hints, time_taken, review_mode, topic_mode, problem_type, ip_address, async_problem_log_put=True): if user_exercise and user_exercise.belongs_to(user_data): dt_now = datetime.datetime.now() exercise = user_exercise.exercise_model old_graph = user_exercise.get_user_exercise_graph() user_exercise.last_done = dt_now user_exercise.seconds_per_fast_problem = exercise.seconds_per_fast_problem user_data.record_activity(user_exercise.last_done) # If a non-admin tries to answer a problem out-of-order, just ignore it if problem_number != user_exercise.total_done + 1 and not user_util.is_current_user_developer( ): # Only admins can answer problems out of order. raise custom_exceptions.QuietException( "Problem number out of order (%s vs %s) for user_id: %s submitting attempt content: %s with seed: %s" % (problem_number, user_exercise.total_done + 1, user_data.user_id, attempt_content, seed)) if len(sha1) <= 0: raise Exception("Missing sha1 hash of problem content.") if len(seed) <= 0: raise Exception("Missing seed for problem content.") if len(attempt_content) > 500: raise Exception("Attempt content exceeded maximum length.") # Build up problem log for deferred put problem_log = exercise_models.ProblemLog( key_name=exercise_models.ProblemLog.key_for( user_data, user_exercise.exercise, problem_number), user=user_data.user, exercise=user_exercise.exercise, problem_number=problem_number, time_taken=time_taken, time_done=dt_now, count_hints=count_hints, hint_used=count_hints > 0, correct=completed and not count_hints and (attempt_number == 1), sha1=sha1, seed=seed, problem_type=problem_type, count_attempts=attempt_number, attempts=[attempt_content], ip_address=ip_address, review_mode=review_mode, topic_mode=topic_mode, ) first_response = (attempt_number == 1 and count_hints == 0) or (count_hints == 1 and attempt_number == 0) if user_exercise.total_done > 0 and user_exercise.streak == 0 and first_response: bingo('hints_keep_going_after_wrong') just_earned_proficiency = False # Users can only attempt problems for themselves, so the experiment # bucket always corresponds to the one for this current user struggling_model = StrugglingExperiment.get_alternative_for_user( user_data, current_user=True) or StrugglingExperiment.DEFAULT if completed: user_exercise.total_done += 1 if problem_log.correct: proficient = user_data.is_proficient_at(user_exercise.exercise) explicitly_proficient = user_data.is_explicitly_proficient_at( user_exercise.exercise) suggested = user_data.is_suggested(user_exercise.exercise) problem_log.suggested = suggested problem_log.points_earned = points.ExercisePointCalculator( user_exercise, topic_mode, suggested, proficient) user_data.add_points(problem_log.points_earned) # Streak only increments if problem was solved correctly (on first attempt) user_exercise.total_correct += 1 user_exercise.streak += 1 user_exercise.longest_streak = max( user_exercise.longest_streak, user_exercise.streak) user_exercise.update_proficiency_model(correct=True) bingo([ 'struggling_problems_correct', ]) if user_exercise.progress >= 1.0 and not explicitly_proficient: bingo([ 'hints_gained_proficiency_all', 'struggling_gained_proficiency_all', ]) if not user_exercise.has_been_proficient(): bingo('hints_gained_new_proficiency') if user_exercise.history_indicates_struggling( struggling_model): bingo('struggling_gained_proficiency_post_struggling') user_exercise.set_proficient(user_data) user_data.reassess_if_necessary() just_earned_proficiency = True problem_log.earned_proficiency = True badges.util_badges.update_with_user_exercise( user_data, user_exercise, include_other_badges=True, action_cache=badges.last_action_cache.LastActionCache. get_cache_and_push_problem_log(user_data, problem_log)) # Update phantom user notifications phantom_users.util_notify.update(user_data, user_exercise) bingo([ 'hints_problems_done', 'struggling_problems_done', ]) else: # Only count wrong answer at most once per problem if first_response: user_exercise.update_proficiency_model(correct=False) bingo([ 'hints_wrong_problems', 'struggling_problems_wrong', ]) if user_exercise.is_struggling(struggling_model): bingo('struggling_struggled_binary') # If this is the first attempt, update review schedule appropriately if attempt_number == 1: user_exercise.schedule_review(completed) user_exercise_graph = exercise_models.UserExerciseGraph.get_and_update( user_data, user_exercise) goals_updated = GoalList.update_goals( user_data, lambda goal: goal.just_did_exercise( user_data, user_exercise, just_earned_proficiency)) # Bulk put db.put([user_data, user_exercise, user_exercise_graph.cache]) if async_problem_log_put: # Defer the put of ProblemLog for now, as we think it might be causing hot tablets # and want to shift it off to an automatically-retrying task queue. # http://ikaisays.com/2011/01/25/app-engine-datastore-tip-monotonically-increasing-values-are-bad/ deferred.defer(exercise_models.commit_problem_log, problem_log, _queue="problem-log-queue", _url="/_ah/queue/deferred_problemlog") else: exercise_models.commit_problem_log(problem_log) if user_data is not None and user_data.coaches: # Making a separate queue for the log summaries so we can clearly see how much they are getting used deferred.defer(video_models.commit_log_summary_coaches, problem_log, user_data.coaches, _queue="log-summary-queue", _url="/_ah/queue/deferred_log_summary") return user_exercise, user_exercise_graph, goals_updated