Skip to content
This repository was archived by the owner on Jun 30, 2024. It is now read-only.

Commit dc0820d

Browse files
committed
Merge branch 'fix_practice_feature' of git://github.com/ImanYZ/RunestoneServer into ImanYZ-fix_practice_feature
2 parents bdf5831 + 6744547 commit dc0820d

File tree

5 files changed

+136
-18
lines changed

5 files changed

+136
-18
lines changed

controllers/admin.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -432,18 +432,18 @@ def add_practice_items():
432432
course = db(db.courses.course_name == auth.user.course_name).select().first()
433433
data = json.loads(request.vars.data)
434434

435-
string_data = [x.encode('UTF8') for x in data]
435+
# Was for Python 2.x
436+
# string_data = [x.encode('UTF8') for x in data]
437+
# Is for Python 3.x
438+
string_data = data
436439

437440
now = datetime.datetime.utcnow()
438441
now_local = now - datetime.timedelta(hours=float(session.timezoneoffset))
439442

440-
students = db((db.auth_user.course_name == auth.user.course_name)) \
441-
.select()
442-
chapters = db((db.chapters.course_id == course.base_course)) \
443-
.select()
443+
students = db((db.auth_user.course_name == auth.user.course_name)).select()
444+
chapters = db((db.chapters.course_id == course.base_course)).select()
444445
for chapter in chapters:
445-
subchapters = db((db.sub_chapters.chapter_id == chapter.id)) \
446-
.select()
446+
subchapters = db((db.sub_chapters.chapter_id == chapter.id)).select()
447447
for subchapter in subchapters:
448448
subchapterTaught = db((db.sub_chapter_taught.course_name == auth.user.course_name) &
449449
(db.sub_chapter_taught.chapter_label == chapter.chapter_label) &

controllers/assignments.py

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -836,7 +836,7 @@ def practice():
836836
float(session.timezoneoffset) if 'timezoneoffset' in session else 0)
837837

838838
if message1 != "":
839-
session.flash = message1 + " " + message2
839+
# session.flash = message1 + " " + message2
840840
return redirect(URL('practiceNotStartedYet',
841841
vars=dict(message1=message1,
842842
message2=message2)))
@@ -848,7 +848,7 @@ def practice():
848848
(db.user_topic_practice.user_id == auth.user.id) &
849849
(db.user_topic_practice.chapter_label == db.chapters.chapter_label) &
850850
(db.user_topic_practice.sub_chapter_label == db.sub_chapters.sub_chapter_label) &
851-
(db.chapters.course_id == auth.user.course_name) &
851+
(db.chapters.course_id == course.base_course) &
852852
(db.sub_chapters.chapter_id == db.chapters.id)) \
853853
.select(db.chapters.chapter_name,
854854
db.sub_chapters.sub_chapter_name,
@@ -881,20 +881,31 @@ def practice():
881881
elif f_card["mastery_percent"] >= 25:
882882
f_card["mastery_color"] = "warning"
883883

884-
# If the student has any flashcards to practice and has not practiced enough to get their points for today or they
885-
# have intrinsic motivation to practice beyond what they are expected to do.
886-
if available_flashcards_num > 0 and (practiced_today_count != questions_to_complete_day or
887-
request.vars.willing_to_continue or
888-
spacing == 0):
884+
# If an instructor removes the practice flag from a question in the middle of the semester
885+
# and students are in the middle of practicing it, the following code makes sure the practice tool does not crash.
886+
questions = []
887+
if len(presentable_flashcards) > 0:
889888
# Present the first one.
890889
flashcard = presentable_flashcards[0]
891890
# Get eligible questions.
892891
questions = _get_qualified_questions(course.base_course,
893892
flashcard.chapter_label,
894893
flashcard.sub_chapter_label)
894+
# If the student has any flashcards to practice and has not practiced enough to get their points for today or they
895+
# have intrinsic motivation to practice beyond what they are expected to do.
896+
if (available_flashcards_num > 0 and
897+
len(questions) > 0 and
898+
(practiced_today_count != questions_to_complete_day or
899+
request.vars.willing_to_continue or
900+
spacing == 0)):
895901
# Find index of the last question asked.
896902
question_names = [q.name for q in questions]
897-
qIndex = question_names.index(flashcard.question_name)
903+
904+
try:
905+
qIndex = question_names.index(flashcard.question_name)
906+
except:
907+
qIndex = 0
908+
898909
# present the next one in the list after the last one that was asked
899910
question = questions[(qIndex + 1) % len(questions)]
900911

@@ -935,15 +946,15 @@ def practice():
935946
course_name=auth.user.course_name,
936947
practice_completion_date=now_local.date()
937948
)
949+
practice_completion_count = _get_practice_completion(auth.user.id,
950+
auth.user.course_name,
951+
spacing)
938952
if practice_graded == 1:
939953
# send practice grade via lti, if setup for that
940954
lti_record = _get_lti_record(session.oauth_consumer_key)
941955
practice_grade = _get_student_practice_grade(auth.user.id, auth.user.course_name)
942956
course_settings = _get_course_practice_record(auth.user.course_name)
943957

944-
practice_completion_count = _get_practice_completion(auth.user.id,
945-
auth.user.course_name,
946-
spacing)
947958
if spacing == 1:
948959
total_possible_points = day_points * max_days
949960
points_received = day_points * practice_completion_count

tests/test_course_1/_sources/test_chapter_1/subchapter_b.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Lets add one activity to this Subchapter!
1111
:correct: b
1212
:feedback_a: It usually takes longer to read a program because the structure is as important as the content and must be interpreted in smaller pieces for understanding.
1313
:feedback_b: It usually takes longer to read a program because the structure is as important as the content and must be interpreted in smaller pieces for understanding.
14+
:practice: T
1415

1516
True or False: Reading a program is like reading other kinds of text.
1617

@@ -54,6 +55,7 @@ Lets add one activity to this Subchapter!
5455

5556
.. activecode:: units1
5657
:autograde: unittest
58+
:practice: T
5759

5860
def add(a,b):
5961
return 4

tests/test_server.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from textwrap import dedent
1616
import json
1717
from threading import Thread
18+
import datetime
1819

1920
# Third-party imports
2021
# -------------------
@@ -567,6 +568,103 @@ def test_assignments(test_client, runestone_db_tools, test_user):
567568
assert "Error" in test_client.text
568569

569570

571+
def test_instructor_practice_admin(test_client, runestone_db_tools, test_user):
572+
course_4 = runestone_db_tools.create_course('test_course_1')
573+
test_student_1 = test_user('test_student_1', 'password_1', course_4)
574+
test_student_1.logout()
575+
test_instructor_1 = test_user('test_instructor_1', 'password_1', course_4)
576+
test_instructor_1.make_instructor()
577+
test_instructor_1.login()
578+
db = runestone_db_tools.db
579+
580+
course_start_date = datetime.datetime.strptime(course_4.term_start_date, '%Y-%m-%d').date()
581+
582+
today = datetime.datetime.today()
583+
start_date = course_start_date + datetime.timedelta(days=13)
584+
end_date = datetime.datetime.today().date() + datetime.timedelta(days=30)
585+
max_practice_days = 40
586+
max_practice_questions = 400
587+
day_points = 1
588+
question_points = 0.2
589+
questions_to_complete_day = 5
590+
graded = 0
591+
592+
# Test the practice tool settings for the course.
593+
flashcard_creation_method = 2
594+
test_client.post('admin/practice',
595+
data = {"StartDate": start_date,
596+
"EndDate": end_date,
597+
"graded": graded,
598+
'maxPracticeDays': max_practice_days,
599+
'maxPracticeQuestions': max_practice_questions,
600+
'pointsPerDay': day_points,
601+
'pointsPerQuestion': question_points,
602+
'questionsPerDay': questions_to_complete_day,
603+
'flashcardsCreationType': 2,
604+
'question_points': question_points})
605+
606+
practice_settings_1 = db(
607+
(db.course_practice.auth_user_id == test_instructor_1.user_id) &
608+
(db.course_practice.course_name == course_4.course_name) &
609+
(db.course_practice.start_date == start_date) &
610+
(db.course_practice.end_date == end_date) &
611+
(db.course_practice.flashcard_creation_method == flashcard_creation_method) &
612+
(db.course_practice.graded == graded)
613+
).select().first()
614+
assert practice_settings_1
615+
if practice_settings_1.spacing == 1:
616+
assert practice_settings_1.max_practice_days == max_practice_days
617+
assert practice_settings_1.day_points == day_points
618+
assert practice_settings_1.questions_to_complete_day == questions_to_complete_day
619+
else:
620+
assert practice_settings_1.max_practice_questions == max_practice_questions
621+
assert practice_settings_1.question_points == question_points
622+
623+
# Test instructor adding a subchapter to the practice tool for students.
624+
625+
# I need to call set_tz_offset to set timezoneoffset in the session.
626+
test_client.post('ajax/set_tz_offset',
627+
data = { 'timezoneoffset': 0 })
628+
629+
# The reason I'm manually stringifying the list value is that test_client.post does something strange with compound objects instead of passing them to json.dumps.
630+
test_client.post('admin/add_practice_items',
631+
data = { 'data': '["Test chapter 1/Subchapter B"]' })
632+
633+
634+
practice_settings_1 = db(
635+
(db.user_topic_practice.user_id == test_student_1.user_id) &
636+
(db.user_topic_practice.course_name == course_4.course_name) &
637+
(db.user_topic_practice.chapter_label == "test_chapter_1") &
638+
(db.user_topic_practice.sub_chapter_label == "subchapter_b")
639+
).select().first()
640+
assert practice_settings_1
641+
642+
# Testing whether a student can answer a practice question.
643+
# test_client.logout()
644+
# test_student_1.login()
645+
646+
# ts = datetime.datetime.utcnow()
647+
# ts -= datetime.timedelta(microseconds=ts.microsecond)
648+
649+
# test_client.post('ajax/hsblog',
650+
# data = {'event': 'mChoice',
651+
# 'act': 'answer:1:correct',
652+
# 'answer': 1,
653+
# 'correct': 'T',
654+
# 'div_id': 'subc_b_1',
655+
# 'course': course_4.course_name,
656+
# 'timezoneoffset': 0})
657+
658+
# mchoice_answers_1 = db(
659+
# (db.mchoice_answers.sid == test_student_1.user_id) &
660+
# (db.mchoice_answers.course_name == course_4.course_name) &
661+
# (db.mchoice_answers.correct == "test_chapter_1") &
662+
# (db.mchoice_answers.sub_chapter_label == "subchapter_b")
663+
# ).select().first()
664+
# assert practice_settings_1
665+
# db.mchoice_answers.insert(sid=sid,timestamp=ts, div_id=div_id, answer=answer, correct=correct, course_name=course)
666+
667+
570668
def test_deleteaccount(test_client, runestone_db_tools, test_user):
571669
course_3 = runestone_db_tools.create_course('test_course_3')
572670
the_user = test_user('user_to_delete', 'password_1', course_3)

views/assignments/practice.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@
33
{{block tabcontent}}
44
{{include '_sphinx_static_files.html'}}
55

6+
<script type="text/javascript">
7+
if(! eBookConfig) {
8+
eBookConfig = {};
9+
}
10+
eBookConfig.practice_mode = true;
11+
</script>
12+
613
<!-- Add icon library -->
714
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
815

0 commit comments

Comments
 (0)