def user_progress_detail(cls, user_id, playlist_id): """ Return a list of video, exercise, and quiz log PlaylistProgressDetail objects associated with a specific user and playlist ID. """ user = FacilityUser.objects.get(id=user_id) playlist = next((pl for pl in [plist.__dict__ for plist in Playlist.all()] + get_leafed_topics() if pl.get("id") == playlist_id), None) pl_video_ids, pl_exercise_ids = cls.get_playlist_entry_ids(playlist) # Retrieve video, exercise, and quiz logs that appear in this playlist user_vid_logs, user_ex_logs = cls.get_user_logs(user, pl_video_ids, pl_exercise_ids) # Format & append quiz the quiz log, if it exists quiz_exists, quiz_log, quiz_pct_score = cls.get_quiz_log(user, (playlist.get("entries") or playlist.get("children")), playlist.get("id")) # Finally, sort an ordered list of the playlist entries, with user progress # injected where it exists. progress_details = list() for ent in (playlist.get("entries") or playlist.get("children")): entry = {} kind = ent.get("entity_kind") or ent.get("kind") if kind == "Divider": continue elif kind == "Video": entity_id = get_slug2id_map().get(ent.get("entity_id")) or ent.get("id") vid_log = next((vid_log for vid_log in user_vid_logs if vid_log["video_id"] == entity_id), None) if vid_log: if vid_log.get("complete"): status = "complete" elif vid_log.get("total_seconds_watched"): status = "inprogress" else: status = "notstarted" leaf_node = get_content_cache().get(vid_log["video_id"]) entry = { "id": entity_id, "kind": kind, "status": status, "score": int(float(vid_log.get("points")) / float(750) * 100), "title": leaf_node["title"], "path": leaf_node["path"], } elif kind == "Exercise": entity_id = (ent.get("entity_id") or ent.get("id")) ex_log = next((ex_log for ex_log in user_ex_logs if ex_log["exercise_id"] == entity_id), None) if ex_log: if ex_log.get("struggling"): status = "struggling" elif ex_log.get("complete"): status = "complete" elif ex_log.get("attempts"): status = "inprogress" ex_log_id = ex_log.get("exercise_id") leaf_node = get_exercise_cache().get(ex_log_id) entry = { "id": ex_log_id, "kind": kind, "status": status, "score": ex_log.get("streak_progress"), "title": leaf_node["title"], "path": leaf_node["path"], } elif kind == "Quiz": entity_id = playlist["id"] if quiz_log: if quiz_log.complete: if quiz_pct_score <= 59: status = "fail" elif quiz_pct_score <= 79: status = "borderline" else: status = "pass" elif quiz_log.attempts: status = "inprogress" else: status = "notstarted" quiz_log_id = quiz_log.quiz entry = { "id": quiz_log_id, "kind": "Quiz", "status": status, "score": quiz_pct_score, "title": playlist.get("title"), "path": "", } if not entry: entry = cls.create_empty_entry(entity_id, kind, playlist) progress_details.append(cls(**entry)) return progress_details
def user_progress(cls, user_id): """ Return a list of PlaylistProgress objects associated with the user. """ user = FacilityUser.objects.get(id=user_id) all_playlists = [getattr(pl, "__dict__", pl) for pl in Playlist.all() + get_leafed_topics()] # Retrieve video, exercise, and quiz logs that appear in this playlist user_vid_logs, user_ex_logs = cls.get_user_logs(user) exercise_ids = set([ex_log["exercise_id"] for ex_log in user_ex_logs]) video_ids = set([get_id2slug_map().get(vid_log["video_id"]) for vid_log in user_vid_logs]) quiz_log_ids = [ql_id["quiz"] for ql_id in QuizLog.objects.filter(user=user).values("quiz")] # Build a list of playlists for which the user has at least one data point ## TODO(dylanjbarth) this won't pick up playlists the user is assigned but has not started yet. user_playlists = list() for p in all_playlists: for e in (p.get("entries") or p.get("children")): if (e.get("entity_kind") or e.get("kind")) == "Video" or (e.get("entity_kind") or e.get("kind")) == "Exercise": entity_id = convert_leaf_url_to_id((e.get("entity_id") or e.get("id"))) if entity_id in exercise_ids or entity_id in video_ids: user_playlists.append(p) break elif e.get("entity_kind") == "Quiz": if p.get("id") in quiz_log_ids: user_playlists.append(p) # 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 * 100) ex_pct_incomplete = int(float(n_ex_incomplete) / n_pl_exercises * 100) ex_pct_struggling = int(float(n_ex_struggling) / n_pl_exercises * 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" # Compute quiz stats quiz_exists, quiz_log, quiz_pct_score = cls.get_quiz_log(user, (p.get("entries") or p.get("children")), p.get("id")) if quiz_log: if quiz_pct_score <= 50: quiz_status = "struggling" elif quiz_pct_score <= 79: quiz_status = "borderline" else: quiz_status = "complete" else: quiz_status = "notstarted" 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, "quiz_status": quiz_status, "quiz_exists": quiz_exists, "quiz_pct_score": quiz_pct_score, "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 user_progress_detail(cls, user_id, playlist_id, language=None): """ Return a list of video, exercise, and quiz log PlaylistProgressDetail objects associated with a specific user and playlist ID. """ if not language: language = Settings.get("default_language") or settings.LANGUAGE_CODE user = FacilityUser.objects.get(id=user_id) playlist = next((pl for pl in get_leafed_topics() if pl.get("id") == playlist_id), None) pl_video_ids, pl_exercise_ids = cls.get_playlist_entry_ids(playlist) # Retrieve video, exercise, and quiz logs that appear in this playlist user_vid_logs, user_ex_logs = cls.get_user_logs(user, pl_video_ids, pl_exercise_ids) # Format & append quiz the quiz log, if it exists # quiz_exists, quiz_log, quiz_pct_score = cls.get_quiz_log(user, (playlist.get("entries") or playlist.get("children")), playlist.get("id")) # Finally, sort an ordered list of the playlist entries, with user progress # injected where it exists. progress_details = list() for entity_id in playlist.get("children"): entry = {} leaf_node = get_content_cache(language=language).get(entity_id) or get_exercise_cache(language=language).get(entity_id) or {} kind = leaf_node.get("kind") status = "notstarted" score = 0 if kind == "Video": vid_log = next((vid_log for vid_log in user_vid_logs if vid_log["video_id"] == entity_id), None) if vid_log: if vid_log.get("complete"): status = "complete" elif vid_log.get("total_seconds_watched"): status = "inprogress" score = int(float(vid_log.get("points")) / float(750) * 100) elif kind == "Exercise": ex_log = next((ex_log for ex_log in user_ex_logs if ex_log["exercise_id"] == entity_id), None) if ex_log: if ex_log.get("struggling"): status = "struggling" elif ex_log.get("complete"): status = "complete" elif ex_log.get("attempts"): status = "inprogress" score = ex_log.get('streak_progress') entry = { "id": entity_id, "kind": kind, "status": status, "score": score, "title": leaf_node["title"], "path": leaf_node["path"], } progress_details.append(cls(**entry)) return progress_details
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) all_playlists = get_leafed_topics(language=language) # Retrieve video, exercise, and quiz logs that appear in this playlist user_vid_logs, user_ex_logs = cls.get_user_logs(user) exercise_ids = set([ex_log["exercise_id"] for ex_log in user_ex_logs]) video_ids = set([get_id2slug_map().get(vid_log["video_id"]) for vid_log in user_vid_logs]) # quiz_log_ids = [ql_id["quiz"] for ql_id in QuizLog.objects.filter(user=user).values("quiz")] # Build a list of playlists for which the user has at least one data point user_playlists = list() for p in all_playlists: for e_id in p.get("children"): if e_id in exercise_ids or e_id in video_ids: user_playlists.append(p) break # 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" # Oh Quizzes, we hardly knew ye! # TODO (rtibbles): Sort out the status of Quizzes, and either reinstate them or remove them. # Compute quiz stats # quiz_exists, quiz_log, quiz_pct_score = cls.get_quiz_log(user, (p.get("entries") or p.get("children")), p.get("id")) # if quiz_log: # if quiz_pct_score <= 50: # quiz_status = "struggling" # elif quiz_pct_score <= 79: # quiz_status = "borderline" # else: # quiz_status = "complete" # else: # quiz_status = "notstarted" 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, # "quiz_status": quiz_status, # "quiz_exists": quiz_exists, # "quiz_pct_score": quiz_pct_score, "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 user_progress_detail(cls, user_id, playlist_id): """ Return a list of video, exercise, and quiz log PlaylistProgressDetail objects associated with a specific user and playlist ID. """ user = FacilityUser.objects.get(id=user_id) playlist = next((pl for pl in get_leafed_topics() if pl.get("id") == playlist_id), None) pl_video_ids, pl_exercise_ids = cls.get_playlist_entry_ids(playlist) # Retrieve video, exercise, and quiz logs that appear in this playlist user_vid_logs, user_ex_logs = cls.get_user_logs(user, pl_video_ids, pl_exercise_ids) # Format & append quiz the quiz log, if it exists # quiz_exists, quiz_log, quiz_pct_score = cls.get_quiz_log(user, (playlist.get("entries") or playlist.get("children")), playlist.get("id")) # Finally, sort an ordered list of the playlist entries, with user progress # injected where it exists. progress_details = list() for entity_id in playlist.get("children"): entry = {} leaf_node = get_content_cache().get(entity_id, get_exercise_cache().get(entity_id, {})) kind = leaf_node.get("kind") if kind == "Video": vid_log = next((vid_log for vid_log in user_vid_logs if vid_log["video_id"] == entity_id), None) if vid_log: if vid_log.get("complete"): status = "complete" elif vid_log.get("total_seconds_watched"): status = "inprogress" else: status = "notstarted" entry = { "id": entity_id, "kind": kind, "status": status, "score": int(float(vid_log.get("points")) / float(750) * 100), "title": leaf_node["title"], "path": leaf_node["path"], } elif kind == "Exercise": ex_log = next((ex_log for ex_log in user_ex_logs if ex_log["exercise_id"] == entity_id), None) if ex_log: if ex_log.get("struggling"): status = "struggling" elif ex_log.get("complete"): status = "complete" elif ex_log.get("attempts"): status = "inprogress" entry = { "id": entity_id, "kind": kind, "status": status, "score": ex_log.get("streak_progress"), "title": leaf_node["title"], "path": leaf_node["path"], } # Oh Quizzes, we hardly knew ye! # TODO (rtibbles): Sort out the status of Quizzes, and either reinstate them or remove them. # Quizzes were introduced to provide a way of practicing multiple types of exercise at once # However, there is currently no way to access them, and the manner for generating them (from the now deprecated Playlist models) is inaccessible # elif kind == "Quiz": # entity_id = playlist["id"] # if quiz_log: # if quiz_log.complete: # if quiz_pct_score <= 59: # status = "fail" # elif quiz_pct_score <= 79: # status = "borderline" # else: # status = "pass" # elif quiz_log.attempts: # status = "inprogress" # else: # status = "notstarted" # quiz_log_id = quiz_log.quiz # entry = { # "id": quiz_log_id, # "kind": "Quiz", # "status": status, # "score": quiz_pct_score, # "title": playlist.get("title"), # "path": "", # } if not entry: entry = cls.create_empty_entry(entity_id, kind, playlist) progress_details.append(cls(**entry)) return progress_details
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) all_playlists = get_leafed_topics(language=language) # Retrieve video, exercise, and quiz logs that appear in this playlist user_vid_logs, user_ex_logs = cls.get_user_logs(user) exercise_ids = set([ex_log["exercise_id"] for ex_log in user_ex_logs]) video_ids = set([ get_id2slug_map().get(vid_log["video_id"]) for vid_log in user_vid_logs ]) # quiz_log_ids = [ql_id["quiz"] for ql_id in QuizLog.objects.filter(user=user).values("quiz")] # Build a list of playlists for which the user has at least one data point user_playlists = list() for p in all_playlists: for e_id in p.get("children"): if e_id in exercise_ids or e_id in video_ids: user_playlists.append(p) break # 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" # Oh Quizzes, we hardly knew ye! # TODO (rtibbles): Sort out the status of Quizzes, and either reinstate them or remove them. # Compute quiz stats # quiz_exists, quiz_log, quiz_pct_score = cls.get_quiz_log(user, (p.get("entries") or p.get("children")), p.get("id")) # if quiz_log: # if quiz_pct_score <= 50: # quiz_status = "struggling" # elif quiz_pct_score <= 79: # quiz_status = "borderline" # else: # quiz_status = "complete" # else: # quiz_status = "notstarted" 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, # "quiz_status": quiz_status, # "quiz_exists": quiz_exists, # "quiz_pct_score": quiz_pct_score, "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 user_progress_detail(cls, user_id, playlist_id, language=None): """ Return a list of video, exercise, and quiz log PlaylistProgressDetail objects associated with a specific user and playlist ID. """ if not language: language = Settings.get( "default_language") or settings.LANGUAGE_CODE user = FacilityUser.objects.get(id=user_id) playlist = next( (pl for pl in get_leafed_topics() if pl.get("id") == playlist_id), None) pl_video_ids, pl_exercise_ids = cls.get_playlist_entry_ids(playlist) # Retrieve video, exercise, and quiz logs that appear in this playlist user_vid_logs, user_ex_logs = cls.get_user_logs( user, pl_video_ids, pl_exercise_ids) # Format & append quiz the quiz log, if it exists # quiz_exists, quiz_log, quiz_pct_score = cls.get_quiz_log(user, (playlist.get("entries") or playlist.get("children")), playlist.get("id")) # Finally, sort an ordered list of the playlist entries, with user progress # injected where it exists. progress_details = list() for entity_id in playlist.get("children"): entry = {} leaf_node = get_content_cache( language=language).get(entity_id) or get_exercise_cache( language=language).get(entity_id) or {} kind = leaf_node.get("kind") status = "notstarted" score = 0 if kind == "Video": vid_log = next((vid_log for vid_log in user_vid_logs if vid_log["video_id"] == entity_id), None) if vid_log: if vid_log.get("complete"): status = "complete" elif vid_log.get("total_seconds_watched"): status = "inprogress" score = int( float(vid_log.get("points")) / float(750) * 100) elif kind == "Exercise": ex_log = next((ex_log for ex_log in user_ex_logs if ex_log["exercise_id"] == entity_id), None) if ex_log: if ex_log.get("struggling"): status = "struggling" elif ex_log.get("complete"): status = "complete" elif ex_log.get("attempts"): status = "inprogress" score = ex_log.get('streak_progress') entry = { "id": entity_id, "kind": kind, "status": status, "score": score, "title": leaf_node["title"], "path": leaf_node["path"], } progress_details.append(cls(**entry)) return progress_details
def user_progress_detail(cls, user_id, playlist_id): """ Return a list of video, exercise, and quiz log PlaylistProgressDetail objects associated with a specific user and playlist ID. """ user = FacilityUser.objects.get(id=user_id) playlist = next( ( pl for pl in [plist.__dict__ for plist in Playlist.all()] + get_leafed_topics() if pl.get("id") == playlist_id ), None, ) pl_video_ids, pl_exercise_ids = cls.get_playlist_entry_ids(playlist) # Retrieve video, exercise, and quiz logs that appear in this playlist user_vid_logs, user_ex_logs = cls.get_user_logs(user, pl_video_ids, pl_exercise_ids) # Format & append quiz the quiz log, if it exists quiz_exists, quiz_log, quiz_pct_score = cls.get_quiz_log( user, (playlist.get("entries") or playlist.get("children")), playlist.get("id") ) # Finally, sort an ordered list of the playlist entries, with user progress # injected where it exists. progress_details = list() for ent in playlist.get("entries") or playlist.get("children"): entry = {} kind = ent.get("entity_kind") or ent.get("kind") if kind == "Divider": continue elif kind == "Video": entity_id = get_slug2id_map().get(ent.get("entity_id")) or ent.get("id") vid_log = next((vid_log for vid_log in user_vid_logs if vid_log["video_id"] == entity_id), None) if vid_log: if vid_log.get("complete"): status = "complete" elif vid_log.get("total_seconds_watched"): status = "inprogress" else: status = "notstarted" leaf_node = get_content_cache().get(vid_log["video_id"]) entry = { "id": entity_id, "kind": kind, "status": status, "score": int(float(vid_log.get("points")) / float(750) * 100), "title": leaf_node["title"], "path": leaf_node["path"], } elif kind == "Exercise": entity_id = ent.get("entity_id") or ent.get("id") ex_log = next((ex_log for ex_log in user_ex_logs if ex_log["exercise_id"] == entity_id), None) if ex_log: if ex_log.get("struggling"): status = "struggling" elif ex_log.get("complete"): status = "complete" elif ex_log.get("attempts"): status = "inprogress" ex_log_id = ex_log.get("exercise_id") leaf_node = get_exercise_cache().get(ex_log_id) entry = { "id": ex_log_id, "kind": kind, "status": status, "score": ex_log.get("streak_progress"), "title": leaf_node["title"], "path": leaf_node["path"], } elif kind == "Quiz": entity_id = playlist["id"] if quiz_log: if quiz_log.complete: if quiz_pct_score <= 59: status = "fail" elif quiz_pct_score <= 79: status = "borderline" else: status = "pass" elif quiz_log.attempts: status = "inprogress" else: status = "notstarted" quiz_log_id = quiz_log.quiz entry = { "id": quiz_log_id, "kind": "Quiz", "status": status, "score": quiz_pct_score, "title": playlist.get("title"), "path": "", } if not entry: entry = cls.create_empty_entry(entity_id, kind, playlist) progress_details.append(cls(**entry)) return progress_details
def user_progress(cls, user_id): """ Return a list of PlaylistProgress objects associated with the user. """ user = FacilityUser.objects.get(id=user_id) all_playlists = [getattr(pl, "__dict__", pl) for pl in Playlist.all() + get_leafed_topics()] # Retrieve video, exercise, and quiz logs that appear in this playlist user_vid_logs, user_ex_logs = cls.get_user_logs(user) exercise_ids = set([ex_log["exercise_id"] for ex_log in user_ex_logs]) video_ids = set([get_id2slug_map().get(vid_log["video_id"]) for vid_log in user_vid_logs]) quiz_log_ids = [ql_id["quiz"] for ql_id in QuizLog.objects.filter(user=user).values("quiz")] # Build a list of playlists for which the user has at least one data point ## TODO(dylanjbarth) this won't pick up playlists the user is assigned but has not started yet. user_playlists = list() for p in all_playlists: for e in p.get("entries") or p.get("children"): if (e.get("entity_kind") or e.get("kind")) == "Video" or ( e.get("entity_kind") or e.get("kind") ) == "Exercise": entity_id = convert_leaf_url_to_id((e.get("entity_id") or e.get("id"))) if entity_id in exercise_ids or entity_id in video_ids: user_playlists.append(p) break elif e.get("entity_kind") == "Quiz": if p.get("id") in quiz_log_ids: user_playlists.append(p) # 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 * 100) ex_pct_incomplete = int(float(n_ex_incomplete) / n_pl_exercises * 100) ex_pct_struggling = int(float(n_ex_struggling) / n_pl_exercises * 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" # Compute quiz stats quiz_exists, quiz_log, quiz_pct_score = cls.get_quiz_log( user, (p.get("entries") or p.get("children")), p.get("id") ) if quiz_log: if quiz_pct_score <= 50: quiz_status = "struggling" elif quiz_pct_score <= 79: quiz_status = "borderline" else: quiz_status = "complete" else: quiz_status = "notstarted" 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, "quiz_status": quiz_status, "quiz_exists": quiz_exists, "quiz_pct_score": quiz_pct_score, "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