Ejemplo n.º 1
0
def get_explore_recommendations(user, request):
    """Get the recommendations for the Explore section, and return them as a list.

    Logic:
    Looks at a preset distance away, beginning at 2 to exclude self recommendations, to 
    recommend a topic for exploration. Currently, the cap is a distance of 6 so that all
    recommendations will still be of moderate relatedness. This number is not permanent, and
    can be tweaked as needed.
    Args:
    user -- The current user as a facility user model object.

    """

    data = generate_recommendation_data()  #topic tree alg
    exercise_parents_table = get_exercise_parents_lookup_table(
    )  #for finding out subtopic ids
    recent_exercises = get_most_recent_exercises(user)  #most recent ex

    #simply getting a list of subtopics accessed by user
    recent_subtopics = list(
        set([
            exercise_parents_table[ex]['subtopic_id']
            for ex in recent_exercises if ex in exercise_parents_table
        ]))

    # Number of sub topic recommendations
    sampleNum = min(len(recent_subtopics), settings.TOPIC_RECOMMENDATION_SIZE)

    random_subtopics = random.sample(recent_subtopics, sampleNum)
    added = []  #keep track of what has been added (below)
    final = []  #final recommendations to return

    for subtopic_id in random_subtopics:

        # benjaoming: This seems to be done partly because subtopic_id is
        # included in the set of recommended subtopics.
        related_subtopics = data[subtopic_id]['related_subtopics'][
            2:7]  #get recommendations based on this, can tweak numbers!

        recommended_topic = next(
            topic for topic in related_subtopics
            if topic not in added and topic not in recent_subtopics)

        if recommended_topic:

            final.append({
                'suggested_topic':
                get_content_item(language=request.language,
                                 content_id=recommended_topic,
                                 topic=True) or {},
                'interest_topic': get_content_item(language=request.language,
                                                   content_id=subtopic_id,
                                                   topic=True) or {},
            })

            added.append(recommended_topic)

    return final
def get_next_recommendations(user, request):
    """Get the recommendations for the Next section, and return them as a list.

    Logic:
    Next recommendations are currently comprised of 3 main subgroups: group recommendations,
    struggling exercises, and topic tree based data. Group recommendations consist of 
    finding the most common item tackled immediately after the most recent item, struggling
    is determined by the "struggling" model attribute, and topic tree data is based off
    the graphical distance between neighboring exercise/topic nodes. 
    Args:
    user -- The current user as a facility user model object.

    """

    exercise_parents_table = get_exercise_parents_lookup_table()

    most_recent = get_most_recent_exercises(user)

    complete_exercises = set(get_completed_exercises(user))

    def filter_complete(ex):
        return ex not in complete_exercises

    if len(most_recent) > 0 and most_recent[0] in exercise_parents_table:
        current_subtopic = exercise_parents_table[most_recent[0]]['subtopic_id']
    else:
        current_subtopic = None

    #logic for recommendations based off of the topic tree structure
    if current_subtopic:
        topic_tree_based_data = generate_recommendation_data()[current_subtopic]['related_subtopics'][:settings.TOPIC_RECOMMENDATION_SIZE]
        topic_tree_based_data = get_exercises_from_topics(topic_tree_based_data)
    else:
        topic_tree_based_data = []
    
    #for checking that only exercises that have not been accessed are returned
    topic_tree_based_data = [ex for ex in topic_tree_based_data if ex not in most_recent or filter_complete(ex)]

    #logic to generate recommendations based on exercises student is struggling with
    struggling = filter(filter_complete, get_exercise_prereqs(get_struggling_exercises(user)))

    #logic to get recommendations based on group patterns, if applicable
    group = filter(filter_complete, get_group_recommendations(user))
  
    #now append titles and other metadata to each exercise id
    final = [] # final data to return
    for exercise_id in (group[:2] + struggling[:2] + topic_tree_based_data[:1]):  #notice the concatenation

        if exercise_id in exercise_parents_table:
            subtopic_id = exercise_parents_table[exercise_id]['subtopic_id']
            exercise = get_content_item(language=request.language, content_id=exercise_id)
            if exercise:
                exercise["topic"] = get_content_item(language=request.language, content_id=subtopic_id, topic=True) or {}
                final.append(exercise)


    #final recommendations are a combination of struggling, group filtering, and topic_tree filtering
    return final
Ejemplo n.º 3
0
    def alter_list_data_to_serialize(self, request, to_be_serialized):
        """
        Defines a hook to process list view data before being serialized.
        We pluck out the "user" and replace with the fields we're interested in (username, facility name, is_teacher),
          and pluck out the content_id and replace with the content_title (if found).
        This is to make the csv output more human friendly.
        :param request: HTTP request object
        :param to_be_serialized: the unprocessed list of objects that will be serialized
        :return: the _processed_ list of objects to serialize
        """

        filtered_bundles = [
            bundle for bundle in to_be_serialized["objects"]
            if (bundle.data["difficulty"], bundle.data["quality"]) != (0, 0)
        ]
        serializable_objects = []
        for bundle in filtered_bundles:
            user_id = bundle.data["user"].data["id"]
            user = self._facility_users.get(user_id)
            bundle.data["username"] = user.username
            bundle.data["facility_name"] = user.facility.name
            bundle.data["is_teacher"] = user.is_teacher
            bundle.data.pop("user")

            content_id = bundle.data.pop("content_id", None)
            content = get_content_item(language=request.language,
                                       content_id=content_id)
            bundle.data["content_title"] = content.get(
                "title", "Missing title") if content else "Unknown content"

            serializable_objects.append(bundle)

        to_be_serialized["objects"] = serializable_objects
        return to_be_serialized
Ejemplo n.º 4
0
    def alter_list_data_to_serialize(self, request, to_be_serialized):
        """
        Defines a hook to process list view data before being serialized.
        We pluck out the "user" and replace with the fields we're interested in (username, facility name, is_teacher),
          and pluck out the content_id and replace with the content_title (if found).
        This is to make the csv output more human friendly.
        :param request: HTTP request object
        :param to_be_serialized: the unprocessed list of objects that will be serialized
        :return: the _processed_ list of objects to serialize
        """

        filtered_bundles = [bundle for bundle in to_be_serialized["objects"] if
                            (bundle.data["difficulty"], bundle.data["quality"]) != (0, 0)]
        serializable_objects = []
        for bundle in filtered_bundles:
            user_id = bundle.data["user"].data["id"]
            user = self._facility_users.get(user_id)
            bundle.data["username"] = user.username
            bundle.data["facility_name"] = user.facility.name
            bundle.data["is_teacher"] = user.is_teacher
            bundle.data.pop("user")

            content_id = bundle.data.pop("content_id", None)
            content = get_content_item(language=request.language, content_id=content_id)
            bundle.data["content_title"] = content.get("title", "Missing title") if content else "Unknown content"

            serializable_objects.append(bundle)

        to_be_serialized["objects"] = serializable_objects
        return to_be_serialized
Ejemplo n.º 5
0
 def setUp(self):
     UpdatesTestCase.setUp(self)
     delete_downloaded_files(self.real_video.youtube_id)
     annotate_content_models_by_youtube_id(
         youtube_ids=[self.real_video.youtube_id])
     updated = get_content_item(content_id=self.real_video.id)
     self.assertFalse(updated['available'])
Ejemplo n.º 6
0
def content_item(request, channel, content_id):
    language = request.language

    content = get_content_item(channel=channel, content_id=content_id, language=language)

    if not content:
        content = {
            "title": "Unavailable Content",
            "description": "This content is unavailable. Either it must be downloaded, or the url is incorrect.",
            "available": False,
            "kind": "Video",
            "id": "unavailable_content",
            "slug": "unavailable_content",
            "path": "unavailable_content"
        }

    if not content.get("available", False):
        if request.is_admin:
            # TODO(bcipolli): add a link, with querystring args that auto-checks this content in the topic tree
            messages.warning(request, _("This content was not found! You can download it by going to the Manage > Videos page."))
        elif request.is_logged_in:
            messages.warning(request, _("This content was not found! Please contact your coach or an admin to have it downloaded."))
        elif not request.is_logged_in:
            messages.warning(request, _("This content was not found! You must login as an admin/coach to download the content."))

    content["messages"] = get_messages_for_api_calls(request)

    return JsonResponse(content)
Ejemplo n.º 7
0
 def test_update_item(self):
     item = get_random_content()[0]
     available = item.get("available")
     inverse_available = not available
     update_item({"available": inverse_available}, item.get("path"))
     item2 = get_content_item(content_id=item.get("id"))
     self.assertEqual(item2.get("available"), inverse_available)
Ejemplo n.º 8
0
 def test_update_item(self):
     item = get_random_content()[0]
     available = item.get("available")
     inverse_available = not available
     update_item({"available": inverse_available}, item.get("path"))
     item2 = get_content_item(content_id=item.get("id"))
     self.assertEqual(item2.get("available"), inverse_available)
def get_explore_recommendations(user, request):
    """Get the recommendations for the Explore section, and return them as a list.

    Logic:
    Looks at a preset distance away, beginning at 2 to exclude self recommendations, to 
    recommend a topic for exploration. Currently, the cap is a distance of 6 so that all
    recommendations will still be of moderate relatedness. This number is not permanent, and
    can be tweaked as needed.
    Args:
    user -- The current user as a facility user model object.

    """

    data = generate_recommendation_data()                           #topic tree alg
    exercise_parents_table = get_exercise_parents_lookup_table()    #for finding out subtopic ids
    recent_exercises = get_most_recent_exercises(user)              #most recent ex

    #simply getting a list of subtopics accessed by user
    recent_subtopics = list(set([exercise_parents_table[ex]['subtopic_id'] for ex in recent_exercises if ex in exercise_parents_table]))
    
    # Number of sub topic recommendations
    sampleNum = min(len(recent_subtopics), settings.TOPIC_RECOMMENDATION_SIZE)
    
    random_subtopics = random.sample(recent_subtopics, sampleNum)
    added = []                                                      #keep track of what has been added (below)
    final = []                                                      #final recommendations to return
    
    for subtopic_id in random_subtopics:

        # benjaoming: This seems to be done partly because subtopic_id is
        # included in the set of recommended subtopics.
        related_subtopics = data[subtopic_id]['related_subtopics'][2:7] #get recommendations based on this, can tweak numbers!
                
        recommended_topic = next(topic for topic in related_subtopics if topic not in added and topic not in recent_subtopics)

        if recommended_topic:

            final.append({
                'suggested_topic': get_content_item(language=request.language, content_id=recommended_topic, topic=True) or {},
                'interest_topic': get_content_item(language=request.language, content_id=subtopic_id, topic=True) or {},
            })

            added.append(recommended_topic)

    return final
Ejemplo n.º 10
0
 def test_download_command(self):
     """
     Basic test of the ``videodownload`` command.
     """
     # Check that it's been marked unavailable
     queue = VideoQueue()
     # Yes this is weird, but the VideoQueue instance will return an
     # instance of a queue that already exists
     queue.clear()
     queue.add_files({self.real_video.youtube_id: self.real_video.title}, language="en")
     call_command("videodownload")
     # Check that it's been marked available
     updated = get_content_item(content_id=self.real_video.id)
     self.assertTrue(updated['available'])
Ejemplo n.º 11
0
    def test_delete_existing_video_file(self):
        """
        Delete a video through the API, when only the video exists on disk (not as an object)
        """
        self.assertTrue(os.path.exists(self.fake_video_file), "Video file should exist on disk.")

        # Delete a video file, make sure
        result = self.client.delete_videos(paths=[self.path])
        self.assertEqual(result.status_code, 200, "An error (%d) was thrown while deleting the video through the API: %s" % (result.status_code, result.content))
        self.assertFalse(os.path.exists(self.fake_video_file), "Video file should not exist on disk.")
        videofile = get_content_item(content_id=self.video_id)
        self.assertFalse(videofile.get("available"))
        assert videofile.get("size_on_disk") == 0
        assert videofile.get("files_complete") == 0
Ejemplo n.º 12
0
 def test_download_command(self):
     """
     Basic test of the ``videodownload`` command.
     """
     # Check that it's been marked unavailable
     queue = VideoQueue()
     # Yes this is weird, but the VideoQueue instance will return an
     # instance of a queue that already exists
     queue.clear()
     queue.add_files({self.real_video.youtube_id: self.real_video.title}, language="en")
     call_command("videodownload")
     # Check that it's been marked available
     updated = get_content_item(content_id=self.real_video.id)
     self.assertTrue(updated['available'])
def get_resume_recommendations(user, request):
    """Get the recommendation for the Resume section.

    Logic:
    Find the most recent incomplete item (video or exercise) and
    return that as the recommendation.
    Args:
    user -- The current user as a facility user model object.

    """

    final = get_most_recent_incomplete_item(user)
    if final:
        content = get_content_item(language=request.language, channel=getattr(final, "channel", "khan"), content_id=final.get("id"))
        return [content] if content else []
    else:
        return []
Ejemplo n.º 14
0
def get_resume_recommendations(user, request):
    """Get the recommendation for the Resume section.

    Logic:
    Find the most recent incomplete item (video or exercise) and
    return that as the recommendation.
    Args:
    user -- The current user as a facility user model object.

    """

    final = get_most_recent_incomplete_item(user)
    if final:
        content = get_content_item(language=request.language,
                                   channel=getattr(final, "channel", "khan"),
                                   content_id=final.get("id"))
        return [content] if content else []
    else:
        return []
Ejemplo n.º 15
0
    def test_simple_download(self):
        """
        Tests that a real, existing video can be downloaded
        """
        # Download a video that exists for real!
        download_video(self.real_video.youtube_id)
        # Check that file exists
        self.assertTrue(
            os.path.exists(get_video_local_path(self.real_video.youtube_id)))
        # After downloading the video, annotate the database
        annotate_content_models_by_youtube_id(
            youtube_ids=[self.real_video.youtube_id])
        # Check that it's been marked available
        updated = get_content_item(content_id=self.real_video.id)
        logger.error(updated)
        self.assertTrue(updated['available'])

        # Adding in an unrelated test (becase we don't need database etc. for
        # this to be tested.
        self.assertEqual(get_local_video_size("/bogus/path", default=123), 123)
Ejemplo n.º 16
0
def content_item(request, channel, content_id):
    language = request.language

    content = get_content_item(channel=channel,
                               content_id=content_id,
                               language=language)

    if not content:
        content = {
            "title": "Unavailable Content",
            "description":
            "This content is unavailable. Either it must be downloaded, or the url is incorrect.",
            "available": False,
            "kind": "Video",
            "id": "unavailable_content",
            "slug": "unavailable_content",
            "path": "unavailable_content"
        }

    if not content.get("available", False):
        if request.is_admin:
            # TODO(bcipolli): add a link, with querystring args that auto-checks this content in the topic tree
            messages.warning(
                request,
                _("This content was not found! You can download it by going to the Manage > Videos page."
                  ))
        elif request.is_logged_in:
            messages.warning(
                request,
                _("This content was not found! Please contact your coach or an admin to have it downloaded."
                  ))
        elif not request.is_logged_in:
            messages.warning(
                request,
                _("This content was not found! You must login as an admin/coach to download the content."
                  ))

    content["messages"] = get_messages_for_api_calls(request)

    return JsonResponse(content)
Ejemplo n.º 17
0
    def test_simple_download(self):
        """
        Tests that a real, existing video can be downloaded
        """
        # Download a video that exists for real!
        download_video(self.real_video.youtube_id)
        # Check that file exists
        self.assertTrue(os.path.exists(
            get_video_local_path(self.real_video.youtube_id)
        ))
        # After downloading the video, annotate the database
        annotate_content_models_by_youtube_id(youtube_ids=[self.real_video.youtube_id])
        # Check that it's been marked available
        updated = get_content_item(content_id=self.real_video.id)
        logger.error(updated)
        self.assertTrue(updated['available'])

        # Adding in an unrelated test (becase we don't need database etc. for
        # this to be tested.
        self.assertEqual(
            get_local_video_size("/bogus/path", default=123),
            123
        )
Ejemplo n.º 18
0
def get_next_recommendations(user, request):
    """Get the recommendations for the Next section, and return them as a list.

    Logic:
    Next recommendations are currently comprised of 3 main subgroups: group recommendations,
    struggling exercises, and topic tree based data. Group recommendations consist of 
    finding the most common item tackled immediately after the most recent item, struggling
    is determined by the "struggling" model attribute, and topic tree data is based off
    the graphical distance between neighboring exercise/topic nodes. 
    Args:
    user -- The current user as a facility user model object.

    """

    exercise_parents_table = get_exercise_parents_lookup_table()

    most_recent = get_most_recent_exercises(user)

    complete_exercises = set(get_completed_exercises(user))

    def filter_complete(ex):
        return ex not in complete_exercises

    if len(most_recent) > 0 and most_recent[0] in exercise_parents_table:
        current_subtopic = exercise_parents_table[
            most_recent[0]]['subtopic_id']
    else:
        current_subtopic = None

    #logic for recommendations based off of the topic tree structure
    if current_subtopic:
        topic_tree_based_data = generate_recommendation_data(
        )[current_subtopic]['related_subtopics'][:settings.
                                                 TOPIC_RECOMMENDATION_DEPTH]
        topic_tree_based_data = get_exercises_from_topics(
            topic_tree_based_data)
    else:
        topic_tree_based_data = []

    #for checking that only exercises that have not been accessed are returned
    topic_tree_based_data = [
        ex for ex in topic_tree_based_data
        if ex not in most_recent or filter_complete(ex)
    ]

    #logic to generate recommendations based on exercises student is struggling with
    struggling = filter(filter_complete,
                        get_exercise_prereqs(get_struggling_exercises(user)))

    #logic to get recommendations based on group patterns, if applicable
    group = filter(filter_complete, get_group_recommendations(user))

    #now append titles and other metadata to each exercise id
    final = []  # final data to return
    for exercise_id in (group[:2] + struggling[:2] +
                        topic_tree_based_data[:1]):  #notice the concatenation

        if exercise_id in exercise_parents_table:
            subtopic_id = exercise_parents_table[exercise_id]['subtopic_id']
            exercise = get_content_item(language=request.language,
                                        content_id=exercise_id)
            if exercise:
                exercise["topic"] = get_content_item(language=request.language,
                                                     content_id=subtopic_id,
                                                     topic=True) or {}
                final.append(exercise)

    #final recommendations are a combination of struggling, group filtering, and topic_tree filtering
    return final
Ejemplo n.º 19
0
def impl(context):
    assert get_content_item(
        content_id=context.content_videos[1].id
    )['path'] in context.browser.current_url, "Last in progress video not in %s" % context.browser.current_url
Ejemplo n.º 20
0
def impl(context):
    assert get_content_item(content_id=context.videos[1].get("id")).get("path") in context.browser.current_url, "Last in progress video not in %s" % context.browser.current_url
Ejemplo n.º 21
0
def impl(context):
    assert get_content_item(content_id=context.videos[1].get("id")).get(
        "path"
    ) in context.browser.current_url, "Last in progress video not in %s" % context.browser.current_url
def impl(context):
    assert get_content_item(content_id=context.content_videos[1].id)['path'] in context.browser.current_url, "Last in progress video not in %s" % context.browser.current_url
Ejemplo n.º 23
0
 def setUp(self):
     UpdatesTestCase.setUp(self)
     delete_downloaded_files(self.real_video.youtube_id)
     annotate_content_models_by_youtube_id(youtube_ids=[self.real_video.youtube_id])
     updated = get_content_item(content_id=self.real_video.id)
     self.assertFalse(updated['available'])