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
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
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
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'])
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)
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 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
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 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
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 []
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 []
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)
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)
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 )
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
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
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.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
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'])