def get(self, path): """ Display a topic page if the URL matches a pre-existing topic, such as /math/algebra or /algebra NOTE: Since there is no specific route we are matching, this handler is registered as the default handler, so unrecognized paths will return a 404. """ if path.endswith('/'): # Canonical paths do not have trailing slashes path = path[:-1] path_list = path.split('/') if len(path_list) > 0: # Only look at the actual topic ID topic = topic_models.Topic.get_by_id(path_list[-1]) if topic: bingo("topic_pages_view_page") # End topic pages A/B test if path != topic.get_extended_slug(): # If the topic ID is found but the path is incorrect, # redirect the user to the canonical path self.redirect("/%s" % topic.get_extended_slug(), True) return TopicPage.show_topic(self, topic) return # error(404) sets the status code to 404. Be aware that execution continues # after the .error call. self.error(404) raise PageNotFoundException("Page not found")
def get(self): thumbnail_link_sets = new_and_noteworthy_link_sets() # If all else fails, just show the TED talk on the homepage marquee_video = { "youtube_id": "gM95HHI4gLk", "href": "/video?v=%s" % "gM95HHI4gLk", "thumb_urls": models.Video.youtube_thumbnail_urls("gM95HHI4gLk"), "title": "Salman Khan talk at TED 2011", "key": "", } if len(thumbnail_link_sets) > 1: day = datetime.datetime.now().day # Switch up the first 4 New & Noteworthy videos on a daily basis current_link_set_offset = day % len(thumbnail_link_sets) # Switch up the marquee video on a daily basis marquee_videos = [] for thumbnail_link_set in thumbnail_link_sets: marquee_videos += filter(lambda item: item["marquee"], thumbnail_link_set) if marquee_videos: marquee_video = marquee_videos[day % len(marquee_videos)] marquee_video["selected"] = True if len(thumbnail_link_sets[current_link_set_offset]) < ITEMS_PER_SET: # If the current offset lands on a set of videos that isn't a full set, just start # again at the first set, which is most likely full. current_link_set_offset = 0 thumbnail_link_sets = thumbnail_link_sets[current_link_set_offset:] + thumbnail_link_sets[:current_link_set_offset] # Only running restructure A/B test for non-mobile clients render_type = 'original' if not self.is_mobile_capable(): render_type = HomepageRestructuringExperiment.get_render_type() bingo('homepage_restructure_visits') if render_type == 'original': library_content = library.library_content_html() else: library_content = library.playlist_content_html() template_values = { 'marquee_video': marquee_video, 'thumbnail_link_sets': thumbnail_link_sets, 'library_content': library_content, 'DVD_list': DVD_list, 'is_mobile_allowed': True, 'approx_vid_count': models.Video.approx_count(), 'exercise_count': models.Exercise.get_count(), 'link_heat': self.request_bool("heat", default=False), } self.render_jinja2_template('homepage.html', template_values)
def get(self): if not self.request_bool("map", default=False): return self.redirect("/#browse") user_data = models.UserData.current() or models.UserData.pre_phantom() user_exercise_graph = models.UserExerciseGraph.get(user_data) show_review_drawer = (not user_exercise_graph.has_completed_review()) template_values = { 'graph_dict_data': exercise_graph_dict_json(user_data), 'user_data': user_data, 'expanded_all_exercises': user_data.expanded_all_exercises, 'map_coords': json.dumps(knowledgemap.deserializeMapCoords(user_data.map_coords)), 'selected_nav_link': 'practice', 'show_review_drawer': show_review_drawer, } if show_review_drawer: template_values['review_statement'] = u"לשלוט בחומר" template_values['review_call_to_action'] = u"לך על זה" bingo('suggested_activity_exercises_landing') self.render_jinja2_template('viewexercises.html', template_values)
def get(self): user_data = (user_models.UserData.current() or user_models.UserData.pre_phantom()) user_exercise_graph = exercise_models.UserExerciseGraph.get(user_data) show_review_drawer = (not user_exercise_graph.has_completed_review()) template_values = { # TODO: should be camel cased once entire knowledgemap.js codebase # is switched to camel case 'map_coords': jsonify( deserializeMapCoords(user_data.map_coords), camel_cased=False), 'topic_graph_json': jsonify( topics_layout(user_data, user_exercise_graph), camel_cased=False), 'graph_dict_data': exercise_graph_dict_json(user_data), 'user_data': user_data, 'selected_nav_link': 'practice', 'show_review_drawer': show_review_drawer, } if show_review_drawer: template_values['review_statement'] = 'Attain mastery' template_values['review_call_to_action'] = "I'll do it" bingo('suggested_activity_exercises_landing') self.render_jinja2_template('viewexercises.html', template_values)
def get(self): user_data = (user_models.UserData.current() or user_models.UserData.pre_phantom()) user_exercise_graph = exercise_models.UserExerciseGraph.get(user_data) show_review_drawer = (not user_exercise_graph.has_completed_review()) template_values = { # TODO: should be camel cased once entire knowledgemap.js codebase # is switched to camel case 'map_coords': jsonify(deserializeMapCoords(user_data.map_coords), camel_cased=False), 'topic_graph_json': jsonify(topics_layout(user_data, user_exercise_graph), camel_cased=False), 'graph_dict_data': exercise_graph_dict_json(user_data), 'user_data': user_data, 'selected_nav_link': 'practice', 'show_review_drawer': show_review_drawer, } if show_review_drawer: template_values['review_statement'] = 'Attain mastery' template_values['review_call_to_action'] = "I'll do it" bingo('suggested_activity_exercises_landing') self.render_jinja2_template('viewexercises.html', template_values)
def hint_problem_number(exercise_name, problem_number): user_data = models.UserData.current() if user_data: exercise = models.Exercise.get_by_name(exercise_name) user_exercise = user_data.get_or_insert_exercise(exercise) if user_exercise and problem_number: prev_user_exercise_graph = models.UserExerciseGraph.get(user_data) attempt_number = request.request_int("attempt_number") count_hints = request.request_int("count_hints") user_exercise, user_exercise_graph, goals_updated = attempt_problem( user_data, user_exercise, problem_number, attempt_number, request.request_string("attempt_content"), request.request_string("sha1"), request.request_string("seed"), request.request_bool("complete"), count_hints, int(request.request_float("time_taken")), request.request_string("non_summative"), request.request_string("problem_type"), request.remote_addr, ) user_states = user_exercise_graph.states(exercise.name) review_mode = request.request_bool("review_mode", default=False) exercise_message_html = templatetags.exercise_message(exercise, user_exercise_graph, review_mode=review_mode) add_action_results(user_exercise, { "exercise_message_html": exercise_message_html, "exercise_state": { "state": [state for state in user_states if user_states[state]], "template" : exercise_message_html, } }) # A hint will count against the user iff they haven't attempted the question yet and it's their first hint if attempt_number == 0 and count_hints == 1: bingo("hints_costly_hint") bingo("hints_costly_hint_binary") return user_exercise logging.warning("Problem %d attempted with no user_data present", problem_number) return unauthorized_response()
def hint_problem_number(exercise_name, problem_number): user_data = models.UserData.current() if user_data: exercise = models.Exercise.get_by_name(exercise_name) user_exercise = user_data.get_or_insert_exercise(exercise) if user_exercise and problem_number: attempt_number = request.request_int("attempt_number") count_hints = request.request_int("count_hints") user_exercise, user_exercise_graph = attempt_problem( user_data, user_exercise, problem_number, attempt_number, request.request_string("attempt_content"), request.request_string("sha1"), request.request_string("seed"), request.request_bool("complete"), count_hints, int(request.request_float("time_taken")), request.request_string("non_summative"), request.request_string("problem_type"), request.remote_addr, ) add_action_results( user_exercise, { "exercise_message_html": templatetags.exercise_message( exercise, user_data.coaches, user_exercise_graph.states(exercise.name)), }) # A hint will count against the user iff they haven't attempted the question yet and it's their first hint if attempt_number == 0 and count_hints == 1: bingo("hints_costly_hint") bingo("hints_costly_hint_binary") return user_exercise logging.warning("Problem %d attempted with no user_data present", problem_number) return unauthorized_response()
def get(self, email_or_username=None, subpath=None): """Render a student profile. Keyword arguments: email_or_username -- matches the first grouping in /profile/(.+?)/(.*) subpath -- matches the second grouping, and is ignored server-side, but is used to route client-side """ current_user_data = UserData.current() or UserData.pre_phantom() if current_user_data.is_pre_phantom and email_or_username is None: # Pre-phantom users don't have any profiles - just redirect them # to the homepage if they try to view their own. self.redirect(util.create_login_url(self.request.uri)) return if not email_or_username: user_data = current_user_data elif email_or_username == 'nouser' and current_user_data.is_phantom: user_data = current_user_data else: user_data = UserData.get_from_url_segment(email_or_username) if (models.UniqueUsername.is_valid_username(email_or_username) and user_data and user_data.username and user_data.username != email_or_username): # The path segment is a username and resolved to the user, # but is not actually their canonical name. Redirect to the # canonical version. if subpath: self.redirect("/profile/%s/%s" % (user_data.username, subpath)) else: self.redirect("/profile/%s" % user_data.username) return profile = UserProfile.from_user(user_data, current_user_data) if profile is None: self.render_jinja2_template('noprofile.html', {}) return is_self = user_data.user_id == current_user_data.user_id show_intro = False if is_self: bingo([ 'suggested_activity_visit_profile', ]) promo_record = models.PromoRecord.get_for_values( "New Profile Promo", user_data.user_id) if promo_record is None: # The user has never seen the new profile page! Show a tour. if subpath: # But if they're not on the root profile page, force them. self.redirect("/profile") return show_intro = True models.PromoRecord.record_promo("New Profile Promo", user_data.user_id, skip_check=True) has_full_access = is_self or user_data.is_visible_to(current_user_data) tz_offset = self.request_int("tz_offset", default=0) template_values = { 'show_intro': show_intro, 'profile': profile, 'tz_offset': tz_offset, 'count_videos': models.Setting.count_videos(), 'count_exercises': models.Exercise.get_count(), 'user_data_student': user_data if has_full_access else None, 'profile_root': user_data.profile_root, "view": self.request_string("view", default=""), } self.render_jinja2_template('viewprofile.html', template_values)
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 get(self, exid=None): # TODO(david): Is there some webapp2 magic that will allow me not to # repeat this URL string in main.py? review_mode = self.request.path == "/review" if not exid and not review_mode: self.redirect("/exercise/%s" % self.request_string("exid", default="addition_1")) return user_data = models.UserData.current() or models.UserData.pre_phantom() user_exercise_graph = models.UserExerciseGraph.get(user_data) if review_mode: # Take the first review exercise if available exid = (user_exercise_graph.review_exercise_names() or user_exercise_graph.proficient_exercise_names() or ["addition_1"])[0] reviews_left_count = user_exercise_graph.reviews_left_count() exercise = models.Exercise.get_by_name(exid) if not exercise: raise MissingExerciseException("Missing exercise w/ exid '%s'" % exid) user_exercise = user_data.get_or_insert_exercise(exercise) # Cache these so we don't have to worry about future lookups user_exercise.exercise_model = exercise user_exercise._user_data = user_data user_exercise._user_exercise_graph = user_exercise_graph user_exercise.summative = exercise.summative # Temporarily work around in-app memory caching bug exercise.user_exercise = None problem_number = self.request_int('problem_number', default=(user_exercise.total_done + 1)) user_data_student = self.request_student_user_data(legacy=True) or user_data if user_data_student.key_email != user_data.key_email and not user_data_student.is_visible_to(user_data): user_data_student = user_data viewing_other = user_data_student.key_email != user_data.key_email # Can't view your own problems ahead of schedule if not viewing_other and problem_number > user_exercise.total_done + 1: problem_number = user_exercise.total_done + 1 # When viewing another student's problem or a problem out-of-order, show read-only view read_only = viewing_other or problem_number != (user_exercise.total_done + 1) exercise_template_html = exercise_template() exercise_body_html, exercise_inline_script, exercise_inline_style, data_require, sha1 = exercise_contents(exercise) user_exercise.exercise_model.sha1 = sha1 user_exercise.exercise_model.related_videos = map(lambda exercise_video: exercise_video.video, user_exercise.exercise_model.related_videos_fetch()) for video in user_exercise.exercise_model.related_videos: video.id = video.key().id() renderable = True if read_only: # Override current problem number and user being inspected # so proper exercise content will be generated user_exercise.total_done = problem_number - 1 user_exercise.user = user_data_student.user user_exercise.read_only = True if not self.request_bool("renderable", True): # We cannot render old problems that were created in the v1 exercise framework. renderable = False query = models.ProblemLog.all() query.filter("user = "******"exercise = ", exid) # adding this ordering to ensure that query is served by an existing index. # could be ok if we remove this query.order('time_done') problem_logs = query.fetch(500) problem_log = None for p in problem_logs: if p.problem_number == problem_number: problem_log = p break user_activity = [] previous_time = 0 if not problem_log or not hasattr(problem_log, "hint_after_attempt_list"): renderable = False else: # Don't include incomplete information problem_log.hint_after_attempt_list = filter(lambda x: x != -1, problem_log.hint_after_attempt_list) while len(problem_log.hint_after_attempt_list) and problem_log.hint_after_attempt_list[0] == 0: user_activity.append([ "hint-activity", "0", max(0, problem_log.hint_time_taken_list[0] - previous_time) ]) previous_time = problem_log.hint_time_taken_list[0] problem_log.hint_after_attempt_list.pop(0) problem_log.hint_time_taken_list.pop(0) # For each attempt, add it to the list and then add any hints # that came after it for i in range(0, len(problem_log.attempts)): user_activity.append([ "correct-activity" if problem_log.correct else "incorrect-activity", unicode(problem_log.attempts[i] if problem_log.attempts[i] else 0), max(0, problem_log.time_taken_attempts[i] - previous_time) ]) previous_time = 0 # Here i is 0-indexed but problems are numbered starting at 1 while (len(problem_log.hint_after_attempt_list) and problem_log.hint_after_attempt_list[0] == i + 1): user_activity.append([ "hint-activity", "0", max(0, problem_log.hint_time_taken_list[0] - previous_time) ]) previous_time = problem_log.hint_time_taken_list[0] # easiest to just pop these instead of maintaining # another index into this list problem_log.hint_after_attempt_list.pop(0) problem_log.hint_time_taken_list.pop(0) user_exercise.user_activity = user_activity if problem_log.count_hints is not None: user_exercise.count_hints = problem_log.count_hints user_exercise.current = problem_log.sha1 == sha1 else: # Not read_only suggested_exercise_names = user_exercise_graph.suggested_exercise_names() if exercise.name in suggested_exercise_names: bingo('suggested_activity_visit_suggested_exercise') is_webos = self.is_webos() browser_disabled = is_webos or self.is_older_ie() renderable = renderable and not browser_disabled url_pattern = "/exercise/%s?student_email=%s&problem_number=%d" user_exercise.previous_problem_url = url_pattern % \ (exid, user_data_student.key_email, problem_number - 1) user_exercise.next_problem_url = url_pattern % \ (exid, user_data_student.key_email, problem_number + 1) user_exercise_json = jsonify.jsonify(user_exercise) template_values = { 'exercise': exercise, 'user_exercise_json': user_exercise_json, 'exercise_body_html': exercise_body_html, 'exercise_template_html': exercise_template_html, 'exercise_inline_script': exercise_inline_script, 'exercise_inline_style': exercise_inline_style, 'data_require': data_require, 'read_only': read_only, 'selected_nav_link': 'practice', 'browser_disabled': browser_disabled, 'is_webos': is_webos, 'renderable': renderable, 'issue_labels': ('Component-Code,Exercise-%s,Problem-%s' % (exid, problem_number)), 'alternate_hints_treatment': ab_test('Hints or Show Solution Dec 10', ViewExercise._hints_ab_test_alternatives, ViewExercise._hints_conversion_names, ViewExercise._hints_conversion_types, 'Hints or Show Solution Nov 5'), 'reviews_left_count': reviews_left_count if review_mode else "null", } self.render_jinja2_template("exercise_template.html", template_values)
def get(self, readable_id=""): # This method displays a video in the context of a particular playlist. # To do that we first need to find the appropriate playlist. If we aren't # given the playlist title in a query param, we need to find a playlist that # the video is a part of. That requires finding the video, given it readable_id # or, to support old URLs, it's youtube_id. video = None playlist = None video_id = self.request.get('v') playlist_title = self.request_string('playlist', default="") or self.request_string('p', default="") readable_id = urllib.unquote(readable_id) readable_id = re.sub('-+$', '', readable_id) # remove any trailing dashes (see issue 1140) # If either the readable_id or playlist title is missing, # redirect to the canonical URL that contains them redirect_to_canonical_url = False if video_id: # Support for old links query = Video.all() query.filter('youtube_id =', video_id) video = query.get() if not video: raise MissingVideoException("Missing video w/ youtube id '%s'" % video_id) readable_id = video.readable_id playlist = video.first_playlist() if not playlist: raise MissingVideoException("Missing video w/ youtube id '%s'" % video_id) redirect_to_canonical_url = True if playlist_title is not None and len(playlist_title) > 0: query = Playlist.all().filter('title =', playlist_title) key_id = 0 for p in query: if p.key().id() > key_id and not p.youtube_id.endswith('_player'): playlist = p key_id = p.key().id() # If a playlist_title wasn't specified or the specified playlist wasn't found # use the first playlist for the requested video. if playlist is None: # Get video by readable_id just to get the first playlist for the video video = Video.get_for_readable_id(readable_id) if video is None: raise MissingVideoException("Missing video '%s'" % readable_id) playlist = video.first_playlist() if not playlist: raise MissingVideoException("Missing video '%s'" % readable_id) redirect_to_canonical_url = True exid = self.request_string('exid', default=None) if redirect_to_canonical_url: qs = {'playlist': playlist.title} if exid: qs['exid'] = exid urlpath = "/video/%s" % urllib.quote(readable_id) url = urlparse.urlunparse(('', '', urlpath, '', urllib.urlencode(qs), '')) self.redirect(url, True) return # If we got here, we have a readable_id and a playlist_title, so we can display # the playlist and the video in it that has the readable_id. Note that we don't # query the Video entities for one with the requested readable_id because in some # cases there are multiple Video objects in the datastore with the same readable_id # (e.g. there are 2 "Order of Operations" videos). videos = VideoPlaylist.get_cached_videos_for_playlist(playlist) previous_video = None next_video = None for v in videos: if v.readable_id == readable_id: v.selected = 'selected' video = v elif video is None: previous_video = v elif next_video is None: next_video = v if video is None: raise MissingVideoException("Missing video '%s'" % readable_id) if App.offline_mode: video_path = "/videos/" + get_mangled_playlist_name(playlist_title) + "/" + video.readable_id + ".flv" else: video_path = video.download_video_url() if video.description == video.title: video.description = None related_exercises = video.related_exercises() button_top_exercise = None if related_exercises: def ex_to_dict(exercise): return { 'name': exercise.display_name, 'url': exercise.relative_url, } button_top_exercise = ex_to_dict(related_exercises[0]) user_video = UserVideo.get_for_video_and_user_data(video, UserData.current(), insert_if_missing=True) awarded_points = 0 if user_video: awarded_points = user_video.points template_values = { 'playlist': playlist, 'video': video, 'videos': videos, 'video_path': video_path, 'video_points_base': consts.VIDEO_POINTS_BASE, 'button_top_exercise': button_top_exercise, 'related_exercises': [], # disabled for now 'previous_video': previous_video, 'next_video': next_video, 'selected_nav_link': 'watch', 'awarded_points': awarded_points, 'issue_labels': ('Component-Videos,Video-%s' % readable_id), 'author_profile': 'https://plus.google.com/103970106103092409324' } template_values = qa.add_template_values(template_values, self.request) bingo(['struggling_videos_landing', 'homepage_restructure_videos_landing']) self.render_jinja2_template('viewvideo.html', template_values)
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
def post(self): #---------------------------------------------- # get user info // redirect for logged out user if not self.session.has_key("userKey"): return self.redirect('/') user_identifier = self.session["userKey"] user = UserProfile.get_by_key_name(user_identifier) if user is None: return self.redirect('/') #---------------------------------------------- # create input object input = {} input["email"] = self.request.get("email") input["phone"] = re.sub('[^\d]+',"",self.request.get("phone")).lstrip('1') #sanitizing for us here input["smsPref"] = self.request.get("smsPref") input["emailPref"] = self.request.get("emailPref") input["terms"] = self.request.get("terms") input["inviteCoupleEmail"] = self.request.get("inviteCoupleEmail") input["inviteCoupleMsg"] = self.request.get("inviteCoupleMsg")[:140] self.context["input"] = input # get all current partners partners = [] for partner in user.partners: partners.append(partner) self.context["partners"] = partners # get all previously sent invites, but don't add to context until we know if we're sending one now sentInvites = EmailInvite.all().filter('inviter =',user).filter('status IN',(1,2)).fetch(1000) # create error object error = self.validateSettings(input,user,partners,sentInvites) self.context["error"] = error # create some blank variables for couple invite stuff self.context["partner"] = "" self.context["inviteCouple"] = "" self.context["inviter"] = "" # if no errors, save the info if not len(error): user.email = input["email"] user.phone = input["phone"] user.emailPref = bool(input["emailPref"]) user.smsPref = bool(input["smsPref"]) if input["inviteCoupleEmail"] and not len(partners): # create new emailInvite object thisInvite = EmailInvite(key_name=uniqid(14), type = 1, #couple invite inviter = user, email = input["inviteCoupleEmail"], message = input["inviteCoupleMsg"] ) thisInvite.send() sentInvites.append(thisInvite) # if this was first-time registration, send to homepage if user.status == 0: # score this as a new registration in our gae bingo module from gae_bingo.gae_bingo import bingo bingo("registered") user.status = 1 user.put(); logging.info("--------------------------------") logging.info("setting justRegistered to True") self.session["justRegistered"] = True self.session.save() return self.redirect('/') user.put() input["success"] = "Your changes have been saved." # do the couples stuff if "inviteCouple" in self.session: thisInvite = EmailInvite.get_by_key_name(self.session["inviteCouple"]) if thisInvite is not None: logging.info("definining firstname here") self.context["inviteCouple"] = thisInvite self.context["inviter"] = thisInvite.inviter # get completed tasks tasks = user.getCompletedTasks() if user.partner: partnerTasks = user.partner.getCompletedTasks() for partnerTask in partnerTasks: tasks.append(partnerTask) self.context["tasks"] = tasks self.context["sentInvites"] = sentInvites self.context["page"] = "settings" self.context["user"] = user self.context["bannerHeader"] = True self.render('settings.html')
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, readable_id=""): # This method displays a video in the context of a particular topic. # To do that we first need to find the appropriate topic. If we aren't # given the topic title in a query param, we need to find a topic that # the video is a part of. That requires finding the video, given it readable_id # or, to support old URLs, it's youtube_id. video = None topic = None video_id = self.request.get('v') topic_id = self.request_string('topic', default="") readable_id = urllib.unquote(readable_id).decode("utf-8") readable_id = re.sub( '-+$', '', readable_id) # remove any trailing dashes (see issue 1140) # If either the readable_id or topic title is missing, # redirect to the canonical URL that contains them redirect_to_canonical_url = False if video_id: # Support for old links query = Video.all() query.filter('youtube_id =', video_id) video = query.get() if not video: raise MissingVideoException( "Missing video w/ youtube id '%s'" % video_id) readable_id = video.readable_id topic = video.first_topic() if not topic: raise MissingVideoException( "No topic has video w/ youtube id '%s'" % video_id) redirect_to_canonical_url = True if topic_id is not None and len(topic_id) > 0: topic = Topic.get_by_id(topic_id) key_id = 0 if not topic else topic.key().id() # If a topic_id wasn't specified or the specified topic wasn't found # use the first topic for the requested video. if topic is None: # Get video by readable_id just to get the first topic for the video video = Video.get_for_readable_id(readable_id) if video is None: raise MissingVideoException("Missing video '%s'" % readable_id) topic = video.first_topic() if not topic: raise MissingVideoException("No topic has video '%s'" % readable_id) redirect_to_canonical_url = True exid = self.request_string('exid', default=None) if redirect_to_canonical_url: qs = {'topic': topic.id} if exid: qs['exid'] = exid urlpath = "/video/%s" % urllib.quote(readable_id) url = urlparse.urlunparse( ('', '', urlpath, '', urllib.urlencode(qs), '')) self.redirect(url, True) return # If we got here, we have a readable_id and a topic, so we can display # the topic and the video in it that has the readable_id. Note that we don't # query the Video entities for one with the requested readable_id because in some # cases there are multiple Video objects in the datastore with the same readable_id # (e.g. there are 2 "Order of Operations" videos). videos = Topic.get_cached_videos_for_topic(topic) previous_video = None next_video = None for v in videos: if v.readable_id == readable_id: v.selected = 'selected' video = v elif video is None: previous_video = v else: next_video = v break # If we're at the beginning or end of a topic, show the adjacent topic. # previous_topic/next_topic are the topic to display. # previous_video_topic/next_video_topic are the subtopics the videos # are actually in. previous_topic = None previous_video_topic = None next_topic = None next_video_topic = None if not previous_video: previous_topic = topic while not previous_video: previous_topic = previous_topic.get_previous_topic() if previous_topic: (previous_video, previous_video_topic ) = previous_topic.get_last_video_and_topic() else: break if not next_video: next_topic = topic while not next_video: next_topic = next_topic.get_next_topic() if next_topic: (next_video, next_video_topic ) = next_topic.get_first_video_and_topic() else: break if video is None: raise MissingVideoException("Missing video '%s'" % readable_id) if App.offline_mode: video_path = "/videos/" + get_mangled_topic_name( topic.id) + "/" + video.readable_id + ".flv" else: video_path = video.download_video_url() if video.description == video.title: video.description = None related_exercises = video.related_exercises() button_top_exercise = None if related_exercises: def ex_to_dict(exercise): return { 'name': exercise.display_name, 'url': exercise.relative_url, } button_top_exercise = ex_to_dict(related_exercises[0]) user_video = UserVideo.get_for_video_and_user_data( video, UserData.current(), insert_if_missing=True) awarded_points = 0 if user_video: awarded_points = user_video.points subtitles_key_name = VideoSubtitles.get_key_name( 'en', video.youtube_id) subtitles = VideoSubtitles.get_by_key_name(subtitles_key_name) subtitles_json = None if subtitles: subtitles_json = subtitles.load_json() template_values = { 'topic': topic, 'video': video, 'videos': videos, 'video_path': video_path, 'video_points_base': consts.VIDEO_POINTS_BASE, 'subtitles_json': subtitles_json, 'button_top_exercise': button_top_exercise, 'related_exercises': [], # disabled for now 'previous_topic': previous_topic, 'previous_video': previous_video, 'previous_video_topic': previous_video_topic, 'next_topic': next_topic, 'next_video': next_video, 'next_video_topic': next_video_topic, 'selected_nav_link': 'watch', 'awarded_points': awarded_points, 'issue_labels': ('Component-Videos,Video-%s' % readable_id), 'author_profile': 'https://plus.google.com/103970106103092409324' } template_values = qa.add_template_values(template_values, self.request) bingo([ 'struggling_videos_landing', 'suggested_activity_videos_landing', 'suggested_activity_videos_landing_binary', ]) self.render_jinja2_template('viewvideo.html', template_values)
def get(self, readable_id=""): # This method displays a video in the context of a particular topic. # To do that we first need to find the appropriate topic. If we aren't # given the topic title in a query param, we need to find a topic that # the video is a part of. That requires finding the video, given it readable_id # or, to support old URLs, it's youtube_id. video = None topic = None video_id = self.request.get('v') topic_id = self.request_string('topic', default="") readable_id = urllib.unquote(readable_id).decode("utf-8") readable_id = re.sub('-+$', '', readable_id) # remove any trailing dashes (see issue 1140) # If either the readable_id or topic title is missing, # redirect to the canonical URL that contains them redirect_to_canonical_url = False if video_id: # Support for old links query = Video.all() query.filter('youtube_id =', video_id) video = query.get() if not video: raise MissingVideoException("Missing video w/ youtube id '%s'" % video_id) readable_id = video.readable_id topic = video.first_topic() if not topic: raise MissingVideoException("No topic has video w/ youtube id '%s'" % video_id) redirect_to_canonical_url = True if topic_id is not None and len(topic_id) > 0: topic = Topic.get_by_id(topic_id) key_id = 0 if not topic else topic.key().id() # If a topic_id wasn't specified or the specified topic wasn't found # use the first topic for the requested video. if topic is None: # Get video by readable_id just to get the first topic for the video video = Video.get_for_readable_id(readable_id) if video is None: raise MissingVideoException("Missing video '%s'" % readable_id) topic = video.first_topic() if not topic: raise MissingVideoException("No topic has video '%s'" % readable_id) redirect_to_canonical_url = True exid = self.request_string('exid', default=None) if redirect_to_canonical_url: qs = {'topic': topic.id} if exid: qs['exid'] = exid urlpath = "/video/%s" % urllib.quote(readable_id) url = urlparse.urlunparse(('', '', urlpath, '', urllib.urlencode(qs), '')) self.redirect(url, True) return # If we got here, we have a readable_id and a topic, so we can display # the topic and the video in it that has the readable_id. Note that we don't # query the Video entities for one with the requested readable_id because in some # cases there are multiple Video objects in the datastore with the same readable_id # (e.g. there are 2 "Order of Operations" videos). videos = Topic.get_cached_videos_for_topic(topic) previous_video = None next_video = None for v in videos: if v.readable_id == readable_id: v.selected = 'selected' video = v elif video is None: previous_video = v else: next_video = v break # If we're at the beginning or end of a topic, show the adjacent topic. # previous_topic/next_topic are the topic to display. # previous_video_topic/next_video_topic are the subtopics the videos # are actually in. previous_topic = None previous_video_topic = None next_topic = None next_video_topic = None if not previous_video: previous_topic = topic while not previous_video: previous_topic = previous_topic.get_previous_topic() if previous_topic: (previous_video, previous_video_topic) = previous_topic.get_last_video_and_topic() else: break if not next_video: next_topic = topic while not next_video: next_topic = next_topic.get_next_topic() if next_topic: (next_video, next_video_topic) = next_topic.get_first_video_and_topic() else: break if video is None: raise MissingVideoException("Missing video '%s'" % readable_id) if App.offline_mode: video_path = "/videos/" + get_mangled_topic_name(topic.id) + "/" + video.readable_id + ".flv" else: video_path = video.download_video_url() if video.description == video.title: video.description = None related_exercises = video.related_exercises() button_top_exercise = None if related_exercises: def ex_to_dict(exercise): return { 'name': exercise.display_name, 'url': exercise.relative_url, } button_top_exercise = ex_to_dict(related_exercises[0]) user_video = UserVideo.get_for_video_and_user_data(video, UserData.current(), insert_if_missing=True) awarded_points = 0 if user_video: awarded_points = user_video.points template_values = { 'topic': topic, 'video': video, 'videos': videos, 'video_path': video_path, 'video_points_base': consts.VIDEO_POINTS_BASE, 'button_top_exercise': button_top_exercise, 'related_exercises': [], # disabled for now 'previous_topic': previous_topic, 'previous_video': previous_video, 'previous_video_topic': previous_video_topic, 'next_topic': next_topic, 'next_video': next_video, 'next_video_topic': next_video_topic, 'selected_nav_link': 'watch', 'awarded_points': awarded_points, 'issue_labels': ('Component-Videos,Video-%s' % readable_id), 'author_profile': 'https://plus.google.com/103970106103092409324', 'is_mobile_allowed': True, } template_values = qa.add_template_values(template_values, self.request) bingo([ 'struggling_videos_landing', 'suggested_activity_videos_landing', 'suggested_activity_videos_landing_binary', ]) self.render_jinja2_template('viewvideo.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 get(self): thumbnail_link_sets = new_and_noteworthy_link_sets() # If all else fails, just show the TED talk on the homepage marquee_video = { "youtube_id": "gM95HHI4gLk", "href": "/video?v=%s" % "gM95HHI4gLk", "thumb_urls": models.Video.youtube_thumbnail_urls("gM95HHI4gLk"), "title": "Salman Khan talk at TED 2011", "key": "", } if len(thumbnail_link_sets) > 1: day = datetime.datetime.now().day # Switch up the first 4 New & Noteworthy videos on a daily basis current_link_set_offset = day % len(thumbnail_link_sets) # Switch up the marquee video on a daily basis marquee_videos = [] for thumbnail_link_set in thumbnail_link_sets: marquee_videos += filter(lambda item: item["marquee"], thumbnail_link_set) if marquee_videos: marquee_video = marquee_videos[day % len(marquee_videos)] marquee_video["selected"] = True if len(thumbnail_link_sets[current_link_set_offset] ) < ITEMS_PER_SET: # If the current offset lands on a set of videos that isn't a full set, just start # again at the first set, which is most likely full. current_link_set_offset = 0 thumbnail_link_sets = thumbnail_link_sets[ current_link_set_offset:] + thumbnail_link_sets[: current_link_set_offset] # Only running restructure A/B test for non-mobile clients render_type = 'original' if not self.is_mobile_capable(): render_type = HomepageRestructuringExperiment.get_render_type() bingo('homepage_restructure_visits') if render_type == 'original': library_content = library.library_content_html() else: library_content = library.playlist_content_html() template_values = { 'marquee_video': marquee_video, 'thumbnail_link_sets': thumbnail_link_sets, 'library_content': library_content, 'DVD_list': DVD_list, 'is_mobile_allowed': True, 'approx_vid_count': models.Video.approx_count(), 'exercise_count': models.Exercise.get_count(), 'link_heat': self.request_bool("heat", default=False), } self.render_jinja2_template('homepage.html', template_values)
def convert_in(self): bingo(self.request.get("conversion_name")) return True
def get(self): bingo("new button design") self.response.write('Thanks!')