def _setup(self, num_logs=50, **kwargs): super(OneHundredRandomLogUpdates, self)._setup(**kwargs) try: self.user = FacilityUser.objects.get(username=self.username) except: #take username from ExerciseLog all_exercises = ExerciseLog.objects.all() self.user = FacilityUser.objects.get(id=all_exercises[0].user_id) print self.username, " not in FacilityUsers, using ", self.user self.num_logs = num_logs #give the platform a chance to cache the logs ExerciseLog.objects.filter(user=self.user).delete() for x in range(num_logs): while True: ex_idx = int(self.random.random() * len(NODE_CACHE["Exercise"].keys())) ex_id = NODE_CACHE["Exercise"].keys()[ex_idx] if not ExerciseLog.objects.filter(user=self.user, exercise_id=ex_id): break ex = ExerciseLog(user=self.user, exercise_id=ex_id) ex.save() self.exercise_list = ExerciseLog.objects.filter(user=self.user) self.exercise_count = self.exercise_list.count() VideoLog.objects.filter(user=self.user).delete() for x in range(num_logs): while True: vid_idx = int(self.random.random() * len(NODE_CACHE["Video"].keys())) vid_id = NODE_CACHE["Video"].keys()[vid_idx] if not VideoLog.objects.filter(user=self.user, video_id=vid_id): break vid = VideoLog(user=self.user, video_id=vid_id) vid.save() self.video_list = VideoLog.objects.filter(user=self.user) self.video_count = self.video_list.count()
def test_videolog_collision(self): # create a new video log with the same youtube_id and user, but different points/total seconds watched videolog = VideoLog(video_id=self.VIDEO_ID, youtube_id=self.YOUTUBE_ID, user=self.user) videolog.points = self.NEW_POINTS videolog.total_seconds_watched = self.NEW_SECONDS_WATCHED # try saving the new VideoLog: this is where the collision will happen, hopefully leading to a merge videolog.save() # get a new reference to the existing VideoLog videolog2 = VideoLog.objects.get(id=self.original_videolog.id) # make sure the VideoLog has been properly merged self.assertEqual(videolog.points, max(self.ORIGINAL_POINTS, self.NEW_POINTS), "The VideoLog's points were not properly merged.") self.assertEqual(videolog.total_seconds_watched, max(self.ORIGINAL_ATTEMPTS, self.NEW_SECONDS_WATCHED), "The VideoLog's total seconds watched have already changed.")
def generate_fake_video_logs(facility_user=None, topics=topics, start_date=datetime.datetime.now() - datetime.timedelta(days=30 * 6)): """Add video logs for the given topics, for each of the given users. If no users are given, they are created. If no topics exist, they are taken from the list at the top of this file.""" date_diff = datetime.datetime.now() - start_date video_logs = [] # It's not a user: probably a list. # Recursive case if not hasattr(facility_user, "username"): # It's NONE :-/ generate the users first! if not facility_user: (facility_user, _, _) = generate_fake_facility_users() for topic in topics: for user in facility_user: video_logs.append(generate_fake_video_logs(facility_user=user, topics=[topic], start_date=start_date)) # Actually generate! else: # First, make videos for the associated logs # Then make some unassociated videos, to simulate both exploration # and watching videos without finishing. # Get (or create) user type try: user_settings = json.loads(facility_user.notes) except: user_settings = sample_user_settings() facility_user.notes = json.dumps(user_settings) facility_user.save() date_diff_started = datetime.timedelta(seconds=datediff(date_diff, units="seconds") * user_settings["time_in_program"]) # when this user started in the program, relative to NOW for topic in topics: videos = get_topic_videos(topic_id=topic) exercises = get_topic_exercises(topic_id=topic) exercise_ids = [ex["id"] if "id" in ex else ex['name'] for ex in exercises] exercise_logs = ExerciseLog.objects.filter(user=facility_user, id__in=exercise_ids) # Probability of watching a video, irrespective of the context p_video_outer = probability_of("video", user_settings=user_settings) logging.debug("# videos: %d; p(videos)=%4.3f, user settings: %s\n" % (len(videos), p_video_outer, json.dumps(user_settings))) for video in videos: p_completed = probability_of("completed", user_settings=user_settings) # If we're just doing random videos, fine. # If these videos relate to exercises, then suppress non-exercise-related videos # for this user. p_video = p_video_outer # start with the context-free value did_exercise = False if exercise_logs.count() > 0: # 5x less likely to watch a video if you haven't done the exercise, if "related_exercise" not in video: p_video /= 5 # suppress # 5x more likely to watch a video if they've done the exercise # 2x more likely to have finished it. else: exercise_log = ExerciseLog.objects.filter(user=facility_user, id=video["related_exercise"]["id"]) did_exercise = exercise_log.count() != 0 if did_exercise: p_video *= 5 p_completed *= 2 # Do the sampling if p_video < random.random(): continue # didn't watch it elif p_completed > random.random(): pct_completed = 100. else: # Slower students will use videos more. Effort also important. pct_completed = 100. * min(1., sqrt(random.random() * sqrt(user_settings["effort_level"] * user_settings["time_in_program"] / sqrt(user_settings["speed_of_learning"])))) # Compute quantities based on sample total_seconds_watched = int(video["duration"] * pct_completed / 100.) points = int(750 * pct_completed / 100.) # Choose a rate of videos, based on their effort level. # Compute the latest possible start time. # Then sample a start time between their start time # and the latest possible start_time if did_exercise: # More jitter if you learn fast, less jitter if you try harder (more diligent) date_jitter = datetime.timedelta(days=max(0, random.gauss(1, user_settings["speed_of_learning"] / user_settings["effort_level"]))) date_completed = exercise_log[0].completion_timestamp - date_jitter else: rate_of_videos = 0.66 * user_settings["effort_level"] + 0.33 * user_settings["speed_of_learning"] # exercises per day time_for_watching = total_seconds_watched time_delta_completed = datetime.timedelta(seconds=random.randint(int(time_for_watching), int(datediff(date_diff_started, units="seconds")))) date_completed = datetime.datetime.now() - time_delta_completed try: log = VideoLog.objects.get(user=facility_user, youtube_id=video["youtube_id"]) except VideoLog.DoesNotExist: logging.info("Creating video log: %-12s: %-45s (%4.1f%% watched, %d points)%s" % ( facility_user.first_name, video["title"], pct_completed, points, " COMPLETE on %s!" % date_completed if pct_completed == 100 else "", )) log = VideoLog( user=facility_user, youtube_id=video["youtube_id"], total_seconds_watched=total_seconds_watched, points=points, completion_timestamp=date_completed, completion_counter=datediff(date_completed, start_date, units="seconds"), ) log.full_clean() # TODO(bcipolli): bulk saving of logs log.save() video_logs.append(log) return video_logs
class TestSaveVideoLog(KALiteTestCase): ORIGINAL_POINTS = 84 ORIGINAL_SECONDS_WATCHED = 32 NEW_POINTS = 32 NEW_SECONDS_WATCHED = 15 YOUTUBE_ID = "aNqG4ChKShI" YOUTUBE_ID2 = "b22tMEc6Kko" USERNAME = "******" PASSWORD = "******" def setUp(self): super(TestSaveVideoLog, self).setUp() # create a facility and user that can be referred to in models across tests self.facility = Facility(name="Test Facility") self.facility.save() self.user = FacilityUser(username=self.USERNAME, facility=self.facility) self.user.set_password(self.PASSWORD) self.user.save() # create an initial VideoLog instance so we have something to update later self.original_videolog = VideoLog(youtube_id=self.YOUTUBE_ID, user=self.user) self.original_videolog.points = self.ORIGINAL_POINTS self.original_videolog.total_seconds_watched = self.ORIGINAL_SECONDS_WATCHED self.original_videolog.save() def test_new_videolog(self): # make sure the target video log does not already exist videologs = VideoLog.objects.filter(youtube_id=self.YOUTUBE_ID2, user__username=self.USERNAME) self.assertEqual(videologs.count(), 0, "The target video log to be newly created already exists") c = KALiteClient() # login success = c.login(username=self.USERNAME, password=self.PASSWORD, facility=self.facility.id) self.assertTrue(success, "Was not able to login as the test user") # save a new video log result = c.save_video_log( youtube_id=self.YOUTUBE_ID2, total_seconds_watched=self.ORIGINAL_SECONDS_WATCHED, points=self.NEW_POINTS, ) self.assertEqual(result.status_code, 200, "An error (%d) was thrown while saving the video log." % result.status_code) # get a reference to the newly created VideoLog videolog = VideoLog.objects.get(youtube_id=self.YOUTUBE_ID2, user__username=self.USERNAME) # make sure the VideoLog was properly created self.assertEqual(videolog.points, self.NEW_POINTS, "The VideoLog's points were not saved correctly.") self.assertEqual(videolog.total_seconds_watched, self.ORIGINAL_SECONDS_WATCHED, "The VideoLog's seconds watched was not saved correctly.") def test_update_videolog(self): # get a new reference to the existing VideoLog videolog = VideoLog.objects.get(id=self.original_videolog.id) # make sure the VideoLog hasn't already been changed self.assertEqual(videolog.points, self.ORIGINAL_POINTS, "The VideoLog's points have already changed.") self.assertEqual(videolog.total_seconds_watched, self.ORIGINAL_SECONDS_WATCHED, "The VideoLog's seconds watched already changed.") c = KALiteClient() # login success = c.login(username=self.USERNAME, password=self.PASSWORD, facility=self.facility.id) self.assertTrue(success, "Was not able to login as the test user") # save a new record onto the video log, with a correct answer (increasing the points and streak) result = c.save_video_log( youtube_id=self.YOUTUBE_ID, total_seconds_watched=self.ORIGINAL_SECONDS_WATCHED + self.NEW_SECONDS_WATCHED, points=self.ORIGINAL_POINTS + self.NEW_POINTS, ) self.assertEqual(result.status_code, 200, "An error (%d) was thrown while saving the video log." % result.status_code) # get a reference to the updated VideoLog videolog = VideoLog.objects.get(youtube_id=self.YOUTUBE_ID, user__username=self.USERNAME) # make sure the VideoLog was properly updated self.assertEqual(videolog.points, self.ORIGINAL_POINTS + self.NEW_POINTS, "The VideoLog's points were not updated correctly.") self.assertEqual(videolog.total_seconds_watched, self.ORIGINAL_SECONDS_WATCHED + self.NEW_SECONDS_WATCHED, "The VideoLog's seconds watched was not updated correctly.")
class TestSaveVideoLog(KALiteTestCase): ORIGINAL_POINTS = 84 ORIGINAL_SECONDS_WATCHED = 32 NEW_POINTS = 32 NEW_SECONDS_WATCHED = 15 YOUTUBE_ID = "aNqG4ChKShI" YOUTUBE_ID2 = "b22tMEc6Kko" USERNAME = "******" PASSWORD = "******" def setUp(self): super(TestSaveVideoLog, self).setUp() # create a facility and user that can be referred to in models across tests self.facility = Facility(name="Test Facility") self.facility.save() self.user = FacilityUser(username=self.USERNAME, facility=self.facility) self.user.set_password(self.PASSWORD) self.user.save() # create an initial VideoLog instance so we have something to update later self.original_videolog = VideoLog(youtube_id=self.YOUTUBE_ID, user=self.user) self.original_videolog.points = self.ORIGINAL_POINTS self.original_videolog.total_seconds_watched = self.ORIGINAL_SECONDS_WATCHED self.original_videolog.save() def test_new_videolog(self): # make sure the target video log does not already exist videologs = VideoLog.objects.filter(youtube_id=self.YOUTUBE_ID2, user__username=self.USERNAME) self.assertEqual( videologs.count(), 0, "The target video log to be newly created already exists") c = KALiteClient() # login success = c.login(username=self.USERNAME, password=self.PASSWORD, facility=self.facility.id) self.assertTrue(success, "Was not able to login as the test user") # save a new video log result = c.save_video_log( youtube_id=self.YOUTUBE_ID2, total_seconds_watched=self.ORIGINAL_SECONDS_WATCHED, points=self.NEW_POINTS, ) self.assertEqual( result.status_code, 200, "An error (%d) was thrown while saving the video log." % result.status_code) # get a reference to the newly created VideoLog videolog = VideoLog.objects.get(youtube_id=self.YOUTUBE_ID2, user__username=self.USERNAME) # make sure the VideoLog was properly created self.assertEqual(videolog.points, self.NEW_POINTS, "The VideoLog's points were not saved correctly.") self.assertEqual( videolog.total_seconds_watched, self.ORIGINAL_SECONDS_WATCHED, "The VideoLog's seconds watched was not saved correctly.") def test_update_videolog(self): # get a new reference to the existing VideoLog videolog = VideoLog.objects.get(id=self.original_videolog.id) # make sure the VideoLog hasn't already been changed self.assertEqual(videolog.points, self.ORIGINAL_POINTS, "The VideoLog's points have already changed.") self.assertEqual(videolog.total_seconds_watched, self.ORIGINAL_SECONDS_WATCHED, "The VideoLog's seconds watched already changed.") c = KALiteClient() # login success = c.login(username=self.USERNAME, password=self.PASSWORD, facility=self.facility.id) self.assertTrue(success, "Was not able to login as the test user") # save a new record onto the video log, with a correct answer (increasing the points and streak) result = c.save_video_log( youtube_id=self.YOUTUBE_ID, total_seconds_watched=self.ORIGINAL_SECONDS_WATCHED + self.NEW_SECONDS_WATCHED, points=self.ORIGINAL_POINTS + self.NEW_POINTS, ) self.assertEqual( result.status_code, 200, "An error (%d) was thrown while saving the video log." % result.status_code) # get a reference to the updated VideoLog videolog = VideoLog.objects.get(youtube_id=self.YOUTUBE_ID, user__username=self.USERNAME) # make sure the VideoLog was properly updated self.assertEqual(videolog.points, self.ORIGINAL_POINTS + self.NEW_POINTS, "The VideoLog's points were not updated correctly.") self.assertEqual( videolog.total_seconds_watched, self.ORIGINAL_SECONDS_WATCHED + self.NEW_SECONDS_WATCHED, "The VideoLog's seconds watched was not updated correctly.")
class TestVideoLogs(TestCase): ORIGINAL_POINTS = 37 ORIGINAL_SECONDS_WATCHED = 3 NEW_POINTS = 22 NEW_SECONDS_WATCHED = 5 YOUTUBE_ID = "aNqG4ChKShI" def setUp(self): # create a facility and user that can be referred to in models across tests self.facility = Facility(name="Test Facility") self.facility.save() self.user = FacilityUser(username="******", password="******", facility=self.facility) self.user.save() # create an initial VideoLog instance so we have something to collide with later self.original_videolog = VideoLog(youtube_id=self.YOUTUBE_ID, user=self.user) self.original_videolog.points = self.ORIGINAL_POINTS self.original_videolog.total_seconds_watched = self.ORIGINAL_SECONDS_WATCHED self.original_videolog.save() # get a new reference to the existing VideoLog videolog = VideoLog.objects.get(id=self.original_videolog.id) # make sure the VideoLog was created correctly self.assertEqual(videolog.points, self.ORIGINAL_POINTS, "The VideoLog's points have already changed.") self.assertEqual( videolog.total_seconds_watched, self.ORIGINAL_SECONDS_WATCHED, "The VideoLog's total seconds watched have already changed.") def test_videolog_update(self): # get a new reference to the existing VideoLog videolog = VideoLog.objects.get(id=self.original_videolog.id) # update the VideoLog videolog.points = self.NEW_POINTS videolog.total_seconds_watched = self.NEW_SECONDS_WATCHED videolog.save() # get a new reference to the existing VideoLog videolog2 = VideoLog.objects.get(id=self.original_videolog.id) # make sure the VideoLog was updated self.assertEqual(videolog2.points, self.NEW_POINTS, "The VideoLog's points were not updated.") self.assertEqual( videolog2.total_seconds_watched, self.NEW_SECONDS_WATCHED, "The VideoLog's total seconds watched were not updated.") @unittest.skip("Auto-merging is not yet automatic, so skip this") def test_videolog_collision(self): # create a new video log with the same youtube_id and user, but different points/total seconds watched videolog = VideoLog(youtube_id=self.YOUTUBE_ID, user=self.user) videolog.points = self.NEW_POINTS videolog.total_seconds_watched = self.NEW_SECONDS_WATCHED # try saving the new VideoLog: this is where the collision will happen, hopefully leading to a merge videolog.save() # get a new reference to the existing VideoLog videolog2 = VideoLog.objects.get(id=self.original_videolog.id) # make sure the VideoLog has been properly merged self.assertEqual(videolog.points, max(self.ORIGINAL_POINTS, self.NEW_POINTS), "The VideoLog's points were not properly merged.") self.assertEqual( videolog.total_seconds_watched, max(self.ORIGINAL_ATTEMPTS, self.NEW_SECONDS_WATCHED), "The VideoLog's total seconds watched have already changed.")
def generate_fake_video_logs(facility_user=None, topics=topics, start_date=datetime.datetime.now() - datetime.timedelta(days=30 * 6)): """Add video logs for the given topics, for each of the given users. If no users are given, they are created. If no topics exist, they are taken from the list at the top of this file.""" own_device = Device.get_own_device() date_diff = datetime.datetime.now() - start_date video_logs = [] # It's not a user: probably a list. # Recursive case if not hasattr(facility_user, "username"): # It's NONE :-/ generate the users first! if not facility_user: (facility_user, _, _) = generate_fake_facility_users() for topic in topics: for user in facility_user: video_logs.append(generate_fake_video_logs(facility_user=user, topics=[topic], start_date=start_date)) # Actually generate! else: # First, make videos for the associated logs # Then make some unassociated videos, to simulate both exploration # and watching videos without finishing. # Get (or create) user type try: user_settings = json.loads(facility_user.notes) except: user_settings = sample_user_settings() facility_user.notes = json.dumps(user_settings) facility_user.save() date_diff_started = datetime.timedelta(seconds=datediff(date_diff, units="seconds") * user_settings["time_in_program"]) # when this user started in the program, relative to NOW for topic in topics: videos = get_topic_videos(topic_id=topic) exercises = get_topic_exercises(topic_id=topic) exercise_ids = [ex["id"] if "id" in ex else ex['name'] for ex in exercises] exercise_logs = ExerciseLog.objects.filter(user=facility_user, id__in=exercise_ids) # Probability of watching a video, irrespective of the context p_video_outer = probability_of("video", user_settings=user_settings) logging.debug("# videos: %d; p(videos)=%4.3f, user settings: %s\n" % (len(videos), p_video_outer, json.dumps(user_settings))) for video in videos: p_completed = probability_of("completed", user_settings=user_settings) # If we're just doing random videos, fine. # If these videos relate to exercises, then suppress non-exercise-related videos # for this user. p_video = p_video_outer # start with the context-free value did_exercise = False if exercise_logs.count() > 0: # 5x less likely to watch a video if you haven't done the exercise, if "related_exercise" not in video: p_video /= 5 # suppress # 5x more likely to watch a video if they've done the exercise # 2x more likely to have finished it. else: exercise_log = ExerciseLog.objects.filter(user=facility_user, id=video["related_exercise"]["id"]) did_exercise = exercise_log.count() != 0 if did_exercise: p_video *= 5 p_completed *= 2 # Do the sampling if p_video < random.random(): continue # didn't watch it elif p_completed > random.random(): pct_completed = 100. else: # Slower students will use videos more. Effort also important. pct_completed = 100. * min(1., sqrt(random.random() * sqrt(user_settings["effort_level"] * user_settings["time_in_program"] / sqrt(user_settings["speed_of_learning"])))) # Compute quantities based on sample total_seconds_watched = int(video["duration"] * pct_completed / 100.) points = int(750 * pct_completed / 100.) # Choose a rate of videos, based on their effort level. # Compute the latest possible start time. # Then sample a start time between their start time # and the latest possible start_time if did_exercise: # More jitter if you learn fast, less jitter if you try harder (more diligent) date_jitter = datetime.timedelta(days=max(0, random.gauss(1, user_settings["speed_of_learning"] / user_settings["effort_level"]))) date_completed = exercise_log[0].completion_timestamp - date_jitter else: rate_of_videos = 0.66 * user_settings["effort_level"] + 0.33 * user_settings["speed_of_learning"] # exercises per day time_for_watching = total_seconds_watched time_delta_completed = datetime.timedelta(seconds=random.randint(int(time_for_watching), int(datediff(date_diff_started, units="seconds")))) date_completed = datetime.datetime.now() - time_delta_completed try: vlog = VideoLog.objects.get(user=facility_user, youtube_id=video["youtube_id"]) except VideoLog.DoesNotExist: logging.info("Creating video log: %-12s: %-45s (%4.1f%% watched, %d points)%s" % ( facility_user.first_name, video["title"], pct_completed, points, " COMPLETE on %s!" % date_completed if pct_completed == 100 else "", )) vlog = VideoLog( user=facility_user, youtube_id=video["youtube_id"], total_seconds_watched=total_seconds_watched, points=points, complete=(pct_completed == 100.), completion_timestamp=date_completed, completion_counter=datediff(date_completed, start_date, units="seconds"), ) vlog.full_clean() # TODO(bcipolli): bulk saving of logs vlog.counter = own_device.increment_and_get_counter() vlog.sign(own_device) # have to sign after setting the counter vlog.save(imported=True) # avoid userlog issues video_logs.append(vlog) return video_logs
class TestVideoLogs(KALiteTestCase): ORIGINAL_POINTS = 37 ORIGINAL_SECONDS_WATCHED = 3 NEW_POINTS = 22 NEW_SECONDS_WATCHED = 5 YOUTUBE_ID = "aNqG4ChKShI" VIDEO_ID = i18n.get_video_id(YOUTUBE_ID) or "dummy" def setUp(self): super(TestVideoLogs, self).setUp() # create a facility and user that can be referred to in models across tests self.facility = Facility(name="Test Facility") self.facility.save() self.user = FacilityUser(username="******", facility=self.facility) self.user.set_password("dumber") self.user.save() # create an initial VideoLog instance so we have something to collide with later self.original_videolog = VideoLog(video_id=self.VIDEO_ID, youtube_id=self.YOUTUBE_ID, user=self.user) self.original_videolog.points = self.ORIGINAL_POINTS self.original_videolog.total_seconds_watched = self.ORIGINAL_SECONDS_WATCHED self.original_videolog.save() # get a new reference to the existing VideoLog videolog = VideoLog.objects.get(id=self.original_videolog.id) # make sure the VideoLog was created correctly self.assertEqual(videolog.points, self.ORIGINAL_POINTS, "The VideoLog's points have already changed.") self.assertEqual(videolog.total_seconds_watched, self.ORIGINAL_SECONDS_WATCHED, "The VideoLog's total seconds watched have already changed.") def test_videolog_update(self): # get a new reference to the existing VideoLog videolog = VideoLog.objects.get(id=self.original_videolog.id) # update the VideoLog videolog.points = self.NEW_POINTS videolog.total_seconds_watched = self.NEW_SECONDS_WATCHED videolog.save() # get a new reference to the existing VideoLog videolog2 = VideoLog.objects.get(id=self.original_videolog.id) # make sure the VideoLog was updated self.assertEqual(videolog2.points, self.NEW_POINTS, "The VideoLog's points were not updated.") self.assertEqual(videolog2.total_seconds_watched, self.NEW_SECONDS_WATCHED, "The VideoLog's total seconds watched were not updated.") @unittest.skip("Auto-merging is not yet automatic, so skip this") def test_videolog_collision(self): # create a new video log with the same youtube_id and user, but different points/total seconds watched videolog = VideoLog(video_id=self.VIDEO_ID, youtube_id=self.YOUTUBE_ID, user=self.user) videolog.points = self.NEW_POINTS videolog.total_seconds_watched = self.NEW_SECONDS_WATCHED # try saving the new VideoLog: this is where the collision will happen, hopefully leading to a merge videolog.save() # get a new reference to the existing VideoLog videolog2 = VideoLog.objects.get(id=self.original_videolog.id) # make sure the VideoLog has been properly merged self.assertEqual(videolog.points, max(self.ORIGINAL_POINTS, self.NEW_POINTS), "The VideoLog's points were not properly merged.") self.assertEqual(videolog.total_seconds_watched, max(self.ORIGINAL_ATTEMPTS, self.NEW_SECONDS_WATCHED), "The VideoLog's total seconds watched have already changed.")