Example #1
0
    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")
Example #2
0
    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)
Example #3
0
    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)
Example #4
0
    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)
Example #5
0
    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)
Example #6
0
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()
Example #7
0
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()
Example #8
0
    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)
Example #9
0
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
Example #10
0
    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)
Example #11
0
    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)
Example #12
0
    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)
Example #13
0
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
Example #14
0
    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')
Example #15
0
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
Example #16
0
    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)
Example #17
0
File: main.py Project: di445/server
    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)
Example #18
0
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
Example #19
0
    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)
Example #20
0
 def convert_in(self):
     bingo(self.request.get("conversion_name"))
     return True
Example #21
0
	def get(self):
		bingo("new button design")
		self.response.write('Thanks!')