예제 #1
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
예제 #2
0
def show_logs(request, ndays=None):
    """Show file-based logging info for video downloads, language packs, and subtitles"""
    ndays = ndays or int(request.GET.get("days", 7))

    def get_logger_filename(logger_type):
        return stats_logger(logger_type).handlers[0].baseFilename

    def parse_data(logger_type, data_fields, windowsize=128, ndays=None):
        parsed_data = {}
        nparts = len(data_fields)
        summary_data = dict([(fld, {}) for fld in (data_fields + ["date"])])

        filepath = get_logger_filename(logger_type)
        if not os.path.exists(filepath):
            return (parsed_data, summary_data)

        # Group by ip, date, and youtube_id
        old_data = ""
        first_loop = True
        last_loop = False
        with open(filepath, "r") as fp:
            fp.seek(0, 2)  # go to the end of the stream
            while True:
                # Read the next chunk of data
                try:
                    # Get the data
                    try:
                        if first_loop:
                            fp.seek(-windowsize, 1)  # go backwards by a few
                            first_loop = False
                        else:
                            fp.seek(-2 * windowsize, 1)  # go backwards by a few

                        cur_data = fp.read(windowsize) + old_data
                    except:
                        if last_loop and not old_data:
                            raise
                        elif last_loop:
                            cur_data = old_data
                            old_data = ""
                        else:
                            last_loop = True
                            fp.seek(0)
                            cur_data = fp.read(windowsize) + old_data  # could be some overlap...

                    if not cur_data:
                        break;
                except:
                    break

                # Parse the data
                lines = cur_data.split("\n")
                old_data = lines[0] if len(lines) > 1 else ""
                new_data = lines[1:] if len(lines) > 1 else lines
                for l in new_data:
                    if not l:
                        continue

                    # All start with a date
                    parts = l.split(" - ", 2)
                    if len(parts) != 2:
                        continue
                    tim = parts[0]
                    dat = tim.split(" ")[0]

                    # Validate that this date is within the accepted range
                    parsed_date = datetime.datetime.strptime(dat, "%Y-%m-%d")
                    #logging.debug("%s %s" % (parsed_date, (datetime.datetime.now() - timedelta(days=ndays))))
                    if ndays is not None and datetime.datetime.now() - timedelta(days=ndays) > parsed_date:
                        last_loop = True
                        old_data = ""
                        break;

                    # The rest is semicolon-delimited
                    parts = parts[1].split(";")  # vd;127.0.0.1;xvnpSRO9IDM

                    # Now save things off
                    parsed_data[tim] = dict([(data_fields[idx], parts[idx]) for idx in range(nparts)])
                    summary_data["date"][dat] = 1 + summary_data["date"].get(dat, 0)
                    for idx in range(nparts):
                        summary_data[data_fields[idx]][parts[idx]] = 1 + summary_data[data_fields[idx]].get(parts[idx], 0)

        for key, val in summary_data.iteritems():
            summary_data[key] = sorted_dict(val, key=lambda t: t[0])

        return (parsed_data, summary_data)

    (video_raw_data, video_summary_data) = parse_data("videos", ["task_id", "ip_address", "youtube_id"], ndays=ndays)
    (lp_raw_data, lp_summary_data)       = parse_data("language_packs", ["task_id", "ip_address", "lang_code", "version"], ndays=ndays)
    (srt_raw_data, srt_summary_data)     = parse_data("subtitles", ["task_id", "ip_address", "lang_code", "youtube_id"], ndays=ndays)

    return {
        "ndays": ndays,
        "videos": {
            "raw": video_raw_data,
            "dates": video_summary_data["date"],
            "ips": video_summary_data["ip_address"],
            "slugs": sum_counter(video_summary_data["youtube_id"], fn=lambda yid: get_id2slug_map().get(get_video_id(yid))),
            "lang_codes": sum_counter(video_summary_data["youtube_id"], fn=lambda yid: get_video_language(yid)),
        },
        "language_packs": {
            "raw": lp_raw_data,
            "dates": lp_summary_data["date"],
            "ips": lp_summary_data["ip_address"],
            "lang_codes": lp_summary_data["lang_code"],
            "versions": lp_summary_data["version"],
        },
        "subtitles": {
            "raw": srt_raw_data,
            "dates": srt_summary_data["date"],
            "ips": srt_summary_data["ip_address"],
            "lang_codes": srt_summary_data["lang_code"],
        },
    }
예제 #3
0
    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
예제 #4
0
def show_logs(request, ndays=None):
    """Show file-based logging info for video downloads, language packs, and subtitles"""
    ndays = ndays or int(request.GET.get("days", 7))

    def get_logger_filename(logger_type):
        return stats_logger(logger_type).handlers[0].baseFilename

    def parse_data(logger_type, data_fields, windowsize=128, ndays=None):
        parsed_data = {}
        nparts = len(data_fields)
        summary_data = dict([(fld, {}) for fld in (data_fields + ["date"])])

        filepath = get_logger_filename(logger_type)
        if not os.path.exists(filepath):
            return (parsed_data, summary_data)

        # Group by ip, date, and youtube_id
        old_data = ""
        first_loop = True
        last_loop = False
        with open(filepath, "r") as fp:
            fp.seek(0, 2)  # go to the end of the stream
            while True:
                # Read the next chunk of data
                try:
                    # Get the data
                    try:
                        if first_loop:
                            fp.seek(-windowsize, 1)  # go backwards by a few
                            first_loop = False
                        else:
                            fp.seek(-2 * windowsize,
                                    1)  # go backwards by a few

                        cur_data = fp.read(windowsize) + old_data
                    except:
                        if last_loop and not old_data:
                            raise
                        elif last_loop:
                            cur_data = old_data
                            old_data = ""
                        else:
                            last_loop = True
                            fp.seek(0)
                            cur_data = fp.read(
                                windowsize
                            ) + old_data  # could be some overlap...

                    if not cur_data:
                        break
                except:
                    break

                # Parse the data
                lines = cur_data.split("\n")
                old_data = lines[0] if len(lines) > 1 else ""
                new_data = lines[1:] if len(lines) > 1 else lines
                for l in new_data:
                    if not l:
                        continue

                    # All start with a date
                    parts = l.split(" - ", 2)
                    if len(parts) != 2:
                        continue
                    tim = parts[0]
                    dat = tim.split(" ")[0]

                    # Validate that this date is within the accepted range
                    parsed_date = datetime.datetime.strptime(dat, "%Y-%m-%d")
                    #logging.debug("%s %s" % (parsed_date, (datetime.datetime.now() - timedelta(days=ndays))))
                    if ndays is not None and datetime.datetime.now(
                    ) - timedelta(days=ndays) > parsed_date:
                        last_loop = True
                        old_data = ""
                        break

                    # The rest is semicolon-delimited
                    parts = parts[1].split(";")  # vd;127.0.0.1;xvnpSRO9IDM

                    # Now save things off
                    parsed_data[tim] = dict([(data_fields[idx], parts[idx])
                                             for idx in range(nparts)])
                    summary_data["date"][dat] = 1 + summary_data["date"].get(
                        dat, 0)
                    for idx in range(nparts):
                        summary_data[data_fields[idx]][parts[
                            idx]] = 1 + summary_data[data_fields[idx]].get(
                                parts[idx], 0)

        for key, val in summary_data.iteritems():
            summary_data[key] = sorted_dict(val, key=lambda t: t[0])

        return (parsed_data, summary_data)

    (video_raw_data,
     video_summary_data) = parse_data("videos",
                                      ["task_id", "ip_address", "youtube_id"],
                                      ndays=ndays)
    (lp_raw_data, lp_summary_data) = parse_data(
        "language_packs", ["task_id", "ip_address", "lang_code", "version"],
        ndays=ndays)
    (srt_raw_data, srt_summary_data) = parse_data(
        "subtitles", ["task_id", "ip_address", "lang_code", "youtube_id"],
        ndays=ndays)

    return {
        "ndays": ndays,
        "videos": {
            "raw":
            video_raw_data,
            "dates":
            video_summary_data["date"],
            "ips":
            video_summary_data["ip_address"],
            "slugs":
            sum_counter(
                video_summary_data["youtube_id"],
                fn=lambda yid: get_id2slug_map().get(get_video_id(yid))),
            "lang_codes":
            sum_counter(video_summary_data["youtube_id"],
                        fn=lambda yid: get_video_language(yid)),
        },
        "language_packs": {
            "raw": lp_raw_data,
            "dates": lp_summary_data["date"],
            "ips": lp_summary_data["ip_address"],
            "lang_codes": lp_summary_data["lang_code"],
            "versions": lp_summary_data["version"],
        },
        "subtitles": {
            "raw": srt_raw_data,
            "dates": srt_summary_data["date"],
            "ips": srt_summary_data["ip_address"],
            "lang_codes": srt_summary_data["lang_code"],
        },
    }
예제 #5
0
"""Classes used by the student progress tastypie API"""
import json

from django.core.urlresolvers import reverse, NoReverseMatch
from django.core.exceptions import ObjectDoesNotExist

from kalite.facility.models import FacilityUser
from kalite.main.models import ExerciseLog, VideoLog
from kalite.playlist.models import VanillaPlaylist as Playlist, QuizLog
from kalite.topic_tools import get_slug2id_map, get_id2slug_map, convert_leaf_url_to_id, get_leafed_topics, get_content_cache, get_exercise_cache

ID2SLUG_MAP = get_id2slug_map()
SLUG2ID_MAP = get_slug2id_map()


class PlaylistProgressParent:
    """Parent class for helpful class methods"""
    @classmethod
    def get_playlist_entry_ids(cls, playlist):
        """Return a tuple of the playlist's video ids and exercise ids as sets"""
        playlist_entries = playlist.get("entries") or playlist.get("children")
        # TODO(dylanjbarth): 0.13 playlist entities shouldn't have the /v or /e in them at all.
        pl_video_ids = set([
            SLUG2ID_MAP.get(entry.get("entity_id")) or entry.get("id")
            for entry in playlist_entries
            if entry.get("entity_kind") == "Video"
        ])
        pl_exercise_ids = set([
            entry.get("entity_id") or entry.get("id")
            for entry in playlist_entries
            if (entry.get("entity_kind") or entry.get("kind")) == "Exercise"
예제 #6
0
    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
예제 #7
0
파일: models.py 프로젝트: kanyanat3/ka-lite
    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