Exemple #1
0
def practice():
    if not auth.user:
        session.flash = "Please Login"
        return redirect(URL('default', 'index'))

    if not session.timezoneoffset:
        session.timezoneoffset = 0

    feedback_saved = request.vars.get('feedback_saved', None)
    if feedback_saved is None:
        feedback_saved = ""

    (now,
     now_local,
     message1,
     message2,
     practice_graded,
     spacing,
     interleaving,
     practice_completion_count,
     remaining_days,
     max_days,
     max_questions,
     day_points,
     question_points,
     presentable_flashcards,
     available_flashcards_num,
     practiced_today_count,
     questions_to_complete_day,
     practice_today_left,
     points_received,
     total_possible_points,
     flashcard_creation_method) = _get_practice_data(auth.user,
                                                     float(session.timezoneoffset) if 'timezoneoffset' in session else 0)

    if message1 != "":
        session.flash = message1 + " " + message2
        return redirect(URL('practiceNotStartedYet',
                            vars=dict(message1=message1,
                                      message2=message2)))

    # Since each authenticated user has only one active course, we retrieve the course this way.
    course = db(db.courses.id == auth.user.course_id).select().first()

    all_flashcards = db((db.user_topic_practice.course_name == auth.user.course_name) &
                        (db.user_topic_practice.user_id == auth.user.id) &
                        (db.user_topic_practice.chapter_label == db.chapters.chapter_label) &
                        (db.user_topic_practice.sub_chapter_label == db.sub_chapters.sub_chapter_label) &
                        (db.chapters.course_id == auth.user.course_name) &
                        (db.sub_chapters.chapter_id == db.chapters.id)) \
            .select(db.chapters.chapter_name,
                    db.sub_chapters.sub_chapter_name,
                    db.user_topic_practice.i_interval,
                    db.user_topic_practice.next_eligible_date,
                    db.user_topic_practice.e_factor,
                    db.user_topic_practice.q,
                    db.user_topic_practice.last_completed,
                    orderby=db.user_topic_practice.id)
    for f_card in all_flashcards:
        if interleaving == 1:
            f_card["remaining_days"] = max(0, (f_card.user_topic_practice.next_eligible_date - now_local.date()).days)
            # f_card["mastery_percent"] = int(100 * f_card["remaining_days"] // 55)
            f_card["mastery_percent"] = int(f_card["remaining_days"])
        else:
            # The maximum q is 5.0 and the minimum e_factor that indicates mastery of the topic is 2.5. `5 * 2.5 = 12.5`
            # I learned that when students under the blocking condition answer something wrong multiple times,
            # it becomes too difficult for them to pass it and the system asks them the same question many times
            # (because most subchapters have only one question). To solve this issue, I changed the blocking formula.
            f_card["mastery_percent"] = int(100 * f_card.user_topic_practice.e_factor *
                                            f_card.user_topic_practice.q / 12.5)
            if f_card["mastery_percent"] > 100:
                f_card["mastery_percent"] = 100

        f_card["mastery_color"] = "danger"
        if f_card["mastery_percent"] >= 75:
            f_card["mastery_color"] = "success"
        elif f_card["mastery_percent"] >= 50:
            f_card["mastery_color"] = "info"
        elif f_card["mastery_percent"] >= 25:
            f_card["mastery_color"] = "warning"

    # If the student has any flashcards to practice and has not practiced enough to get their points for today or they
    # have intrinsic motivation to practice beyond what they are expected to do.
    if available_flashcards_num > 0 and (practiced_today_count != questions_to_complete_day or
                                            request.vars.willing_to_continue or
                                            spacing == 0):
        # Present the first one.
        flashcard = presentable_flashcards[0]
        # Get eligible questions.
        questions = _get_qualified_questions(course.base_course,
                                             flashcard.chapter_label,
                                             flashcard.sub_chapter_label)
        # Find index of the last question asked.
        question_names = [q.name for q in questions]
        qIndex = question_names.index(flashcard.question_name)
        # present the next one in the list after the last one that was asked
        question = questions[(qIndex + 1) % len(questions)]

        # This replacement is to render images
        question.htmlsrc = bytes(question.htmlsrc).decode('utf8').replace('src="../_static/',
                                                                          'src="../static/' + course[
                                                                              'course_name'] + '/_static/')
        question.htmlsrc = question.htmlsrc.replace("../_images",
                                                    "/{}/static/{}/_images".format(request.application,
                                                                                   course.course_name))

        autogradable = 1
        # If it is possible to autograde it:
        if ((question.autograde is not None) or
                (question.question_type is not None and question.question_type in
                 ['mchoice', 'parsonsprob', 'fillintheblank', 'clickablearea', 'dragndrop'])):
            autogradable = 2

        questioninfo = [question.htmlsrc, question.name, question.id, autogradable]

        # This is required to check the same question in do_check_answer().
        flashcard.question_name = question.name
        # This is required to only check answers after this timestamp in do_check_answer().
        flashcard.last_presented = now
        flashcard.timezoneoffset = float(session.timezoneoffset) if 'timezoneoffset' in session else 0
        flashcard.update_record()

    else:
        questioninfo = None

        # Add a practice completion record for today, if there isn't one already.
        practice_completion_today = db((db.user_topic_practice_Completion.course_name == auth.user.course_name) &
                                       (db.user_topic_practice_Completion.user_id == auth.user.id) &
                                       (db.user_topic_practice_Completion.practice_completion_date == now_local.date()))
        if practice_completion_today.isempty():
            db.user_topic_practice_Completion.insert(
                user_id=auth.user.id,
                course_name=auth.user.course_name,
                practice_completion_date=now_local.date()
            )
            if practice_graded == 1:
                # send practice grade via lti, if setup for that
                lti_record = _get_lti_record(session.oauth_consumer_key)
                practice_grade = _get_student_practice_grade(auth.user.id, auth.user.course_name)
                course_settings = _get_course_practice_record(auth.user.course_name)

                practice_completion_count = _get_practice_completion(auth.user.id,
                                                                     auth.user.course_name,
                                                                     spacing)
                if spacing == 1:
                    total_possible_points = day_points * max_days
                    points_received = day_points * practice_completion_count
                else:
                    total_possible_points = question_points * max_questions
                    points_received = question_points * practice_completion_count

                if lti_record and \
                        practice_grade and \
                        practice_grade.lis_outcome_url and \
                        practice_grade.lis_result_sourcedid and \
                        course_settings:
                    if spacing == 1:
                        send_lti_grade(assignment_points=max_days,
                                       score=practice_completion_count,
                                       consumer=lti_record.consumer,
                                       secret=lti_record.secret,
                                       outcome_url=practice_grade.lis_outcome_url,
                                       result_sourcedid=practice_grade.lis_result_sourcedid)
                    else:
                        send_lti_grade(assignment_points=max_questions,
                                       score=practice_completion_count,
                                       consumer=lti_record.consumer,
                                       secret=lti_record.secret,
                                       outcome_url=practice_grade.lis_outcome_url,
                                       result_sourcedid=practice_grade.lis_result_sourcedid)

    return dict(course=course,
                q=questioninfo, all_flashcards=all_flashcards,
                flashcard_count=available_flashcards_num,
                # The number of days the student has completed their practice.
                practice_completion_count=practice_completion_count,
                remaining_days=remaining_days, max_questions=max_questions, max_days=max_days,
                # The number of times remaining to practice today to get the completion point.
                practice_today_left=practice_today_left,
                # The number of times this user has submitted their practice from the beginning of today (12:00 am)
                # till now.
                practiced_today_count=practiced_today_count,
                total_today_count=min(practice_today_left + practiced_today_count, questions_to_complete_day),
                questions_to_complete_day=questions_to_complete_day,
                points_received=points_received,
                total_possible_points=total_possible_points,
                practice_graded=practice_graded,
                spacing=spacing, interleaving=interleaving,
                flashcard_creation_method=flashcard_creation_method,
                feedback_saved=feedback_saved)
def practice():
    if not session.timezoneoffset:
        session.timezoneoffset = 0

    feedback_saved = request.vars.get("feedback_saved", None)
    if feedback_saved is None:
        feedback_saved = ""

    (
        now,
        now_local,
        message1,
        message2,
        practice_graded,
        spacing,
        interleaving,
        practice_completion_count,
        remaining_days,
        max_days,
        max_questions,
        day_points,
        question_points,
        presentable_flashcards,
        available_flashcards_num,
        practiced_today_count,
        questions_to_complete_day,
        practice_today_left,
        points_received,
        total_possible_points,
        flashcard_creation_method,
    ) = _get_practice_data(
        auth.user,
        float(session.timezoneoffset) if "timezoneoffset" in session else 0,
        db,
    )

    try:
        db.useinfo.insert(
            sid=auth.user.username,
            act=message1 or "beginning practice",
            div_id="/runestone/assignments/practice",
            event="practice",
            timestamp=datetime.datetime.utcnow(),
            course_id=auth.user.course_name,
        )
    except Exception as e:
        logger.error(f"failed to insert log record for practice: {e}")

    if message1 != "":
        # session.flash = message1 + " " + message2
        return redirect(
            URL("practiceNotStartedYet",
                vars=dict(message1=message1, message2=message2)))

    # Since each authenticated user has only one active course, we retrieve the course this way.
    course = db(db.courses.id == auth.user.course_id).select().first()

    all_flashcards = db(
        (db.user_topic_practice.course_name == auth.user.course_name)
        & (db.user_topic_practice.user_id == auth.user.id)
        & (db.user_topic_practice.chapter_label == db.chapters.chapter_label)
        & (db.user_topic_practice.sub_chapter_label ==
           db.sub_chapters.sub_chapter_label)
        & (db.chapters.course_id == course.base_course)
        & (db.sub_chapters.chapter_id == db.chapters.id)).select(
            db.chapters.chapter_name,
            db.sub_chapters.sub_chapter_name,
            db.user_topic_practice.i_interval,
            db.user_topic_practice.next_eligible_date,
            db.user_topic_practice.e_factor,
            db.user_topic_practice.q,
            db.user_topic_practice.last_completed,
            orderby=db.user_topic_practice.id,
        )
    for f_card in all_flashcards:
        if interleaving == 1:
            f_card["remaining_days"] = max(
                0,
                (f_card.user_topic_practice.next_eligible_date -
                 now_local.date()).days,
            )
            # f_card["mastery_percent"] = int(100 * f_card["remaining_days"] // 55)
            f_card["mastery_percent"] = int(f_card["remaining_days"])
        else:
            # The maximum q is 5.0 and the minimum e_factor that indicates mastery of the topic is 2.5. `5 * 2.5 = 12.5`
            # I learned that when students under the blocking condition answer something wrong multiple times,
            # it becomes too difficult for them to pass it and the system asks them the same question many times
            # (because most subchapters have only one question). To solve this issue, I changed the blocking formula.
            f_card["mastery_percent"] = int(
                100 * f_card.user_topic_practice.e_factor *
                f_card.user_topic_practice.q / 12.5)
            if f_card["mastery_percent"] > 100:
                f_card["mastery_percent"] = 100

        f_card["mastery_color"] = "danger"
        if f_card["mastery_percent"] >= 75:
            f_card["mastery_color"] = "success"
        elif f_card["mastery_percent"] >= 50:
            f_card["mastery_color"] = "info"
        elif f_card["mastery_percent"] >= 25:
            f_card["mastery_color"] = "warning"

    # If an instructor removes the practice flag from a question in the middle of the semester
    # and students are in the middle of practicing it, the following code makes sure the practice tool does not crash.
    questions = []
    if len(presentable_flashcards) > 0:
        # Present the first one.
        flashcard = presentable_flashcards[0]
        # Get eligible questions.
        questions = _get_qualified_questions(course.base_course,
                                             flashcard.chapter_label,
                                             flashcard.sub_chapter_label, db)
    # If the student has any flashcards to practice and has not practiced enough to get their points for today or they
    # have intrinsic motivation to practice beyond what they are expected to do.
    if (available_flashcards_num > 0 and len(questions) > 0
            and (practiced_today_count != questions_to_complete_day
                 or request.vars.willing_to_continue or spacing == 0)):
        # Find index of the last question asked.
        question_names = [q.name for q in questions]

        try:
            qIndex = question_names.index(flashcard.question_name)
        except Exception:
            qIndex = 0

        # present the next one in the list after the last one that was asked
        question = questions[(qIndex + 1) % len(questions)]

        # This replacement is to render images
        question.htmlsrc = question.htmlsrc.replace(
            'src="../_static/', 'src="' + get_course_url("_static/"))
        question.htmlsrc = question.htmlsrc.replace("../_images/",
                                                    get_course_url("_images/"))

        autogradable = 1
        # If it is possible to autograde it:
        if (question.autograde
                is not None) or (question.question_type is not None
                                 and question.question_type in [
                                     "mchoice",
                                     "parsonsprob",
                                     "fillintheblank",
                                     "clickablearea",
                                     "dragndrop",
                                 ]):
            autogradable = 2

        questioninfo = [
            question.htmlsrc, question.name, question.id, autogradable
        ]

        # This is required to check the same question in do_check_answer().
        flashcard.question_name = question.name
        # This is required to only check answers after this timestamp in do_check_answer().
        flashcard.last_presented = now
        flashcard.timezoneoffset = (float(session.timezoneoffset)
                                    if "timezoneoffset" in session else 0)
        flashcard.update_record()

    else:
        questioninfo = None

        # Add a practice completion record for today, if there isn't one already.
        practice_completion_today = db(
            (db.user_topic_practice_completion.course_name ==
             auth.user.course_name)
            & (db.user_topic_practice_completion.user_id == auth.user.id)
            & (db.user_topic_practice_completion.practice_completion_date ==
               now_local.date()))
        if practice_completion_today.isempty():
            db.user_topic_practice_completion.insert(
                user_id=auth.user.id,
                course_name=auth.user.course_name,
                practice_completion_date=now_local.date(),
            )
            practice_completion_count = _get_practice_completion(
                auth.user.id, auth.user.course_name, spacing, db)
            if practice_graded == 1:
                # send practice grade via lti, if setup for that
                lti_record = _get_lti_record(session.oauth_consumer_key)
                practice_grade = _get_student_practice_grade(
                    auth.user.id, auth.user.course_name)
                course_settings = _get_course_practice_record(
                    auth.user.course_name)

                if spacing == 1:
                    total_possible_points = day_points * max_days
                    points_received = day_points * practice_completion_count
                else:
                    total_possible_points = question_points * max_questions
                    points_received = question_points * practice_completion_count

                if (lti_record and practice_grade
                        and practice_grade.lis_outcome_url
                        and practice_grade.lis_result_sourcedid
                        and course_settings):
                    if spacing == 1:
                        send_lti_grade(
                            assignment_points=max_days,
                            score=practice_completion_count,
                            consumer=lti_record.consumer,
                            secret=lti_record.secret,
                            outcome_url=practice_grade.lis_outcome_url,
                            result_sourcedid=practice_grade.
                            lis_result_sourcedid,
                        )
                    else:
                        send_lti_grade(
                            assignment_points=max_questions,
                            score=practice_completion_count,
                            consumer=lti_record.consumer,
                            secret=lti_record.secret,
                            outcome_url=practice_grade.lis_outcome_url,
                            result_sourcedid=practice_grade.
                            lis_result_sourcedid,
                        )

    set_latex_preamble(course.base_course)

    return dict(
        course=course,
        q=questioninfo,
        all_flashcards=all_flashcards,
        flashcard_count=available_flashcards_num,
        # The number of days the student has completed their practice.
        practice_completion_count=practice_completion_count,
        remaining_days=remaining_days,
        max_questions=max_questions,
        max_days=max_days,
        # The number of times remaining to practice today to get the completion point.
        practice_today_left=practice_today_left,
        # The number of times this user has submitted their practice from the beginning of today (12:00 am)
        # till now.
        practiced_today_count=practiced_today_count,
        total_today_count=min(practice_today_left + practiced_today_count,
                              questions_to_complete_day),
        questions_to_complete_day=questions_to_complete_day,
        points_received=points_received,
        total_possible_points=total_possible_points,
        practice_graded=practice_graded,
        spacing=spacing,
        interleaving=interleaving,
        flashcard_creation_method=flashcard_creation_method,
        feedback_saved=feedback_saved,
    )
def practice():
    if not auth.user:
        session.flash = "Please Login"
        return redirect(URL('default', 'index'))

    if not session.timezoneoffset:
        session.timezoneoffset = 0

    feedback_saved = request.vars.get('feedback_saved', None)
    if feedback_saved is None:
        feedback_saved = ""

    (now,
     now_local,
     message1,
     message2,
     practice_graded,
     spacing,
     interleaving,
     practice_completion_count,
     remaining_days,
     max_days,
     max_questions,
     day_points,
     question_points,
     presentable_flashcards,
     available_flashcards_num,
     practiced_today_count,
     questions_to_complete_day,
     practice_today_left,
     points_received,
     total_possible_points,
     flashcard_creation_method) = _get_practice_data(auth.user,
                                                     float(session.timezoneoffset) if 'timezoneoffset' in session else 0)

    if message1 != "":
        session.flash = message1 + " " + message2
        return redirect(URL('practiceNotStartedYet',
                            vars=dict(message1=message1,
                                      message2=message2)))

    # Since each authenticated user has only one active course, we retrieve the course this way.
    course = db(db.courses.id == auth.user.course_id).select().first()

    all_flashcards = db((db.user_topic_practice.course_name == auth.user.course_name) &
                        (db.user_topic_practice.user_id == auth.user.id) &
                        (db.user_topic_practice.chapter_label == db.chapters.chapter_label) &
                        (db.user_topic_practice.sub_chapter_label == db.sub_chapters.sub_chapter_label) &
                        (db.chapters.course_id == auth.user.course_name) &
                        (db.sub_chapters.chapter_id == db.chapters.id)) \
            .select(db.chapters.chapter_name,
                    db.sub_chapters.sub_chapter_name,
                    db.user_topic_practice.i_interval,
                    db.user_topic_practice.next_eligible_date,
                    db.user_topic_practice.e_factor,
                    db.user_topic_practice.q,
                    db.user_topic_practice.last_completed,
                    orderby=db.user_topic_practice.id)
    for f_card in all_flashcards:
        if interleaving == 1:
            f_card["remaining_days"] = max(0, (f_card.user_topic_practice.next_eligible_date - now_local.date()).days)
            # f_card["mastery_percent"] = int(100 * f_card["remaining_days"] // 55)
            f_card["mastery_percent"] = int(f_card["remaining_days"])
        else:
            # The maximum q is 5.0 and the minimum e_factor that indicates mastery of the topic is 2.5. `5 * 2.5 = 12.5`
            # I learned that when students under the blocking condition answer something wrong multiple times,
            # it becomes too difficult for them to pass it and the system asks them the same question many times
            # (because most subchapters have only one question). To solve this issue, I changed the blocking formula.
            f_card["mastery_percent"] = int(100 * f_card.user_topic_practice.e_factor *
                                            f_card.user_topic_practice.q / 12.5)
            if f_card["mastery_percent"] > 100:
                f_card["mastery_percent"] = 100

        f_card["mastery_color"] = "danger"
        if f_card["mastery_percent"] >= 75:
            f_card["mastery_color"] = "success"
        elif f_card["mastery_percent"] >= 50:
            f_card["mastery_color"] = "info"
        elif f_card["mastery_percent"] >= 25:
            f_card["mastery_color"] = "warning"

    # If the student has any flashcards to practice and has not practiced enough to get their points for today or they
    # have intrinsic motivation to practice beyond what they are expected to do.
    if available_flashcards_num > 0 and (practiced_today_count != questions_to_complete_day or
                                            request.vars.willing_to_continue or
                                            spacing == 0):
        # Present the first one.
        flashcard = presentable_flashcards[0]
        # Get eligible questions.
        questions = _get_qualified_questions(course.base_course,
                                             flashcard.chapter_label,
                                             flashcard.sub_chapter_label)
        # Find index of the last question asked.
        question_names = [q.name for q in questions]
        qIndex = question_names.index(flashcard.question_name)
        # present the next one in the list after the last one that was asked
        question = questions[(qIndex + 1) % len(questions)]

        # This replacement is to render images
        question.htmlsrc = bytes(question.htmlsrc).decode('utf8').replace('src="../_static/',
                                                                          'src="../static/' + course[
                                                                              'course_name'] + '/_static/')
        question.htmlsrc = question.htmlsrc.replace("../_images",
                                                    "/{}/static/{}/_images".format(request.application,
                                                                                   course.course_name))

        autogradable = 1
        # If it is possible to autograde it:
        if ((question.autograde is not None) or
                (question.question_type is not None and question.question_type in
                 ['mchoice', 'parsonsprob', 'fillintheblank', 'clickablearea', 'dragndrop'])):
            autogradable = 2

        questioninfo = [question.htmlsrc, question.name, question.id, autogradable]

        # This is required to check the same question in do_check_answer().
        flashcard.question_name = question.name
        # This is required to only check answers after this timestamp in do_check_answer().
        flashcard.last_presented = now
        flashcard.timezoneoffset = float(session.timezoneoffset) if 'timezoneoffset' in session else 0
        flashcard.update_record()

    else:
        questioninfo = None

        # Add a practice completion record for today, if there isn't one already.
        practice_completion_today = db((db.user_topic_practice_Completion.course_name == auth.user.course_name) &
                                       (db.user_topic_practice_Completion.user_id == auth.user.id) &
                                       (db.user_topic_practice_Completion.practice_completion_date == now_local.date()))
        if practice_completion_today.isempty():
            db.user_topic_practice_Completion.insert(
                user_id=auth.user.id,
                course_name=auth.user.course_name,
                practice_completion_date=now_local.date()
            )
            if practice_graded == 1:
                # send practice grade via lti, if setup for that
                lti_record = _get_lti_record(session.oauth_consumer_key)
                practice_grade = _get_student_practice_grade(auth.user.id, auth.user.course_name)
                course_settings = _get_course_practice_record(auth.user.course_name)

                practice_completion_count = _get_practice_completion(auth.user.id,
                                                                     auth.user.course_name,
                                                                     spacing)
                if spacing == 1:
                    total_possible_points = day_points * max_days
                    points_received = day_points * practice_completion_count
                else:
                    total_possible_points = question_points * max_questions
                    points_received = question_points * practice_completion_count

                if lti_record and \
                        practice_grade and \
                        practice_grade.lis_outcome_url and \
                        practice_grade.lis_result_sourcedid and \
                        course_settings:
                    if spacing == 1:
                        send_lti_grade(assignment_points=max_days,
                                       score=practice_completion_count,
                                       consumer=lti_record.consumer,
                                       secret=lti_record.secret,
                                       outcome_url=practice_grade.lis_outcome_url,
                                       result_sourcedid=practice_grade.lis_result_sourcedid)
                    else:
                        send_lti_grade(assignment_points=max_questions,
                                       score=practice_completion_count,
                                       consumer=lti_record.consumer,
                                       secret=lti_record.secret,
                                       outcome_url=practice_grade.lis_outcome_url,
                                       result_sourcedid=practice_grade.lis_result_sourcedid)

    return dict(course=course, course_name=auth.user.course_name,
                course_id=auth.user.course_name,
                q=questioninfo, all_flashcards=all_flashcards,
                flashcard_count=available_flashcards_num,
                # The number of days the student has completed their practice.
                practice_completion_count=practice_completion_count,
                remaining_days=remaining_days, max_questions=max_questions, max_days=max_days,
                # The number of times remaining to practice today to get the completion point.
                practice_today_left=practice_today_left,
                # The number of times this user has submitted their practice from the beginning of today (12:00 am)
                # till now.
                practiced_today_count=practiced_today_count,
                total_today_count=min(practice_today_left + practiced_today_count, questions_to_complete_day),
                questions_to_complete_day=questions_to_complete_day,
                points_received=points_received,
                total_possible_points=total_possible_points,
                practice_graded=practice_graded,
                spacing=spacing, interleaving=interleaving,
                flashcard_creation_method=flashcard_creation_method,
                feedback_saved=feedback_saved)