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)
Example #2
0
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))
Example #3
0
    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))
Example #4
0
    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))
Example #5
0
 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)
Example #6
0
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
Example #7
0
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
Example #8
0
    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)
Example #9
0
    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)