Beispiel #1
0
    def create_playlist_progress(cls, user, quiz=True):
        default_playlist = "g4_u401_p1"
        playlist = [pl for pl in Playlist.all() if pl.id == default_playlist]
        assert (
            playlist[0].id == default_playlist
        ), "Unexpected playlist ID. Update tests to match new playlists.json"
        playlist = playlist[0]

        # Creating one specific entry for a specific item in the playlist
        # Note (aron): The new nalanda playlists don't have any videos now
        # So I commented those out
        # VideoLog(**{
        #     "user": user,
        #     "video_id": "nFsQA2Zvy1o",
        #     "youtube_id": "nFsQA2Zvy1o",
        #     "total_seconds_watched": 290,
        #     "points": 750,
        #     "complete": True,
        # }).save()

        ExerciseLog(
            **{
                "user": user,
                "exercise_id": "telling_time",
                "streak_progress": 50,
                "attempts": 25,
                "points": 100,
                "complete": False,
                "struggling": True,
            }).save()

        if quiz:
            QuizLog(
                **{
                    "user": user,
                    "quiz": default_playlist,
                    "complete": True,
                    "attempts": 1,
                    "response_log": "[4]",
                    "total_correct": 4,
                    "total_number": 6,
                }).save()

        return playlist
    def create_playlist_progress(cls, user, quiz=True):
        default_playlist = "g4_u401_p1"
        playlist = [pl for pl in Playlist.all() if pl.id == default_playlist]
        assert(playlist[0].id == default_playlist), "Unexpected playlist ID. Update tests to match new playlists.json"
        playlist = playlist[0]

        # Creating one specific entry for a specific item in the playlist
        # Note (aron): The new nalanda playlists don't have any videos now
        # So I commented those out
        # VideoLog(**{
        #     "user": user,
        #     "video_id": "nFsQA2Zvy1o",
        #     "youtube_id": "nFsQA2Zvy1o",
        #     "total_seconds_watched": 290,
        #     "points": 750,
        #     "complete": True,
        # }).save()

        ExerciseLog(**{
            "user": user,
            "exercise_id": "telling_time",
            "streak_progress": 50,
            "attempts": 25,
            "points": 100,
            "complete": False,
            "struggling": True,
        }).save()

        if quiz:
            QuizLog(**{
                "user": user,
                "quiz": default_playlist,
                "complete": True,
                "attempts": 1,
                "response_log": "[4]",
                "total_correct": 4,
                "total_number": 6,
            }).save()

        return playlist
Beispiel #3
0
def tabular_view(request, report_type="exercise"):
    """Tabular view also gets data server-side."""
    # important for setting the defaults for the coach nav bar

    language = lcode_to_django_lang(request.language)

    facility, group_id, context = coach_nav_context(request, "tabular")

    # Define how students are ordered--used to be as efficient as possible.
    student_ordering = ["last_name", "first_name", "username"]

    # Get a list of topics (sorted) and groups
    topics = [get_node_cache("Topic", language=language).get(tid["id"]) for tid in get_knowledgemap_topics(language=language) if report_type.title() in tid["contains"]]
    playlists = Playlist.all()

    (groups, facilities, ungrouped_available) = get_accessible_objects_from_logged_in_user(request, facility=facility)

    context.update(plotting_metadata_context(request, facility=facility))

    context.update({
        # For translators: the following two translations are nouns
        "report_types": ({"value": "exercise", "name":_("exercise")}, {"value": "video", "name": _("video")}),
        "request_report_type": report_type,
        "topics": [{"id": t["id"], "title": t["title"]} for t in topics if t],
        "playlists": [{"id": p.id, "title": p.title, "tag": p.tag} for p in playlists if p],
    })

    # get querystring info
    topic_id = request.GET.get("topic", "")
    playlist_id = request.GET.get("playlist", "")
    # No valid data; just show generic
    # Exactly one of topic_id or playlist_id should be present
    if not ((topic_id or playlist_id) and not (topic_id and playlist_id)):
        if playlists:
            messages.add_message(request, WARNING, _("Please select a playlist."))
        elif topics:
            messages.add_message(request, WARNING, _("Please select a topic."))
        return context

    playlist = (filter(lambda p: p.id == playlist_id, Playlist.all()) or [None])[0]

    if group_id:
        # Narrow by group
        if group_id == control_panel_api_resources.UNGROUPED_KEY:
            users = FacilityUser.objects.filter(group__isnull=True, is_teacher=False)
            if facility:
                # filter only those ungrouped students for the facility
                users = users.filter(facility=facility)
                users = users.order_by(*student_ordering)
            else:
                # filter all ungroup students
                users = FacilityUser.objects.filter(group__isnull=True, is_teacher=False).order_by(*student_ordering)

        else:
            users = FacilityUser.objects.filter(
                group=group_id, is_teacher=False).order_by(*student_ordering)

    elif facility:
        # Narrow by facility
        search_groups = [groups_dict["groups"] for groups_dict in groups if groups_dict["facility"] == facility.id]
        assert len(search_groups) <= 1, "Should only have one or zero matches."

        # Return groups and ungrouped
        search_groups = search_groups[0]  # make sure to include ungrouped students
        users = FacilityUser.objects.filter(
            Q(group__in=search_groups) | Q(group=None, facility=facility), is_teacher=False).order_by(*student_ordering)

    else:
        # Show all (including ungrouped)
        search_groups = []
        for groups_dict in groups:
            search_groups += groups_dict["groups"]
        users = FacilityUser.objects.filter(
            Q(group__in=search_groups) | Q(group=None), is_teacher=False).order_by(*student_ordering)

    # We have enough data to render over a group of students
    # Get type-specific information
    if report_type == "exercise":
        # Fill in exercises
        if topic_id:
            exercises = get_topic_exercises(topic_id=topic_id)
        elif playlist:
            exercises = playlist.get_playlist_entries("Exercise", language=language)

        context["exercises"] = exercises

        # More code, but much faster
        exercise_names = [ex["id"] for ex in context["exercises"]]
        # Get students
        context["students"] = []
        exlogs = ExerciseLog.objects \
            .filter(user__in=users, exercise_id__in=exercise_names) \
            .order_by(*["user__%s" % field for field in student_ordering]) \
            .values("user__id", "struggling", "complete", "exercise_id")
        exlogs = list(exlogs)  # force the query to be evaluated

        exlog_idx = 0
        for user in users:
            log_table = {}
            while exlog_idx < len(exlogs) and exlogs[exlog_idx]["user__id"] == user.id:
                log_table[exlogs[exlog_idx]["exercise_id"]] = exlogs[exlog_idx]
                exlog_idx += 1

            context["students"].append({  # this could be DRYer
                "first_name": user.first_name,
                "last_name": user.last_name,
                "username": user.username,
                "name": user.get_name(),
                "id": user.id,
                "exercise_logs": log_table,
            })

    elif report_type == "video":
        # Fill in videos
        if topic_id:
            context["videos"] = get_topic_videos(topic_id=topic_id)
        elif playlist:
            context["videos"] = playlist.get_playlist_entries("Video", language=language)

        # More code, but much faster
        video_ids = [vid["id"] for vid in context["videos"]]
        # Get students
        context["students"] = []
        vidlogs = VideoLog.objects \
            .filter(user__in=users, video_id__in=video_ids) \
            .order_by(*["user__%s" % field for field in student_ordering])\
            .values("user__id", "complete", "video_id", "total_seconds_watched", "points")
        vidlogs = list(vidlogs)  # force the query to be executed now

        vidlog_idx = 0
        for user in users:
            log_table = {}
            while vidlog_idx < len(vidlogs) and vidlogs[vidlog_idx]["user__id"] == user.id:
                log_table[vidlogs[vidlog_idx]["video_id"]] = vidlogs[vidlog_idx]
                vidlog_idx += 1

            context["students"].append({  # this could be DRYer
                "first_name": user.first_name,
                "last_name": user.last_name,
                "username": user.username,
                "name": user.get_name(),
                "id": user.id,
                "video_logs": log_table,
            })
    else:
        raise Http404(_("Unknown report_type: %(report_type)s") % {"report_type": report_type})

    # Validate results by showing user messages.
    if not users:
        # 1. check group facility groups
        if len(groups) > 0 and not groups[0]['groups']:
            # 1. No groups available (for facility) and "no students" returned.
            messages.add_message(request, WARNING,
                                 _("No learner accounts have been created for selected facility/group."))
        elif topic_id and playlist_id:
            # 2. Both topic and playlist are selected.
            messages.add_message(request, WARNING, _("Please select either a topic or a playlist above, but not both."))
        elif not topic_id and not playlist_id:
            # 3. Group was selected, but data not queried because a topic or playlist was not selected.
            if playlists:
                # 4. No playlist was selected.
                messages.add_message(request, WARNING, _("Please select a playlist."))
            elif topics:
                # 5. No topic was selected.
                messages.add_message(request, WARNING, _("Please select a topic."))
        else:
            # 6. Everything specified, but no users fit the query.
            messages.add_message(request, WARNING, _("No learner accounts in this group have been created."))
    # End: Validate results by showing user messages.

    log_coach_report_view(request)

    return context
Beispiel #4
0
    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
Beispiel #5
0
    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
Beispiel #6
0
def tabular_view(request, report_type="exercise"):
    """Tabular view also gets data server-side."""
    # important for setting the defaults for the coach nav bar

    language = lcode_to_django_lang(request.language)

    facility, group_id, context = coach_nav_context(request, "tabular")

    # Define how students are ordered--used to be as efficient as possible.
    student_ordering = ["last_name", "first_name", "username"]

    # Get a list of topics (sorted) and groups
    topics = [
        get_node_cache("Topic", language=language).get(tid["id"])
        for tid in get_knowledgemap_topics(language=language)
        if report_type.title() in tid["contains"]
    ]
    playlists = Playlist.all()

    (groups, facilities,
     ungrouped_available) = get_accessible_objects_from_logged_in_user(
         request, facility=facility)

    context.update(plotting_metadata_context(request, facility=facility))

    context.update({
        # For translators: the following two translations are nouns
        "report_types": ({
            "value": "exercise",
            "name": _("exercise")
        }, {
            "value": "video",
            "name": _("video")
        }),
        "request_report_type":
        report_type,
        "topics": [{
            "id": t["id"],
            "title": t["title"]
        } for t in topics if t],
        "playlists": [{
            "id": p.id,
            "title": p.title,
            "tag": p.tag
        } for p in playlists if p],
    })

    # get querystring info
    topic_id = request.GET.get("topic", "")
    playlist_id = request.GET.get("playlist", "")
    # No valid data; just show generic
    # Exactly one of topic_id or playlist_id should be present
    if not ((topic_id or playlist_id) and not (topic_id and playlist_id)):
        if playlists:
            messages.add_message(request, WARNING,
                                 _("Please select a playlist."))
        elif topics:
            messages.add_message(request, WARNING, _("Please select a topic."))
        return context

    playlist = (filter(lambda p: p.id == playlist_id, Playlist.all())
                or [None])[0]

    if group_id:
        # Narrow by group
        if group_id == control_panel_api_resources.UNGROUPED_KEY:
            users = FacilityUser.objects.filter(group__isnull=True,
                                                is_teacher=False)
            if facility:
                # filter only those ungrouped students for the facility
                users = users.filter(facility=facility)
                users = users.order_by(*student_ordering)
            else:
                # filter all ungroup students
                users = FacilityUser.objects.filter(
                    group__isnull=True,
                    is_teacher=False).order_by(*student_ordering)

        else:
            users = FacilityUser.objects.filter(
                group=group_id, is_teacher=False).order_by(*student_ordering)

    elif facility:
        # Narrow by facility
        search_groups = [
            groups_dict["groups"] for groups_dict in groups
            if groups_dict["facility"] == facility.id
        ]
        assert len(search_groups) <= 1, "Should only have one or zero matches."

        # Return groups and ungrouped
        search_groups = search_groups[
            0]  # make sure to include ungrouped students
        users = FacilityUser.objects.filter(
            Q(group__in=search_groups) | Q(group=None, facility=facility),
            is_teacher=False).order_by(*student_ordering)

    else:
        # Show all (including ungrouped)
        search_groups = []
        for groups_dict in groups:
            search_groups += groups_dict["groups"]
        users = FacilityUser.objects.filter(
            Q(group__in=search_groups) | Q(group=None),
            is_teacher=False).order_by(*student_ordering)

    # We have enough data to render over a group of students
    # Get type-specific information
    if report_type == "exercise":
        # Fill in exercises
        if topic_id:
            exercises = get_topic_exercises(topic_id=topic_id)
        elif playlist:
            exercises = playlist.get_playlist_entries("Exercise",
                                                      language=language)

        context["exercises"] = exercises

        # More code, but much faster
        exercise_names = [ex["id"] for ex in context["exercises"]]
        # Get students
        context["students"] = []
        exlogs = ExerciseLog.objects \
            .filter(user__in=users, exercise_id__in=exercise_names) \
            .order_by(*["user__%s" % field for field in student_ordering]) \
            .values("user__id", "struggling", "complete", "exercise_id")
        exlogs = list(exlogs)  # force the query to be evaluated

        exlog_idx = 0
        for user in users:
            log_table = {}
            while exlog_idx < len(
                    exlogs) and exlogs[exlog_idx]["user__id"] == user.id:
                log_table[exlogs[exlog_idx]["exercise_id"]] = exlogs[exlog_idx]
                exlog_idx += 1

            context["students"].append({  # this could be DRYer
                "first_name": user.first_name,
                "last_name": user.last_name,
                "username": user.username,
                "name": user.get_name(),
                "id": user.id,
                "exercise_logs": log_table,
            })

    elif report_type == "video":
        # Fill in videos
        if topic_id:
            context["videos"] = get_topic_videos(topic_id=topic_id)
        elif playlist:
            context["videos"] = playlist.get_playlist_entries(
                "Video", language=language)

        # More code, but much faster
        video_ids = [vid["id"] for vid in context["videos"]]
        # Get students
        context["students"] = []
        vidlogs = VideoLog.objects \
            .filter(user__in=users, video_id__in=video_ids) \
            .order_by(*["user__%s" % field for field in student_ordering])\
            .values("user__id", "complete", "video_id", "total_seconds_watched", "points")
        vidlogs = list(vidlogs)  # force the query to be executed now

        vidlog_idx = 0
        for user in users:
            log_table = {}
            while vidlog_idx < len(
                    vidlogs) and vidlogs[vidlog_idx]["user__id"] == user.id:
                log_table[vidlogs[vidlog_idx]
                          ["video_id"]] = vidlogs[vidlog_idx]
                vidlog_idx += 1

            context["students"].append({  # this could be DRYer
                "first_name": user.first_name,
                "last_name": user.last_name,
                "username": user.username,
                "name": user.get_name(),
                "id": user.id,
                "video_logs": log_table,
            })
    else:
        raise Http404(
            _("Unknown report_type: %(report_type)s") %
            {"report_type": report_type})

    # Validate results by showing user messages.
    if not users:
        # 1. check group facility groups
        if len(groups) > 0 and not groups[0]['groups']:
            # 1. No groups available (for facility) and "no students" returned.
            messages.add_message(
                request, WARNING,
                _("No learner accounts have been created for selected facility/group."
                  ))
        elif topic_id and playlist_id:
            # 2. Both topic and playlist are selected.
            messages.add_message(
                request, WARNING,
                _("Please select either a topic or a playlist above, but not both."
                  ))
        elif not topic_id and not playlist_id:
            # 3. Group was selected, but data not queried because a topic or playlist was not selected.
            if playlists:
                # 4. No playlist was selected.
                messages.add_message(request, WARNING,
                                     _("Please select a playlist."))
            elif topics:
                # 5. No topic was selected.
                messages.add_message(request, WARNING,
                                     _("Please select a topic."))
        else:
            # 6. Everything specified, but no users fit the query.
            messages.add_message(
                request, WARNING,
                _("No learner accounts in this group have been created."))
    # End: Validate results by showing user messages.

    log_coach_report_view(request)

    return context
Beispiel #7
0
    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
Beispiel #8
0
    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