def test_get_content_parents(self): """ The function get_content_parents() should return a empty list when an empty list of ids is passed to it. """ self.assertEqual(get_content_parents(ids=list()), list())
def aggregate_learner_logs(request): learners = get_learners_from_GET(request) event_limit = request.GET.get("event_limit", 10) # Look back a week by default time_window = request.GET.get("time_window", 7) start_date = request.GET.get("start_date") end_date = request.GET.get("end_date") topic_ids = json.loads(request.GET.get("topic_ids", "[]")) log_types = request.GET.getlist("log_type", ["exercise", "video", "content"]) output_logs = [] output_dict = { "content_time_spent": 0, "exercise_attempts": 0, "exercise_mastery": None, "total_in_progress": 0, "total_complete": 0, "total_struggling": 0, "total_not_attempted": 0, "available_topics": [], } end_date = datetime.datetime.strptime(end_date,'%Y/%m/%d') if end_date else datetime.datetime.now() start_date = datetime.datetime.strptime(start_date,'%Y/%m/%d') if start_date else end_date - datetime.timedelta(time_window) number_content = 0 all_object_ids = set() for log_type in log_types: LogModel, fields, id_field, obj_ids, objects = return_log_type_details(log_type, topic_ids) log_objects = LogModel.objects.filter( user__in=learners, latest_activity_timestamp__gte=start_date, latest_activity_timestamp__lte=end_date, **obj_ids).order_by("-latest_activity_timestamp") number_content += len(set(log_objects.values_list(id_field, flat=True))) if log_type == "video": output_dict["total_in_progress"] += log_objects.filter(complete=False).count() output_dict["content_time_spent"] += log_objects.aggregate(Sum("total_seconds_watched"))["total_seconds_watched__sum"] or 0 elif log_type == "content": output_dict["total_in_progress"] += log_objects.filter(complete=False).count() output_dict["content_time_spent"] += log_objects.aggregate(Sum("time_spent"))["time_spent__sum"] or 0 elif log_type == "exercise": output_dict["total_struggling"] = log_objects.filter(struggling=True).count() output_dict["total_in_progress"] += log_objects.filter(complete=False, struggling=False).count() output_dict["exercise_attempts"] = AttemptLog.objects.filter(user__in=learners, timestamp__gte=start_date, timestamp__lte=end_date, **obj_ids).count() if log_objects.aggregate(Avg("streak_progress"))["streak_progress__avg"] is not None: output_dict["exercise_mastery"] = round(log_objects.aggregate(Avg("streak_progress"))["streak_progress__avg"]) output_logs.extend(log_objects) output_dict["total_complete"] += log_objects.filter(complete=True).count() object_buffer = LogModel.objects.filter( user__in=learners, latest_activity_timestamp__gte=start_date, latest_activity_timestamp__lte=end_date).values_list(id_field, flat=True) if len(object_buffer) > 1: all_object_ids.update(object_buffer) elif len(object_buffer) == 1: all_object_ids.add(object_buffer) if len(all_object_ids) > 0: output_dict["available_topics"] = map(lambda x: {"id": x.get("id"), "title": x.get("title")}, get_content_parents(ids=list(all_object_ids))) output_dict["total_not_attempted"] = number_content*len(learners) - ( output_dict["total_complete"] + output_dict["total_struggling"] + output_dict["total_in_progress"]) # Report total time in hours output_dict["content_time_spent"] = round(output_dict["content_time_spent"]/3600.0,1) output_logs.sort(key=lambda x: x.latest_activity_timestamp, reverse=True) learner_event_objects = dict([(item["id"], item) for item in get_topic_nodes( ids=[getattr(log, "exercise_id", getattr(log, "video_id", getattr(log, "content_id", ""))) for log in output_logs[:event_limit]], language=request.language) or []]) output_dict["learner_events"] = [{ "learner": log.user.get_name(), "complete": log.complete, "struggling": getattr(log, "struggling", None), "progress": getattr(log, "streak_progress", getattr(log, "progress", None)), "content": learner_event_objects.get(getattr(log, "exercise_id", getattr(log, "video_id", getattr(log, "content_id", ""))), {}), } for log in output_logs[:event_limit]] output_dict["total_time_logged"] = round((UserLogSummary.objects\ .filter(user__in=learners, start_datetime__gte=start_date, start_datetime__lte=end_date)\ .aggregate(Sum("total_seconds")).get("total_seconds__sum") or 0)/3600.0, 1) return JsonResponse(output_dict)
def aggregate_learner_logs(request): learners = get_learners_from_GET(request) event_limit = request.GET.get("event_limit", 10) # Look back a week by default time_window = request.GET.get("time_window", 7) start_date = request.GET.get("start_date") end_date = request.GET.get("end_date") topic_ids = json.loads(request.GET.get("topic_ids", "[]")) # Previously, we defaulted to all types of logs, but views on coach reports # seem to assume only exercises # log_types = request.GET.getlist("log_type", ["exercise", "video", "content"]) log_types = request.GET.getlist("log_type", ["exercise"]) output_logs = [] output_dict = { "content_time_spent": 0, "exercise_attempts": 0, "exercise_mastery": None, "total_in_progress": 0, "total_complete": 0, "total_struggling": 0, "total_not_attempted": 0, "available_topics": [], } end_date = datetime.datetime.strptime( end_date, '%Y/%m/%d') if end_date else datetime.datetime.now() start_date = datetime.datetime.strptime( start_date, '%Y/%m/%d' ) if start_date else end_date - datetime.timedelta(time_window) number_content = 0 all_object_ids = set() for log_type in log_types: LogModel, fields, id_field, obj_ids, objects = return_log_type_details( log_type, topic_ids, request.language) log_objects = LogModel.objects.filter( user__in=learners, latest_activity_timestamp__gte=start_date, latest_activity_timestamp__lte=end_date, **obj_ids).order_by("-latest_activity_timestamp") number_content += len(set(log_objects.values_list(id_field, flat=True))) output_dict["total_complete"] += log_objects.filter( complete=True).count() if log_type == "video": output_dict["total_in_progress"] += log_objects.filter( complete=False).count() output_dict["content_time_spent"] += log_objects.aggregate( Sum("total_seconds_watched") )["total_seconds_watched__sum"] or 0 elif log_type == "content": output_dict["total_in_progress"] += log_objects.filter( complete=False).count() output_dict["content_time_spent"] += log_objects.aggregate( Sum("time_spent"))["time_spent__sum"] or 0 elif log_type == "exercise": output_dict["total_struggling"] += log_objects.filter( struggling=True).count() output_dict["total_in_progress"] += log_objects.filter( complete=False, struggling=False).count() # Summarize struggling, in progress, and completed output_dict["exercise_attempts"] += output_dict[ "total_struggling"] + output_dict[ "total_complete"] + output_dict["total_in_progress"] # The below doesn't filter correctly, suspecting either bad # AttemptLog generated in generaterealdata or because timestamp # isn't correctly updated # output_dict["exercise_attempts"] = AttemptLog.objects.filter(user__in=learners, # timestamp__gte=start_date, # timestamp__lte=end_date, **obj_ids).count() if log_objects.aggregate(Avg( "streak_progress"))["streak_progress__avg"] is not None: output_dict["exercise_mastery"] = round( log_objects.aggregate( Avg("streak_progress"))["streak_progress__avg"]) output_logs.extend(log_objects) object_buffer = LogModel.objects.filter( user__in=learners, latest_activity_timestamp__gte=start_date, latest_activity_timestamp__lte=end_date).values_list(id_field, flat=True) if len(object_buffer) > 1: all_object_ids.update(object_buffer) elif len(object_buffer) == 1: all_object_ids.add(object_buffer) if len(all_object_ids) > 0: output_dict["available_topics"] = map( lambda x: { "id": x.get("id"), "title": x.get("title") }, get_content_parents(ids=list(all_object_ids), language=request.language)) output_dict["total_not_attempted"] = number_content * len(learners) - ( output_dict["total_complete"] + output_dict["total_struggling"] + output_dict["total_in_progress"]) # Report total time in hours output_dict["content_time_spent"] = round( output_dict["content_time_spent"] / 3600.0, 1) output_logs.sort(key=lambda x: x.latest_activity_timestamp, reverse=True) learner_event_objects = dict([ (item["id"], item) for item in get_topic_nodes(ids=[ getattr(log, "exercise_id", getattr(log, "video_id", getattr(log, "content_id", ""))) for log in output_logs[:event_limit] ], language=request.language) or [] ]) output_dict["learner_events"] = [{ "learner": log.user.get_name(), "complete": log.complete, "struggling": getattr(log, "struggling", None), "progress": getattr(log, "streak_progress", getattr(log, "progress", None)), "content": learner_event_objects.get( getattr(log, "exercise_id", getattr(log, "video_id", getattr(log, "content_id", ""))), {}), } for log in output_logs[:event_limit]] output_dict["total_time_logged"] = round((UserLogSummary.objects\ .filter(user__in=learners, start_datetime__gte=start_date, start_datetime__lte=end_date)\ .aggregate(Sum("total_seconds")).get("total_seconds__sum") or 0)/3600.0, 1) return JsonResponse(output_dict)
def user_progress(cls, user_id, language=None): """ Return a list of PlaylistProgress objects associated with the user. """ if not language: language = Settings.get( "default_language") or settings.LANGUAGE_CODE user = FacilityUser.objects.get(id=user_id) # Retrieve video, exercise, and quiz logs that appear in this playlist user_vid_logs, user_ex_logs = cls.get_user_logs(user) exercise_ids = list( set([ex_log["exercise_id"] for ex_log in user_ex_logs])) video_ids = list( set([vid_log["video_id"] for vid_log in user_vid_logs])) # Build a list of playlists for which the user has at least one data point user_playlists = get_content_parents(ids=exercise_ids + video_ids, language=language) # Store stats for each playlist user_progress = list() for i, p in enumerate(user_playlists): # Playlist entry totals pl_video_ids, pl_exercise_ids = cls.get_playlist_entry_ids(p) n_pl_videos = float(len(pl_video_ids)) n_pl_exercises = float(len(pl_exercise_ids)) # Vid & exercise logs in this playlist pl_ex_logs = [ ex_log for ex_log in user_ex_logs if ex_log["exercise_id"] in pl_exercise_ids ] pl_vid_logs = [ vid_log for vid_log in user_vid_logs if vid_log["video_id"] in pl_video_ids ] # Compute video stats n_vid_complete = len( [vid for vid in pl_vid_logs if vid["complete"]]) n_vid_started = len([ vid for vid in pl_vid_logs if (vid["total_seconds_watched"] > 0) and (not vid["complete"]) ]) vid_pct_complete = int(float(n_vid_complete) / n_pl_videos * 100) if n_pl_videos else 0 vid_pct_started = int(float(n_vid_started) / n_pl_videos * 100) if n_pl_videos else 0 if vid_pct_complete == 100: vid_status = "complete" elif n_vid_started > 0: vid_status = "inprogress" else: vid_status = "notstarted" # Compute exercise stats n_ex_mastered = len([ex for ex in pl_ex_logs if ex["complete"]]) n_ex_started = len([ex for ex in pl_ex_logs if ex["attempts"] > 0]) n_ex_incomplete = len([ ex for ex in pl_ex_logs if (ex["attempts"] > 0 and not ex["complete"]) ]) n_ex_struggling = len( [ex for ex in pl_ex_logs if ex["struggling"]]) ex_pct_mastered = int( float(n_ex_mastered) / (n_pl_exercises or 1) * 100) ex_pct_incomplete = int( float(n_ex_incomplete) / (n_pl_exercises or 1) * 100) ex_pct_struggling = int( float(n_ex_struggling) / (n_pl_exercises or 1) * 100) if not n_ex_started: ex_status = "notstarted" elif ex_pct_struggling > 0: # note: we want to help students prioritize areas they need to focus on # therefore if they are struggling in this exercise group, we highlight it for them ex_status = "struggling" elif ex_pct_mastered < 99: ex_status = "inprogress" else: ex_status = "complete" progress = { "title": p.get("title"), "id": p.get("id"), "tag": p.get("tag"), "vid_pct_complete": vid_pct_complete, "vid_pct_started": vid_pct_started, "vid_status": vid_status, "ex_pct_mastered": ex_pct_mastered, "ex_pct_incomplete": ex_pct_incomplete, "ex_pct_struggling": ex_pct_struggling, "ex_status": ex_status, "n_pl_videos": n_pl_videos, "n_pl_exercises": n_pl_exercises, } try: progress["url"] = reverse("view_playlist", kwargs={"playlist_id": p.get("id")}) except NoReverseMatch: progress["url"] = reverse("learn") + p.get("path") user_progress.append(cls(**progress)) return user_progress
def test_get_content_parents(self): """ The function get_content_parents() should return a empty list when an empty list of ids is passed to it. """ self.assertEqual(get_content_parents(ids=list()), list())
def user_progress(cls, user_id, language=None): """ Return a list of PlaylistProgress objects associated with the user. """ if not language: language = Settings.get("default_language") or settings.LANGUAGE_CODE user = FacilityUser.objects.get(id=user_id) # Retrieve video, exercise, and quiz logs that appear in this playlist user_vid_logs, user_ex_logs = cls.get_user_logs(user) exercise_ids = list(set([ex_log["exercise_id"] for ex_log in user_ex_logs])) video_ids = list(set([vid_log["video_id"] for vid_log in user_vid_logs])) # Build a list of playlists for which the user has at least one data point user_playlists = get_content_parents(ids=exercise_ids+video_ids, language=language) # Store stats for each playlist user_progress = list() for i, p in enumerate(user_playlists): # Playlist entry totals pl_video_ids, pl_exercise_ids = cls.get_playlist_entry_ids(p) n_pl_videos = float(len(pl_video_ids)) n_pl_exercises = float(len(pl_exercise_ids)) # Vid & exercise logs in this playlist pl_ex_logs = [ex_log for ex_log in user_ex_logs if ex_log["exercise_id"] in pl_exercise_ids] pl_vid_logs = [vid_log for vid_log in user_vid_logs if vid_log["video_id"] in pl_video_ids] # Compute video stats n_vid_complete = len([vid for vid in pl_vid_logs if vid["complete"]]) n_vid_started = len([vid for vid in pl_vid_logs if (vid["total_seconds_watched"] > 0) and (not vid["complete"])]) vid_pct_complete = int(float(n_vid_complete) / n_pl_videos * 100) if n_pl_videos else 0 vid_pct_started = int(float(n_vid_started) / n_pl_videos * 100) if n_pl_videos else 0 if vid_pct_complete == 100: vid_status = "complete" elif n_vid_started > 0: vid_status = "inprogress" else: vid_status = "notstarted" # Compute exercise stats n_ex_mastered = len([ex for ex in pl_ex_logs if ex["complete"]]) n_ex_started = len([ex for ex in pl_ex_logs if ex["attempts"] > 0]) n_ex_incomplete = len([ex for ex in pl_ex_logs if (ex["attempts"] > 0 and not ex["complete"])]) n_ex_struggling = len([ex for ex in pl_ex_logs if ex["struggling"]]) ex_pct_mastered = int(float(n_ex_mastered) / (n_pl_exercises or 1) * 100) ex_pct_incomplete = int(float(n_ex_incomplete) / (n_pl_exercises or 1) * 100) ex_pct_struggling = int(float(n_ex_struggling) / (n_pl_exercises or 1) * 100) if not n_ex_started: ex_status = "notstarted" elif ex_pct_struggling > 0: # note: we want to help students prioritize areas they need to focus on # therefore if they are struggling in this exercise group, we highlight it for them ex_status = "struggling" elif ex_pct_mastered < 99: ex_status = "inprogress" else: ex_status = "complete" progress = { "title": p.get("title"), "id": p.get("id"), "tag": p.get("tag"), "vid_pct_complete": vid_pct_complete, "vid_pct_started": vid_pct_started, "vid_status": vid_status, "ex_pct_mastered": ex_pct_mastered, "ex_pct_incomplete": ex_pct_incomplete, "ex_pct_struggling": ex_pct_struggling, "ex_status": ex_status, "n_pl_videos": n_pl_videos, "n_pl_exercises": n_pl_exercises, } try: progress["url"] = reverse("view_playlist", kwargs={"playlist_id": p.get("id")}) except NoReverseMatch: progress["url"] = reverse("learn") + p.get("path") user_progress.append(cls(**progress)) return user_progress