def raw_exercise_contents(exercise_file): if templatetags.use_compressed_packages(): exercises_dir = "../khan-exercises/exercises-packed" safe_to_cache = True else: exercises_dir = "../khan-exercises/exercises" safe_to_cache = False path = os.path.join(os.path.dirname(__file__), "%s/%s" % (exercises_dir, exercise_file)) f = None contents = "" try: f = open(path) contents = f.read() except: raise MissingExerciseException( "Missing exercise file for exid '%s'" % exercise_file) finally: if f: f.close() if not len(contents): raise MissingExerciseException( "Missing exercise content for exid '%s'" % exercise_file) if safe_to_cache: return contents else: # we are displaying an unpacked exercise, either locally or in prod # with a querystring override. It's unsafe to cache this. return layer_cache.UncachedResult(contents)
def exercise_contents(exercise): contents = raw_exercise_contents("%s.html" % exercise.name) re_data_require = re.compile("^<html.*(data-require=\".*\").*>", re.MULTILINE) match_data_require = re_data_require.search(contents) data_require = match_data_require.groups()[0] if match_data_require else "" re_body_contents = re.compile("<body>(.*)</body>", re.DOTALL) match_body_contents = re_body_contents.search(contents) body_contents = match_body_contents.groups()[0] re_script_contents = re.compile("<script[^>]*>(.*?)</script>", re.DOTALL) list_script_contents = re_script_contents.findall(contents) script_contents = ";".join(list_script_contents) re_style_contents = re.compile("<style[^>]*>(.*?)</style>", re.DOTALL) list_style_contents = re_style_contents.findall(contents) style_contents = "\n".join(list_style_contents) sha1 = hashlib.sha1(contents).hexdigest() if not len(body_contents): raise MissingExerciseException( "Missing exercise body in content for exid '%s'" % exercise.name) return map( lambda s: s.decode('utf-8'), (body_contents, script_contents, style_contents, data_require, sha1))
def get(self, exid=None): if not exid: exid = self.request_string("exid") exercise = exercise_models.Exercise.get_by_name(exid) if not exercise: raise MissingExerciseException("Missing exercise w/ exid '%s'" % exid) topic = exercise.first_topic() if not topic: raise MissingExerciseException("Exercise '%s' is missing a topic" % exid) self.redirect("/%s/e/%s?%s" % (topic.get_extended_slug(), urllib.quote(exid), self.request.query_string))
def get(self, topic_id): topic = topic_models.Topic.get_by_id(topic_id) if not topic: raise MissingExerciseException("Missing topic w/ id '%s'" % topic_id) self.redirect("/%s/e?%s" % (topic.get_extended_slug(), self.request.query_string))
def get(self, exploration=None): if not exploration: self.render_jinja2_template('labs/explorations/index.html', {}) elif exploration in EXPLORATIONS: self.render_jinja2_template( 'labs/explorations/%s.html' % exploration, {}) else: raise MissingExerciseException('Missing exploration %s' % exploration)
def raw_exercise_contents(exercise_file): path = os.path.join(os.path.dirname(__file__), "khan-exercises/exercises/%s" % exercise_file) f = None contents = "" try: f = open(path) contents = f.read() except: raise MissingExerciseException("Missing exercise file for exid '%s'" % exercise_file) finally: if f: f.close() if not len(contents): raise MissingExerciseException( "Missing exercise content for exid '%s'" % exercise.name) return contents
def exercise_template(): path = os.path.join(os.path.dirname(__file__), "khan-exercises/exercises/khan-exercise.html") contents = "" f = open(path) if f: try: contents = f.read() finally: f.close() if not len(contents): raise MissingExerciseException("Missing exercise template") return contents
def get(self, topic_path, exid=None): title = None description = None review_mode = "review" == topic_path practice_mode = bool(exid) practice_exercise = None topic = None topic_exercise_badge = None user_exercises = None if review_mode: title = "Review" else: topic_path_list = topic_path.split('/') topic_id = topic_path_list[-1] if len(topic_id) > 0: topic = topic_models.Topic.get_by_id(topic_id) # Topics are required if not topic: raise MissingExerciseException( "Exercise '%s' is missing a topic" % exid) title = topic.standalone_title topic_exercise_badge = topic.get_exercise_badge() if exid: practice_exercise = exercise_models.Exercise.get_by_name(exid) # Exercises are not required but must be valid if supplied if not practice_exercise: raise MissingExerciseException( "Missing exercise w/ exid '%s'" % exid) title = practice_exercise.display_name description = practice_exercise.description user_data = user_models.UserData.current( ) or user_models.UserData.pre_phantom() if practice_mode: # Practice mode involves a single exercise only user_exercises = exercise_models.UserExercise.next_in_practice( user_data, practice_exercise) elif review_mode: # Review mode sends down up to a certain limit of review exercises user_exercises = exercise_models.UserExercise.next_in_review( user_data, n=MAX_CARDS_PER_REVIEW_STACK) else: # Topics mode context switches between multiple exercises user_exercises = exercise_models.UserExercise.next_in_topic( user_data, topic) if len(user_exercises) == 0: # If something has gone wrong and we didn't get any UserExercises, # somebody could've hit the /review URL without any review problems # or we hit another issue. Send 'em back to the dashboard for now. self.redirect("/exercisedashboard") return stack = get_dummy_stack(review_mode) cards = (get_review_cards(user_exercises) if review_mode else get_problem_cards(user_exercises)) # We have to compute this and save it before JSON-ifiying because it # modifies user_exercises, which we JSONify as well. problem_history_values = (self.problem_history_values( user_data, user_exercises[0]) if practice_mode else {}) template_values = { "title": title, "description": description, "selected_nav_link": "practice", "renderable": True, "read_only": False, "stack_json": jsonify(stack, camel_cased=True), "cards_json": jsonify(cards, camel_cased=True), "review_mode_json": jsonify(review_mode, camel_cased=True), "practice_mode_json": jsonify(practice_mode, camel_cased=True), "topic_json": jsonify(topic, camel_cased=True), "topic_exercise_badge_json": jsonify(topic_exercise_badge, camel_cased=True), "practice_exercise_json": jsonify(practice_exercise, camel_cased=True), "user_data_json": jsonify(user_data, camel_cased=True), "user_exercises_json": jsonify(user_exercises, camel_cased=True), "show_intro": user_data.is_phantom or user_data.is_pre_phantom, } # Add disabled browser warnings template_values.update(self.browser_support_values()) # Add history data to template context if we're viewing an old problem template_values.update(problem_history_values) self.render_jinja2_template("exercises/exercise_template.html", template_values)
def get(self): user_data = models.UserData.current() or models.UserData.pre_phantom() exid = self.request_string("exid", default="addition_1") 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 this so we don't have to worry about future lookups user_exercise.exercise_model = exercise user_exercise._user_data = user_data 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_user_data( "student_email") 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 is_webos = self.is_webos() browser_disabled = is_webos or self.is_older_ie() renderable = renderable and not browser_disabled url_pattern = "/exercises?exid=%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', ViewExercise._hints_ab_test_alternatives, ViewExercise._hints_conversion_names, ViewExercise._hints_conversion_types) } self.render_jinja2_template("exercise_template.html", template_values)