Exemplo n.º 1
0
 def test_get_content_parents(self):
     """
     The function get_content_parents() should return a empty list when an
     empty list of ids is passed to it.
     """
     self.assertEqual(get_content_parents(ids=list()), list())
Exemplo n.º 2
0
def aggregate_learner_logs(request):

    learners = get_learners_from_GET(request)

    event_limit = request.GET.get("event_limit", 10)

    # Look back a week by default
    time_window = request.GET.get("time_window", 7)

    start_date = request.GET.get("start_date")

    end_date = request.GET.get("end_date")

    topic_ids = json.loads(request.GET.get("topic_ids", "[]"))

    log_types = request.GET.getlist("log_type", ["exercise", "video", "content"])

    output_logs = []

    output_dict = {
        "content_time_spent": 0,
        "exercise_attempts": 0,
        "exercise_mastery": None,
        "total_in_progress": 0,
        "total_complete": 0,
        "total_struggling": 0,
        "total_not_attempted": 0,
        "available_topics": [],
    }

    end_date = datetime.datetime.strptime(end_date,'%Y/%m/%d') if end_date else datetime.datetime.now()

    start_date = datetime.datetime.strptime(start_date,'%Y/%m/%d') if start_date else end_date - datetime.timedelta(time_window)

    number_content = 0

    all_object_ids = set()

    for log_type in log_types:

        LogModel, fields, id_field, obj_ids, objects = return_log_type_details(log_type, topic_ids)   

        log_objects = LogModel.objects.filter(
            user__in=learners,
            latest_activity_timestamp__gte=start_date,
            latest_activity_timestamp__lte=end_date, **obj_ids).order_by("-latest_activity_timestamp")

        number_content += len(set(log_objects.values_list(id_field, flat=True)))

        if log_type == "video":
            output_dict["total_in_progress"] += log_objects.filter(complete=False).count()
            output_dict["content_time_spent"] += log_objects.aggregate(Sum("total_seconds_watched"))["total_seconds_watched__sum"] or 0
        elif log_type == "content":
            output_dict["total_in_progress"] += log_objects.filter(complete=False).count()
            output_dict["content_time_spent"] += log_objects.aggregate(Sum("time_spent"))["time_spent__sum"] or 0
        elif log_type == "exercise":
            output_dict["total_struggling"] = log_objects.filter(struggling=True).count()
            output_dict["total_in_progress"] += log_objects.filter(complete=False, struggling=False).count()
            output_dict["exercise_attempts"] = AttemptLog.objects.filter(user__in=learners,
                timestamp__gte=start_date,
                timestamp__lte=end_date, **obj_ids).count()
            if log_objects.aggregate(Avg("streak_progress"))["streak_progress__avg"] is not None:
                output_dict["exercise_mastery"] = round(log_objects.aggregate(Avg("streak_progress"))["streak_progress__avg"])
        output_logs.extend(log_objects)
        output_dict["total_complete"] += log_objects.filter(complete=True).count()

        object_buffer = LogModel.objects.filter(
            user__in=learners,
            latest_activity_timestamp__gte=start_date,
            latest_activity_timestamp__lte=end_date).values_list(id_field, flat=True)

        if len(object_buffer) > 1:
            all_object_ids.update(object_buffer)
        elif len(object_buffer) == 1:
            all_object_ids.add(object_buffer)
    if len(all_object_ids) > 0:
        output_dict["available_topics"] = map(lambda x: {"id": x.get("id"), "title": x.get("title")}, get_content_parents(ids=list(all_object_ids)))
    output_dict["total_not_attempted"] = number_content*len(learners) - (
        output_dict["total_complete"] + output_dict["total_struggling"] + output_dict["total_in_progress"])
    # Report total time in hours
    output_dict["content_time_spent"] = round(output_dict["content_time_spent"]/3600.0,1)
    output_logs.sort(key=lambda x: x.latest_activity_timestamp, reverse=True)

    learner_event_objects = dict([(item["id"], item) for item in get_topic_nodes(
        ids=[getattr(log, "exercise_id", getattr(log, "video_id", getattr(log, "content_id", ""))) for log in output_logs[:event_limit]], language=request.language) or []])

    output_dict["learner_events"] = [{
        "learner": log.user.get_name(),
        "complete": log.complete,
        "struggling": getattr(log, "struggling", None),
        "progress": getattr(log, "streak_progress", getattr(log, "progress", None)),
        "content": learner_event_objects.get(getattr(log, "exercise_id", getattr(log, "video_id", getattr(log, "content_id", ""))), {}),
        } for log in output_logs[:event_limit]]
    output_dict["total_time_logged"] = round((UserLogSummary.objects\
        .filter(user__in=learners, start_datetime__gte=start_date, start_datetime__lte=end_date)\
        .aggregate(Sum("total_seconds")).get("total_seconds__sum") or 0)/3600.0, 1)
    return JsonResponse(output_dict)
Exemplo n.º 3
0
def aggregate_learner_logs(request):

    learners = get_learners_from_GET(request)

    event_limit = request.GET.get("event_limit", 10)

    # Look back a week by default
    time_window = request.GET.get("time_window", 7)

    start_date = request.GET.get("start_date")

    end_date = request.GET.get("end_date")

    topic_ids = json.loads(request.GET.get("topic_ids", "[]"))

    # Previously, we defaulted to all types of logs, but views on coach reports
    # seem to assume only exercises
    # log_types = request.GET.getlist("log_type", ["exercise", "video", "content"])
    log_types = request.GET.getlist("log_type", ["exercise"])

    output_logs = []

    output_dict = {
        "content_time_spent": 0,
        "exercise_attempts": 0,
        "exercise_mastery": None,
        "total_in_progress": 0,
        "total_complete": 0,
        "total_struggling": 0,
        "total_not_attempted": 0,
        "available_topics": [],
    }

    end_date = datetime.datetime.strptime(
        end_date, '%Y/%m/%d') if end_date else datetime.datetime.now()

    start_date = datetime.datetime.strptime(
        start_date, '%Y/%m/%d'
    ) if start_date else end_date - datetime.timedelta(time_window)

    number_content = 0

    all_object_ids = set()

    for log_type in log_types:

        LogModel, fields, id_field, obj_ids, objects = return_log_type_details(
            log_type, topic_ids, request.language)

        log_objects = LogModel.objects.filter(
            user__in=learners,
            latest_activity_timestamp__gte=start_date,
            latest_activity_timestamp__lte=end_date,
            **obj_ids).order_by("-latest_activity_timestamp")

        number_content += len(set(log_objects.values_list(id_field,
                                                          flat=True)))

        output_dict["total_complete"] += log_objects.filter(
            complete=True).count()
        if log_type == "video":
            output_dict["total_in_progress"] += log_objects.filter(
                complete=False).count()
            output_dict["content_time_spent"] += log_objects.aggregate(
                Sum("total_seconds_watched")
            )["total_seconds_watched__sum"] or 0
        elif log_type == "content":
            output_dict["total_in_progress"] += log_objects.filter(
                complete=False).count()
            output_dict["content_time_spent"] += log_objects.aggregate(
                Sum("time_spent"))["time_spent__sum"] or 0
        elif log_type == "exercise":
            output_dict["total_struggling"] += log_objects.filter(
                struggling=True).count()
            output_dict["total_in_progress"] += log_objects.filter(
                complete=False, struggling=False).count()

            # Summarize struggling, in progress, and completed
            output_dict["exercise_attempts"] += output_dict[
                "total_struggling"] + output_dict[
                    "total_complete"] + output_dict["total_in_progress"]
            # The below doesn't filter correctly, suspecting either bad
            # AttemptLog generated in generaterealdata or because timestamp
            # isn't correctly updated
            # output_dict["exercise_attempts"] = AttemptLog.objects.filter(user__in=learners,
            #     timestamp__gte=start_date,
            #     timestamp__lte=end_date, **obj_ids).count()
            if log_objects.aggregate(Avg(
                    "streak_progress"))["streak_progress__avg"] is not None:
                output_dict["exercise_mastery"] = round(
                    log_objects.aggregate(
                        Avg("streak_progress"))["streak_progress__avg"])
        output_logs.extend(log_objects)

        object_buffer = LogModel.objects.filter(
            user__in=learners,
            latest_activity_timestamp__gte=start_date,
            latest_activity_timestamp__lte=end_date).values_list(id_field,
                                                                 flat=True)

        if len(object_buffer) > 1:
            all_object_ids.update(object_buffer)
        elif len(object_buffer) == 1:
            all_object_ids.add(object_buffer)
    if len(all_object_ids) > 0:
        output_dict["available_topics"] = map(
            lambda x: {
                "id": x.get("id"),
                "title": x.get("title")
            },
            get_content_parents(ids=list(all_object_ids),
                                language=request.language))
    output_dict["total_not_attempted"] = number_content * len(learners) - (
        output_dict["total_complete"] + output_dict["total_struggling"] +
        output_dict["total_in_progress"])
    # Report total time in hours
    output_dict["content_time_spent"] = round(
        output_dict["content_time_spent"] / 3600.0, 1)
    output_logs.sort(key=lambda x: x.latest_activity_timestamp, reverse=True)

    learner_event_objects = dict([
        (item["id"], item)
        for item in get_topic_nodes(ids=[
            getattr(log, "exercise_id",
                    getattr(log, "video_id", getattr(log, "content_id", "")))
            for log in output_logs[:event_limit]
        ],
                                    language=request.language) or []
    ])

    output_dict["learner_events"] = [{
        "learner":
        log.user.get_name(),
        "complete":
        log.complete,
        "struggling":
        getattr(log, "struggling", None),
        "progress":
        getattr(log, "streak_progress", getattr(log, "progress", None)),
        "content":
        learner_event_objects.get(
            getattr(log, "exercise_id",
                    getattr(log, "video_id", getattr(log, "content_id", ""))),
            {}),
    } for log in output_logs[:event_limit]]
    output_dict["total_time_logged"] = round((UserLogSummary.objects\
        .filter(user__in=learners, start_datetime__gte=start_date, start_datetime__lte=end_date)\
        .aggregate(Sum("total_seconds")).get("total_seconds__sum") or 0)/3600.0, 1)
    return JsonResponse(output_dict)
Exemplo n.º 4
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)

        # Retrieve video, exercise, and quiz logs that appear in this playlist
        user_vid_logs, user_ex_logs = cls.get_user_logs(user)

        exercise_ids = list(
            set([ex_log["exercise_id"] for ex_log in user_ex_logs]))
        video_ids = list(
            set([vid_log["video_id"] for vid_log in user_vid_logs]))

        # Build a list of playlists for which the user has at least one data point
        user_playlists = get_content_parents(ids=exercise_ids + video_ids,
                                             language=language)

        # Store stats for each playlist
        user_progress = list()
        for i, p in enumerate(user_playlists):
            # Playlist entry totals
            pl_video_ids, pl_exercise_ids = cls.get_playlist_entry_ids(p)
            n_pl_videos = float(len(pl_video_ids))
            n_pl_exercises = float(len(pl_exercise_ids))

            # Vid & exercise logs in this playlist
            pl_ex_logs = [
                ex_log for ex_log in user_ex_logs
                if ex_log["exercise_id"] in pl_exercise_ids
            ]
            pl_vid_logs = [
                vid_log for vid_log in user_vid_logs
                if vid_log["video_id"] in pl_video_ids
            ]

            # Compute video stats
            n_vid_complete = len(
                [vid for vid in pl_vid_logs if vid["complete"]])
            n_vid_started = len([
                vid for vid in pl_vid_logs
                if (vid["total_seconds_watched"] > 0) and (not vid["complete"])
            ])
            vid_pct_complete = int(float(n_vid_complete) / n_pl_videos *
                                   100) if n_pl_videos else 0
            vid_pct_started = int(float(n_vid_started) / n_pl_videos *
                                  100) if n_pl_videos else 0
            if vid_pct_complete == 100:
                vid_status = "complete"
            elif n_vid_started > 0:
                vid_status = "inprogress"
            else:
                vid_status = "notstarted"

            # Compute exercise stats
            n_ex_mastered = len([ex for ex in pl_ex_logs if ex["complete"]])
            n_ex_started = len([ex for ex in pl_ex_logs if ex["attempts"] > 0])
            n_ex_incomplete = len([
                ex for ex in pl_ex_logs
                if (ex["attempts"] > 0 and not ex["complete"])
            ])
            n_ex_struggling = len(
                [ex for ex in pl_ex_logs if ex["struggling"]])
            ex_pct_mastered = int(
                float(n_ex_mastered) / (n_pl_exercises or 1) * 100)
            ex_pct_incomplete = int(
                float(n_ex_incomplete) / (n_pl_exercises or 1) * 100)
            ex_pct_struggling = int(
                float(n_ex_struggling) / (n_pl_exercises or 1) * 100)
            if not n_ex_started:
                ex_status = "notstarted"
            elif ex_pct_struggling > 0:
                # note: we want to help students prioritize areas they need to focus on
                # therefore if they are struggling in this exercise group, we highlight it for them
                ex_status = "struggling"
            elif ex_pct_mastered < 99:
                ex_status = "inprogress"
            else:
                ex_status = "complete"

            progress = {
                "title": p.get("title"),
                "id": p.get("id"),
                "tag": p.get("tag"),
                "vid_pct_complete": vid_pct_complete,
                "vid_pct_started": vid_pct_started,
                "vid_status": vid_status,
                "ex_pct_mastered": ex_pct_mastered,
                "ex_pct_incomplete": ex_pct_incomplete,
                "ex_pct_struggling": ex_pct_struggling,
                "ex_status": ex_status,
                "n_pl_videos": n_pl_videos,
                "n_pl_exercises": n_pl_exercises,
            }

            try:
                progress["url"] = reverse("view_playlist",
                                          kwargs={"playlist_id": p.get("id")})
            except NoReverseMatch:
                progress["url"] = reverse("learn") + p.get("path")

            user_progress.append(cls(**progress))

        return user_progress
Exemplo n.º 5
0
 def test_get_content_parents(self):
     """
     The function get_content_parents() should return a empty list when an empty list of ids is passed to it.
     """
     self.assertEqual(get_content_parents(ids=list()), list())
Exemplo n.º 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)

        # Retrieve video, exercise, and quiz logs that appear in this playlist
        user_vid_logs, user_ex_logs = cls.get_user_logs(user)

        exercise_ids = list(set([ex_log["exercise_id"] for ex_log in user_ex_logs]))
        video_ids = list(set([vid_log["video_id"] for vid_log in user_vid_logs]))

        # Build a list of playlists for which the user has at least one data point
        user_playlists = get_content_parents(ids=exercise_ids+video_ids, language=language)

        # Store stats for each playlist
        user_progress = list()
        for i, p in enumerate(user_playlists):
            # Playlist entry totals
            pl_video_ids, pl_exercise_ids = cls.get_playlist_entry_ids(p)
            n_pl_videos = float(len(pl_video_ids))
            n_pl_exercises = float(len(pl_exercise_ids))

            # Vid & exercise logs in this playlist
            pl_ex_logs = [ex_log for ex_log in user_ex_logs if ex_log["exercise_id"] in pl_exercise_ids]
            pl_vid_logs = [vid_log for vid_log in user_vid_logs if vid_log["video_id"] in pl_video_ids]

            # Compute video stats
            n_vid_complete = len([vid for vid in pl_vid_logs if vid["complete"]])
            n_vid_started = len([vid for vid in pl_vid_logs if (vid["total_seconds_watched"] > 0) and (not vid["complete"])])
            vid_pct_complete = int(float(n_vid_complete) / n_pl_videos * 100) if n_pl_videos else 0
            vid_pct_started = int(float(n_vid_started) / n_pl_videos * 100) if n_pl_videos else 0
            if vid_pct_complete == 100:
                vid_status = "complete"
            elif n_vid_started > 0:
                vid_status = "inprogress"
            else:
                vid_status = "notstarted"

            # Compute exercise stats
            n_ex_mastered = len([ex for ex in pl_ex_logs if ex["complete"]])
            n_ex_started = len([ex for ex in pl_ex_logs if ex["attempts"] > 0])
            n_ex_incomplete = len([ex for ex in pl_ex_logs if (ex["attempts"] > 0 and not ex["complete"])])
            n_ex_struggling = len([ex for ex in pl_ex_logs if ex["struggling"]])
            ex_pct_mastered = int(float(n_ex_mastered) / (n_pl_exercises or 1) * 100)
            ex_pct_incomplete = int(float(n_ex_incomplete) / (n_pl_exercises or 1) * 100)
            ex_pct_struggling = int(float(n_ex_struggling) / (n_pl_exercises or 1) * 100)
            if not n_ex_started:
                ex_status = "notstarted"
            elif ex_pct_struggling > 0:
                # note: we want to help students prioritize areas they need to focus on
                # therefore if they are struggling in this exercise group, we highlight it for them
                ex_status = "struggling"
            elif ex_pct_mastered < 99:
                ex_status = "inprogress"
            else:
                ex_status = "complete"

            progress = {
                "title": p.get("title"),
                "id": p.get("id"),
                "tag": p.get("tag"),
                "vid_pct_complete": vid_pct_complete,
                "vid_pct_started": vid_pct_started,
                "vid_status": vid_status,
                "ex_pct_mastered": ex_pct_mastered,
                "ex_pct_incomplete": ex_pct_incomplete,
                "ex_pct_struggling": ex_pct_struggling,
                "ex_status": ex_status,
                "n_pl_videos": n_pl_videos,
                "n_pl_exercises": n_pl_exercises,
            }

            try:
                progress["url"] = reverse("view_playlist", kwargs={"playlist_id": p.get("id")})
            except NoReverseMatch:
                progress["url"] = reverse("learn") + p.get("path")

            user_progress.append(cls(**progress))

        return user_progress