-
Notifications
You must be signed in to change notification settings - Fork 1
/
application.py
1374 lines (1226 loc) · 59.2 KB
/
application.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import os, sys, sqlite3, datetime, json, subprocess, pipes, uuid, shutil, cgi, ast, sets, dateutil.parser
from collections import defaultdict
from flask import render_template, redirect, Markup, jsonify, url_for, request, send_file, Response
from sqlalchemy import not_, and_
from sqlalchemy.sql import func
from flask.ext.security import Security, login_required, roles_required, current_user
from flask.ext.security.signals import user_registered
from flask.ext.login import logout_user
from flask_security.forms import RegisterForm, TextField, Required
import utils
import ast_utils
import random
from types import NoneType
from application_config import app, db
from common_models import roles_users, Role, User, user_datastore
class Assignment(db.Model):
id = db.Column(db.Integer(), primary_key = True)
name = db.Column(db.String(100), index = True, unique = True, nullable = False)
semester = db.Column(db.String(100), nullable = False)
href = db.Column(db.Text(), nullable = False)
description = db.Column(db.Text(), nullable = False)
deadline = db.Column(db.DateTime(), nullable = False)
points = db.Column(db.Integer(), nullable = False)
grades = db.relationship('Grade', lazy='dynamic', backref='assignment')
submissions = db.relationship('Submission', lazy='dynamic', backref='assignment')
def to_dict(self):
return {
'name': self.name,
'description': self.description,
'deadline': str(self.deadline),
'points': self.points
}
class Grade(db.Model):
id = db.Column(db.Integer(), primary_key = True)
score = db.Column(db.Integer(), nullable = False)
submitted = db.Column(db.DateTime(), nullable = False)
user_id = db.Column(db.Integer(), db.ForeignKey('user.id'))
assignment_id = db.Column(db.Integer(), db.ForeignKey('assignment.id'))
class Program(db.Model):
id = db.Column(db.Integer(), primary_key = True)
title = db.Column(db.Text(), nullable = False, default = 'Untitled Program')
code = db.Column(db.Text(), nullable = False, default = '')
last_modified = db.Column(db.DateTime(), nullable = False, default = datetime.datetime.now)
user_id = db.Column(db.Integer(), db.ForeignKey('user.id'))
code_revisions = db.relationship('CodeRevision', lazy='dynamic', backref='program')
class LabProgram(db.Model):
id = db.Column(db.Integer(), primary_key = True)
section = db.Column(db.Integer(), nullable = False)
lab_id = db.Column(db.Integer(), nullable = False)
user_id = db.Column(db.Integer(), db.ForeignKey('user.id'))
program_id = db.Column(db.Integer(), db.ForeignKey('program.id'))
class CodeRevision(db.Model):
id = db.Column(db.Integer(), primary_key = True)
title = db.Column(db.Text(), nullable = False)
diff = db.Column(db.Text(), nullable = False)
time = db.Column(db.DateTime(), nullable = False)
user_id = db.Column(db.Integer(), db.ForeignKey('user.id'))
program_id = db.Column(db.Integer(), db.ForeignKey('program.id'))
class CodeRun(db.Model):
id = db.Column(db.Integer(), primary_key = True)
title = db.Column(db.Text(), nullable = False)
code = db.Column(db.Text(), nullable = False)
output = db.Column(db.Text())
error = db.Column(db.Text())
time = db.Column(db.DateTime(), nullable = False)
user_id = db.Column(db.Integer(), db.ForeignKey('user.id'))
program_id = db.Column(db.Integer(), db.ForeignKey('program.id'))
class Submission(db.Model):
id = db.Column(db.Integer(), primary_key = True)
title = db.Column(db.Text(), nullable = False)
code = db.Column(db.Text(), nullable = False)
submit_time = db.Column(db.DateTime(), nullable = False, default = datetime.datetime.now)
assignment_id = db.Column(db.Integer(), db.ForeignKey('assignment.id'))
user_id = db.Column(db.Integer(), db.ForeignKey('user.id'))
class Lesson(db.Model):
id = db.Column(db.Integer(), primary_key = True)
name = db.Column(db.String(30), nullable=False)
link = db.Column(db.String(30), nullable=False, index = True)
sublessons = db.relationship('Sublesson', lazy='dynamic', backref='lesson')
def to_dict(self):
return {
'name': self.name,
'link': self.link,
'sublessons': map(lambda x: x.to_dict(), self.sublessons)
}
class Sublesson(db.Model):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(30), nullable=False)
link = db.Column(db.String(30), nullable=False)
lesson_id = db.Column(db.Integer(), db.ForeignKey('lesson.id'))
def to_dict(self):
return {
'name': self.name,
'link': self.link
}
class Week(db.Model):
id = db.Column(db.Integer(), primary_key=True)
lesson = db.Column(db.Integer(), db.ForeignKey('lesson.id'))
assignment = db.Column(db.Integer(), db.ForeignKey('assignment.id'))
def to_dict(self):
return {
'lesson': self.lesson,
'assignment': self.assignment.to_dict()
}
class ExtendedRegisterForm(RegisterForm):
firstname = TextField('First Name', [Required()])
lastname = TextField('Last Name', [Required()])
class Quiz(db.Model):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(30), nullable=False)
week = db.Column(db.Integer(), nullable=False)
deadline = db.Column(db.DateTime(), nullable=False)
assignment_id = db.Column(db.Integer(), db.ForeignKey('assignment.id'))
questions = db.relationship('QuizQuestion', lazy='dynamic', backref='quiz')
def to_dict(self):
return {
'id': self.id,
'name': self.name,
'questions': map(lambda x: x.to_dict(), self.questions)
}
class QuizQuestion(db.Model):
id = db.Column(db.Integer(), primary_key=True)
question = db.Column(db.String(1000), nullable=False)
answer_choices = db.Column(db.String(7000), nullable=False)
solution = db.Column(db.String(100), nullable=False)
quiz_id = db.Column(db.Integer(), db.ForeignKey('quiz.id'))
def to_dict(self):
return {'id': self.id, 'question': self.question, 'answer_choices': self.answer_choices, 'solution': self.solution, 'quiz_id': self.quiz_id}
class QuizResponse(db.Model):
id = db.Column(db.Integer(), primary_key=True)
submit_time = db.Column(db.DateTime(), nullable=False)
quiz_id = db.Column(db.Integer(), db.ForeignKey('quiz.id'), nullable=False)
question_id = db.Column(db.Integer(), db.ForeignKey('quiz_question.id'), nullable=False)
user_id = db.Column(db.Integer(), db.ForeignKey('user.id'), nullable=False)
user_answer = db.Column(db.String(100), nullable=False)
templates_concepts = db.Table('templates_concepts',
db.Column('practice_problem_template_id', db.Integer(), db.ForeignKey('practice_problem_template.id')),
db.Column('practice_problem_concept_id', db.Integer(), db.ForeignKey('practice_problem_concept.id')))
class Homework(db.Model):
id = db.Column(db.Integer(), primary_key=True)
week = db.Column(db.Integer(), nullable=False)
deadline = db.Column(db.DateTime(), nullable=False)
assignment_id = db.Column(db.Integer(), db.ForeignKey('assignment.id'))
class HomeworkProblem(db.Model):
id = db.Column(db.Integer(), primary_key=True)
deadline = db.Column(db.DateTime(), nullable=False)
homework_id = db.Column(db.Integer(), db.ForeignKey('homework.id'))
template_id = db.Column(db.Integer(), db.ForeignKey('practice_problem_template.id'))
class Language(db.Model):
id = db.Column(db.Integer(), primary_key=True)
language = db.Column(db.Text(), nullable=False)
#TODO pull data from table for multiple languages
language_map = {'python':1, 1:'python', 'javascript':2, 2:'javascript'}
class PracticeProblemTemplate(db.Model):
id = db.Column(db.Integer(), primary_key=True)
problem_name = db.Column(db.Text(), nullable=False)
prompt = db.Column(db.Text(), nullable=False)
solution = db.Column(db.Text(), nullable=False)
test = db.Column(db.Text(), nullable=False)
hint = db.Column(db.Text(), nullable=False)
gen_template_vars = db.Column(db.Text(), nullable=False)
concepts = db.relationship('PracticeProblemConcept', secondary=templates_concepts,
backref=db.backref('practice_problem_template', lazy='dynamic'), lazy='dynamic')
is_current = db.Column(db.Boolean(), nullable=False)
is_homework = db.Column(db.Boolean(), nullable=False)
language_id = db.Column(db.Integer(), db.ForeignKey('language.id'), nullable=False)
def to_dict(self):
return {
'id': self.id,
'problem_name': self.problem_name,
'prompt': self.prompt,
'solution': self.solution,
'test': self.test,
'hint': self.hint,
'gen_template_vars': self.gen_template_vars,
'concepts': self.concepts,
'is_current': self.is_current,
'language_id': self.language_id
}
submissions_concepts = db.Table('submissions_concepts',
db.Column('practice_problem_submission_id', db.Integer(), db.ForeignKey('practice_problem_submission.id')),
db.Column('practice_problem_concept_id', db.Integer(), db.ForeignKey('practice_problem_concept.id')))
class PracticeProblemSubmission(db.Model):
id = db.Column(db.Integer(), primary_key=True)
code = db.Column(db.Text(), nullable=False)
result_test = db.Column(db.Text(), nullable=False)
result_test_error = db.Column(db.Boolean(), nullable=False)
result_no_test = db.Column(db.Text(), nullable=False)
result_no_test_error = db.Column(db.Boolean(), nullable=False)
got_hint = db.Column(db.Boolean(), nullable=False)
gave_up = db.Column(db.Boolean(), nullable=False)
correct = db.Column(db.Boolean(), nullable=False)
started = db.Column(db.DateTime(), nullable=False)
submitted = db.Column(db.DateTime(), nullable=False)
template_vars = db.Column(db.Text(), nullable=False)
problem_id = db.Column(db.Integer(), db.ForeignKey('practice_problem_template.id'), nullable=False)
user_id = db.Column(db.Integer(), db.ForeignKey('user.id'), nullable=False)
language_id = db.Column(db.Integer(), db.ForeignKey('language.id'), nullable=False)
concepts = db.relationship('PracticeProblemConcept', secondary=submissions_concepts,
backref=db.backref('practice_problem_submission', lazy='dynamic'), lazy='dynamic')
def to_dict(self):
return {
'id': self.id,
'code': self.code,
'result_test': self.result_test,
'result_test_error': self.result_test_error,
'result_no_test': self.result_no_test,
'result_no_test_error': self.result_no_test_error,
'got_hint': self.got_hint,
'gave_up': self.gave_up,
'correct': self.correct,
'started': self.started,
'submitted': self.submitted,
'template_vars': self.template_vars,
'problem_id': self.problem_id,
'user_id': self.user_id,
'language_id': self.language_id,
'concepts': self.concepts
}
class PracticeProblemConcept(db.Model):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.Text(), nullable=False)
display_name = db.Column(db.Text())
explanation = db.Column(db.Text())
language_id = db.Column(db.Integer(), db.ForeignKey('language.id'), nullable=False)
security = Security(app, user_datastore, register_form=ExtendedRegisterForm)
@user_registered.connect_via(app)
def user_registered_sighandler(app, user, confirm_token):
default_role = user_datastore.find_role("user")
user_datastore.add_role_to_user(user, default_role)
db.session.commit()
################################################################################
# Registration Routes
################################################################################
@app.route('/register', methods=['GET'])
def register():
return render_template('register.html')
################################################################################
# Settings Routes
################################################################################
@app.route('/settings', methods=['GET'])
def register():
return render_template('settings.html')
################################################################################
# Student Routes
################################################################################
@app.route('/')
def index():
if not current_user.is_authenticated():
return render_template('index.html')
else:
return redirect('/about/')
@app.route('/about/')
def about():
# TODO add new schedule
#weeks = map(lambda x:x.__dict__, Week.query.all())
weeks = []
# not sure if this is the best way
for week in weeks:
week['assignment'] = Assignment.query.get(week['assignment'])
week['lesson'] = Lesson.query.get(week['lesson'])
quiz = Quiz.query.filter(Quiz.week==week['id'])
if len(quiz.all()) == 1:
week['quiz'] = quiz.first()
return render_template('about.html', weeks=weeks)
@app.route('/about/fa13/')
def about_fa13():
return render_template('about_fa13.html')
@app.route('/lessons/')
def lesson_home():
context = {}
lessons = map(lambda x: x.__dict__, Lesson.query.all())
context['lessons'] = lessons
context['lesson_name'] = 'Lessons'
return render_template('lesson_home.html', context=context)
@app.route('/lessons/<path:lesson_path>/')
def lesson(lesson_path):
file_path = os.path.join('gen', lesson_path)
if '.pdf' in file_path:
if os.path.exists(os.path.join('templates', file_path)):
return send_file(os.path.join('templates', file_path), mimetype='application/pdf')
else:
return redirect('/')
if '.html' in file_path:
if os.path.exists(os.path.join('templates', file_path)):
return render_template(file_path)
else:
lesson = Lesson.query.filter(Lesson.link==lesson_path).first()
context = {}
sublessons = map(lambda x: x.__dict__, lesson.sublessons)
for sublesson in sublessons:
sublesson['sublesson'] = []
context['lessons'] = sublessons
context['lesson_name'] = lesson.name
return render_template('lesson_home.html', context=context)
return redirect('/')
#TODO make better
@app.route('/quiz/toaster/')
def quiz_toaster():
return quiz(1)
@app.route('/quiz/slingshot/')
def quiz_slingshot():
return quiz(2)
@app.route('/quiz/alpaca/')
def quiz_alpaca():
return quiz(3)
@app.route('/quiz/freedom/')
def quiz_freedom():
return quiz(4)
@app.route('/quiz/banana/')
def quiz_banana():
return quiz(5)
@app.route('/quiz/toaster/submit/', methods=['POST'])
@login_required
def submit_quiz_toaster():
return submit_quiz(1)
@app.route('/quiz/slingshot/submit/', methods=['POST'])
@login_required
def submit_quiz_slingshot():
return submit_quiz(2)
@app.route('/quiz/alpaca/submit/', methods=['POST'])
@login_required
def submit_quiz_alpaca():
return submit_quiz(3)
@app.route('/quiz/freedom/submit/', methods=['POST'])
@login_required
def submit_quiz_freedom():
return submit_quiz(4)
@app.route('/quiz/banana/submit/', methods=['POST'])
@login_required
def submit_quiz_banana():
return submit_quiz(5)
@app.route('/quiz/<int:quiz_week>/')
def quiz(quiz_week):
if not current_user.is_authenticated():
return render_template('message.html', message='You need to log in first')
quizzes = Quiz.query.filter_by(week=quiz_week).all()
if len(quizzes) < 1:
return redirect('/')
first_quiz = quizzes[0]
questions = map(lambda x: x.__dict__, first_quiz.questions)
for question in questions:
question['answer_choices'] = json.loads(question['answer_choices'])
first_quiz = first_quiz.__dict__
first_quiz['questions'] = questions
quiz_responses = QuizResponse.query.filter_by(quiz_id=first_quiz['id'], user_id=current_user.id).all()
show_solutions = False
if len(quiz_responses) > 0:
show_solutions = True
first_quiz['score'] = len(first_quiz['questions'])
first_quiz['total'] = len(first_quiz['questions'])
responses = {}
for quiz_response in quiz_responses:
responses[quiz_response.question_id] = quiz_response.user_answer
for question in questions:
for answer_choice in question['answer_choices']:
if answer_choice['id'] == question['solution']:
answer_choice['correct'] = True
elif answer_choice['id'] == responses[question['id']]:
answer_choice['incorrect'] = True
first_quiz['score'] -= 1
return render_template('quiz.html', quiz=first_quiz, show_solutions=show_solutions)
@app.route('/quiz/<int:quiz_week>/submit/', methods=['POST'])
@login_required
def submit_quiz(quiz_week):
quiz = Quiz.query.filter_by(week=quiz_week).first()
questions = {}
for question in map(lambda x: x.__dict__, quiz.questions):
questions[question['id']] = question
if quiz == None:
return 'Error: No quiz found with given week'
answer_choices = request.form.getlist('selected[]')
time_now = datetime.datetime.now()
total = len(answer_choices)
score = 0
for answer_choice in answer_choices:
question_id, answer = answer_choice.strip().split(',')
question_id = int(question_id)
if answer == questions[question_id]['solution']:
score += 1
qr = QuizResponse(quiz_id=int(quiz.id), question_id=question_id, user_id=current_user.id, user_answer=answer, submit_time=time_now)
db.session.add(qr)
time_now = datetime.datetime.now()
assignments = Assignment.query.filter_by(href='/quiz/' + str(quiz_week) + '/', semester='sp14').all()
if len(assignments) == 0:
return 'Error: No assignment found for quiz'
else:
assignment = assignments[0]
grade = Grade(score=score, submitted=time_now, user_id=current_user.id, assignment_id=assignment.id)
db.session.add(grade)
db.session.commit()
return 'Submitted'
@app.route('/labs/<int:lab_id>/')
def lab(lab_id):
section = 0
program = None
if current_user.is_authenticated():
lab_program = LabProgram.query.filter_by(lab_id=lab_id, user_id=current_user.id).first()
if lab_program != None:
section = lab_program.section
program = Program.query.filter_by(id=lab_program.program_id, user_id=current_user.id).first()
if program != None:
return render_template('lab.html', lab_id=lab_id, section=section, program=program)
return render_template('lab.html', lab_id=lab_id, section=section)
def get_user_progress(user_id, language_id):
all_subs = PracticeProblemSubmission.query.filter_by(user_id=user_id, language_id=language_id).all()
attempted_probs = {}
for sub in all_subs:
# Id submissions by their id as well as the time the student started working
# on it, so we only get one score per version of a problem
sub_id = json.dumps((sub.problem_id, sub.started.isoformat()))
score = -float('inf')
if sub.correct:
if sub.got_hint:
score = 1
else:
score = 2
elif sub.gave_up:
score = -1
if sub_id in attempted_probs:
# Take the maximum score a student achieved on a given problem session
# (e.g. they get full credit for getting it right second try)
attempted_probs[sub_id] = max(attempted_probs[sub_id], score)
else:
attempted_probs[sub_id] = score
# Sort problems by submission time so we can keep a running progress total
sorted_score_tups = sorted(attempted_probs.items(), key=lambda x: json.loads(x[0])[1])
# Keep track of problem id as well as score achieved (we may have multiple
# scores for the same problem if the student did multiple versions of it)
sub_score_pairs = map(lambda x: (json.loads(x[0])[0], x[1]), sorted_score_tups)
# Keep this dictionary around so we don't repeat problem queries
seen_problems = {}
# Keep a set of all problems the user got without a hint and don't repeat them
mastered_problem_ids = []
concept_progress = {}
problem_id_to_time_given_up = defaultdict(list)
score_pair = None
for i, score_pair in enumerate(sub_score_pairs):
# If student attempted problem but never gave up or got it right, ignore it
if score_pair[1] == -float('inf'):
continue
if score_pair[0] not in seen_problems:
seen_problems[score_pair[0]] = PracticeProblemTemplate.query.filter_by(id=score_pair[0]).first()
problem = seen_problems[score_pair[0]]
if score_pair[1] == 2 and score_pair[0] not in mastered_problem_ids:
mastered_problem_ids.append(score_pair[0])
# if the user gave up on the problem, record the index the user gave up on the problem
if score_pair[1] == -1:
problem_id_to_time_given_up[score_pair[0]].append(i)
for concept in problem.concepts:
if concept.name not in concept_progress:
concept_progress[concept.name] = 0
concept_progress[concept.name] += score_pair[1]
# Keep concept_progress between 0 and 10 at all times
concept_progress[concept.name] = max(min(concept_progress[concept.name], 10), 0)
return (mastered_problem_ids, concept_progress, score_pair, seen_problems, problem_id_to_time_given_up, len(sub_score_pairs))
def get_next_problem(user_id, language_id):
exempt_problem_ids, concept_progress, last_prob, seen_problems, problem_id_to_time_given_up, problems_attempted = get_user_progress(user_id, language_id)
going_back = False
if last_prob != None:
# If we gave up on our last problem, possibly go back to a mastered problem
if last_prob[1] == -1:
going_back = True
exempt_problem_ids = []
# Don't repeat a problem twice in a row
if last_prob[0] not in exempt_problem_ids:
exempt_problem_ids.append(last_prob[0])
# Get all current problems we haven't mastered and didn't just attempt
all_problems = PracticeProblemTemplate.query.filter(and_(and_(not_(PracticeProblemTemplate.id.in_(exempt_problem_ids)), PracticeProblemTemplate.is_current == True)), PracticeProblemTemplate.language_id == language_id).all()
# filter out problems that the user has repeatedly given up on
new_all_problems = []
for prob in all_problems:
if prob.id in problem_id_to_time_given_up:
# if it has been at least (3 * number given up) problems since the last time problem prob has been given up on,
# then we can reconsider this problem for use
if problems_attempted - problem_id_to_time_given_up[prob.id][-1] > len(problem_id_to_time_given_up[prob.id]) * 3:
new_all_problems.append(prob)
else:
new_all_problems.append(prob)
if len(new_all_problems) > 5:
all_problems = new_all_problems
# Give each problem a score based on how well we know each of its concepts
problem_scores = {}
for prob in all_problems:
prob_score = 0
for concept in prob.concepts:
if concept.name in concept_progress:
prob_score += concept_progress[concept.name]
else:
# Unseen concepts get a score of minus 10
prob_score -= 10
# Normalize score by the length of the concept list
if prob.concepts.count() > 0:
prob_score /= float(prob.concepts.count())
problem_scores[prob.problem_name] = prob_score
# Sort problems by score (high score means we've learned more of its concepts)
sorted_prob_score_pairs = sorted(problem_scores.items(), key=lambda x: x[1], reverse=True)
# Default to easiest problem if we've mastered all problems or if we haven't
# done any yet
next_prob_name = 'print'
if len(sorted_prob_score_pairs) > 0 and sorted_prob_score_pairs[0][1] > -10:
next_prob_name = sorted_prob_score_pairs[0][0]
# If we're going back, randomize the next problem somewhat to prevent
# serving the same problem every time we give up on something
if going_back:
max_score = sorted_prob_score_pairs[0][1]
top_prob_names = []
for psp in sorted_prob_score_pairs:
if psp[0] < max_score - 1:
break
top_prob_names.append(psp[0])
next_prob_name = random.choice(top_prob_names)
return next_prob_name
@app.route('/concept/<language>/<concept_name>/')
@login_required
def concept_explanation(language, concept_name):
if language not in language_map:
return redirect('/practice_progress/')
language_id = language_map[language]
concept = PracticeProblemConcept.query.filter_by(name=concept_name, language_id=language_id).first()
if concept == None:
return redirect('/practice_progress/')
return render_template('concept_explanation.html', concept=concept)
@app.route('/practice_progress/<language>')
@login_required
def practice_progress(language):
if language not in language_map:
return redirect('/')
return practice_progress_by_user_id(current_user.id, language_map[language])
def practice_progress_by_user_id(user_id, language_id, admin=False):
mastered_problem_ids, concept_progress, last_prob, seen_problems, problem_id_to_time_given_up, problems_attempted = get_user_progress(user_id, language_id)
language = language_map[language_id]
# Get a list of mastered problems that are still current,
# in reverse order of mastery
mastered_problems = []
for prob_id in reversed(mastered_problem_ids):
if seen_problems[prob_id].is_current:
mastered_problems.append(seen_problems[prob_id].to_dict())
# Get percent of current problems that have been mastered
current_problem_count = PracticeProblemTemplate.query.filter_by(is_current=True, language_id=language_id).count()
mastered_percent = int(float(len(mastered_problems)) / current_problem_count * 100)
# Get progress for all concepts, including ones the user has not yet encountered
all_concepts = PracticeProblemConcept.query.filter_by(language_id=language_id).all()
all_concept_progress = {}
concept_display_names = {}
for concept in all_concepts:
all_concept_progress[concept.name] = 0
concept_display_names[concept.name] = concept.display_name
for concept_name, concept_score in concept_progress.items():
# Convert concept_score to a percentage, assuming it is out of ten
all_concept_progress[concept_name] = float(concept_score) * 10
sorted_concept_progress = sorted(all_concept_progress.items(), key=lambda x: x[1], reverse=True)
# Get map of concept display names to scores
display_concept_progress = []
for name, score in sorted_concept_progress:
display_concept_progress.append((name, concept_display_names[name], score))
user = User.query.get(user_id)
# Get all attempts
all_attempts = PracticeProblemSubmission.query.filter_by(user_id=user_id, language_id=language_id)
attempts = []
current_problem_id = -1
for att in all_attempts:
if current_problem_id != att.problem_id:
if current_problem_id != -1:
attempts.append(current_attempt)
current_problem_id = att.problem_id
current_problem_name = PracticeProblemTemplate.query.get(att.problem_id).problem_name
current_attempt = {'gave_up': False, 'got_hint': False, 'correct': False, 'num_attempted': 0, 'problem_name': current_problem_name}
current_attempt['num_attempted'] += 1
current_attempt['got_hint'] |= att.got_hint
current_attempt['correct'] |= att.correct
current_attempt['gave_up'] = att.gave_up
if current_problem_id != -1:
attempts.append(current_attempt)
return render_template('practice_progress.html', mastered_problems=mastered_problems, mastered_percent=mastered_percent, concept_progress=display_concept_progress, name=user.firstname + ' ' + user.lastname, attempts = attempts, admin=admin, user_id=user_id, language=language)
@app.route('/practice/<language>/')
@login_required
def practice_default(language):
if language not in language_map:
return redirect('/')
next_problem_name = get_next_problem(current_user.id, language_map[language])
return redirect('/practice/%s/%s/' % (language, next_problem_name))
@app.route('/homework/')
@login_required
def homework():
language_id = language_map['javascript'];
problem_list = HomeworkProblem.query.order_by(HomeworkProblem.deadline).all()
correct_subs = PracticeProblemSubmission.query.filter_by(user_id=current_user.id, language_id=language_id, correct=True).all()
correct_ids = map(lambda x: x.problem_id, correct_subs)
for problem in problem_list:
if problem.template_id not in correct_ids:
return redirect('/homework/problem/' + str(problem.id) + '/')
return redirect('/homework/calendar/')
@app.route('/homework/calendar/')
@login_required
def homework_calendar():
language_id = language_map['javascript'];
day_map = {1: 'Thu', 2: 'Fri', 3: 'Sat', 4: 'Sun', 5: 'Mon', 6: 'Tue'}
problem_list = HomeworkProblem.query.order_by(HomeworkProblem.deadline).all()
homework_list = Homework.query.all()
template_list = PracticeProblemTemplate.query.filter_by(language_id=language_id).all()
template_dict = {}
for template in template_list:
template_dict[template.id] = template
homework_dict = {}
for homework in homework_list:
homework_dict[homework.id] = homework
correct_subs = PracticeProblemSubmission.query.filter_by(user_id=current_user.id, language_id=language_id, correct=True).all()
correct_ids = map(lambda x: x.problem_id, correct_subs)
homeworks = {}
for problem in problem_list:
template = template_dict[problem.template_id]
homework = homework_dict[problem.homework_id]
problem_obj = template.to_dict()
problem_day = 6 - (homework.deadline - problem.deadline).days
problem_obj['problem_id'] = problem.id
problem_obj['completed'] = problem.template_id in correct_ids
if homework.week not in homeworks:
homeworks[homework.week] = {}
if problem_day not in homeworks[homework.week]:
homeworks[homework.week][problem_day] = []
homeworks[homework.week][problem_day].append(problem_obj)
return render_template('homework_calendar.html', homeworks=homeworks, day_map=day_map)
@app.route('/homework/problem/<int:problem_id>/')
@login_required
def homework_prob(problem_id):
language_id = language_map['javascript']
homework_problem = HomeworkProblem.query.filter_by(id=problem_id).first();
if homework_problem == None:
return redirect('/')
problem_obj = PracticeProblemTemplate.query.get(homework_problem.template_id);
problem = problem_obj.to_dict()
problem['template_vars'] = utils.get_template_vars(problem['gen_template_vars'])
problem = utils.get_problem(problem, language_id)
# Deal with concepts
concept_names = ast_utils.get_concepts(problem['solution'], language_id)
problem['concept_names'] = json.dumps(concept_names)
# Remove from our problem template any concepts which are not found in this
# random version of it
concepts = problem['concepts'].all()
edited = False
for i in range(len(concepts)-1, -1, -1):
if concepts[i].name not in concept_names:
del concepts[i]
edited = True
if edited:
problem_obj.concepts = concepts
db.session.commit()
# Handle start_time server side
dthandler = lambda obj: obj.isoformat()
start_time = json.dumps(datetime.datetime.now(), default=dthandler)
problem['start_time'] = start_time
return render_template('homework.html', problem=problem, homework=homework_problem)
@app.route('/practice/<language>/<problem_name>/')
@login_required
def practice(language, problem_name):
if not current_user.is_authenticated():
return render_template('message.html', message='You need to log in first')
if language not in language_map:
return redirect('/')
language_id = language_map[language]
problem_obj = PracticeProblemTemplate.query.filter_by(problem_name=problem_name, is_current=True, language_id=language_id).first()
if problem_obj is not None:
problem = problem_obj.to_dict()
problem['template_vars'] = utils.get_template_vars(problem['gen_template_vars'])
problem = utils.get_problem(problem, language_id)
concept_names = ast_utils.get_concepts(problem['solution'], language_id)
problem['concept_names'] = json.dumps(concept_names)
# Remove from our problem template any concepts which are not found in this
# random version of it
concepts = problem['concepts'].all()
edited = False
display_concepts = []
for i in range(len(concepts)-1, -1, -1):
if concepts[i].name not in concept_names:
del concepts[i]
edited = True
else:
display_concepts.append((concepts[i].name, concepts[i].display_name))
if edited:
problem_obj.concepts = concepts
db.session.commit()
# Get a list of new concepts, if there are any. This is extremely
# inefficient because we already queried all the user's submissions to
# figure out which problem to serve next
all_subs = PracticeProblemSubmission.query.filter_by(user_id=current_user.id, language_id=language_id).all()
seen_concept_names = sets.Set()
for sub in all_subs:
if sub.correct:
for concept in sub.concepts.all():
seen_concept_names.add(concept.name)
new_concepts = []
for concept in concepts:
if concept.name not in seen_concept_names:
new_concepts.append((concept.name, concept.display_name))
# Handle start_time server side
dthandler = lambda obj: obj.isoformat()
start_time = json.dumps(datetime.datetime.now(), default=dthandler)
problem['start_time'] = start_time
return render_template('practice.html', problem=problem, concepts=display_concepts, new_concepts=new_concepts, language=language)
else:
return redirect('/practice/%s' % language)
@app.route('/practice_progress/<language>/<problem_name>/')
@login_required
def practice_view(language, problem_name):
if language not in language_map:
return redirect('/')
return practice_view_by_user_id(current_user.id, language_map[language], problem_name)
def practice_view_by_user_id(user_id, language_id, problem_name, fullhistory=False, admin=False):
problem_obj = PracticeProblemTemplate.query.filter_by(problem_name=problem_name, is_current=True, language_id=language_id).first()
if problem_obj == None:
return redirect('/practice_progress/%s' % language_map[language_id])
if fullhistory:
problem_submissions = PracticeProblemSubmission.query.filter_by(user_id=user_id, language_id=language_id, problem_id=problem_obj.id).order_by(PracticeProblemSubmission.submitted.desc())
if problem_submissions == None:
return redirect('/practice_progress/%s' % language_map[language_id])
problem_submissions = list(problem_submissions)
if len(problem_submissions) == 0:
return redirect('/practice_progress/%s' % language_map[language_id])
problem = problem_obj.to_dict()
problem['template_vars'] = problem_submissions[0].template_vars
problem = utils.get_problem(problem, language_id)
else:
problem_submission = PracticeProblemSubmission.query.filter_by(user_id=user_id, language_id=language_id, problem_id=problem_obj.id, correct=True).order_by(PracticeProblemSubmission.started.desc()).first()
if problem_submission == None:
return redirect('/practice_progress/%s' % language_map[language_id])
problem = problem_obj.to_dict()
problem['template_vars'] = problem_submission.template_vars
problem = utils.get_problem(problem, language_id)
if fullhistory:
return render_template('practice_view_history.html', problem=problem, user_submissions=problem_submissions, admin=admin, user_id=user_id, language=language_map[language_id])
else:
return render_template('practice_view.html', problem=problem, user_solution=problem_submission.code, language=language_map[language_id])
@app.route('/admin/practice_progress/<user_id>/<language>/')
@roles_required('admin')
def admin_practice_progress(user_id, language):
if language not in language_map:
return redirect('/')
return practice_progress_by_user_id(user_id, language_map[language], admin=True)
@app.route('/admin/practice_progress/<user_id>/<language>/<problem_name>/')
@roles_required('admin')
def admin_practice_view(user_id, language, problem_name):
if language not in language_map:
return redirect('/')
return practice_view_by_user_id(user_id, language_map[language], problem_name, fullhistory=True, admin=True)
# Parses x for dicts, and returns a tuple where the first item is a list of the
# dicts and the second item is x with all the dicts removed
def str_remove_dicts(x):
in_single_quotes = False
in_double_quotes = False
escaped = False
dict_nest_level = 0
dict_start_idx = 0
i = 0
dicts = []
while i < len(x):
ch = x[i]
if ch == "'" and not escaped and not in_double_quotes:
in_single_quotes = not in_single_quotes
if ch == '"' and not escaped and not in_single_quotes:
in_double_quotes = not in_double_quotes
if ch == "\\":
escaped = not escaped
else:
escaped = False
if ch == '{' and not in_single_quotes and not in_double_quotes:
if dict_nest_level == 0:
dict_start_idx = i
dict_nest_level += 1
if ch == '}' and not in_single_quotes and not in_double_quotes:
dict_nest_level -= 1
if dict_nest_level == 0:
dicts.append(ast.literal_eval(x[dict_start_idx:i+1]))
x = x[:dict_start_idx] + x[i+1:]
i = dict_start_idx - 1
i += 1
return (dicts, x)
# Check if two dicts are the same, ignoring key order
def same_dict(x, y):
if len(x.keys()) != len(y.keys()):
return False
for k, v in x.items():
if y[k] != v:
return False
return True
# Check if two output strings are really the same
# Ignores dictionary print order
def same_output(x, y):
x_dicts, x_code = str_remove_dicts(x)
y_dicts, y_code = str_remove_dicts(y)
if x_code.strip() != y_code.strip():
return False
if len(x_dicts) != len(y_dicts):
return False
for i, d in enumerate(x_dicts):
if not same_dict(y_dicts[i], d):
return False
return True
@app.route('/practice/<language>/<problem_name>/submit/', methods=['POST'])
@login_required
def submit_practice(language, problem_name):
if language not in language_map:
return 'error'
language_id = language_map[language]
code = request.form['code']
result_test = request.form['result_test']
result_no_test = request.form['result_no_test']
result_test_error = 1 if request.form['result_test_error'] == 'true' else 0
result_no_test_error = 1 if request.form['result_no_test_error'] == 'true' else 0
start_time = dateutil.parser.parse(json.loads(request.form['start_time']))
submit_time = datetime.datetime.now()
template_vars = request.form['template_vars']
concept_names = json.loads(request.form['concept_names'])
concepts = []
for concept_name in concept_names:
concept = PracticeProblemConcept.query.filter_by(name=concept_name, language_id=language_id).first()
if concept == None:
concept = PracticeProblemConcept(name=concept_name, language_id=language_id)
db.session.add(concept)
db.session.commit()
concepts.append(concept)
problem = PracticeProblemTemplate.query.filter_by(problem_name=problem_name, is_current=True, language_id=language_id).first().to_dict()
got_hint = True if request.form['got_hint'] == 'true' else False
gave_up = True if request.form['gave_up'] == 'true' else False
problem['template_vars'] = template_vars
problem = utils.get_problem(problem, language_id)
correct = same_output(problem['expected_test'], result_test) and same_output(problem['expected_no_test'], result_no_test)
submission = PracticeProblemSubmission(problem_id=problem['id'], language_id=language_id, user_id=current_user.id, code=code, result_test=result_test, result_no_test=result_no_test, result_test_error=result_test_error, result_no_test_error=result_no_test_error, got_hint=got_hint, gave_up=gave_up, correct=correct, started=start_time, submitted=submit_time, template_vars=problem['template_vars'], concepts=concepts)
db.session.add(submission)
db.session.commit()
return_data = {}
if correct:
return_data['correct'] = 'correct'
return_data['solution'] = problem['solution']
elif result_no_test_error:
return_data['correct'] = 'error'
else:
# Insert random inspirational failure quote
return_data['correct'] = 'incorrect'
failure_quotes = [
('Our greatest glory is not in never failing, but in rising up every time we fail.', 'Ralph Waldo Emerson'),
('Satisfaction lies in the effort, not in the attainment. Full effort is full victory.', 'Mahatma Gandhi'),
('As you proceed through life, following your own path, birds will shit on you. Don\'t bother to brush it off. Getting a comedic view of your situation gives you spiritual distance. Having a sense of humor saves you.', 'Joseph Campbell'),
('If you focus on the risks, they\'ll multiply in your mind and eventually paralyze you. You want to focus on the task, instead, on doing what needs to be done.', 'Barry Eisler'),
('If you take responsibility and blame yourself, you have the power to change things. But if you put responsibility on someone else, then you are giving them the power to decide your fate.', 'Deja King'),
('H is for Habit, winners make a habit of doing the things losers don\'t want to do.', 'Lucas Remmerswaal'),
('Success is a state of mind. If you want success, start thinking of yourself as a success.', 'Joyce Brothers'),
("If you set your goals ridiculously high and it's a failure, you will fail above everyone elses success", 'James Cameron'),
('Genius is one percent inspiration and ninety-nine percent perspiration.', 'Thomas Edison'),
('Luck is the dividend of sweat. The more you sweat, the luckier you get.', 'Ray Kroc'),
('To have failed is to have striven, to have striven is to have grown.', 'Maltbie Davenport Babcock'),
('He who does not hope to win has already lost.', 'Jose Joaquin Olmedo'),
('When you get crapped on, grow a garden.', 'Tanja Kobasic'),
('It is the work that matters, not the applause that follows.', 'Robert Falco Scott'),
("I have not failed. I've just found 10,000 ways that won't work.", 'Thomas Edison'),
('Success is not final, failure is not fatal: it is the courage to continue that counts.', 'Winston Churchill'),
('Pain is temporary. Quitting lasts forever.', 'Lance Armstrong'),
('Failure is the condiment that gives success its flavor.', 'Truman Capote'),
('Success is stumbling from failure to failure with no loss of enthusiasm.', 'Winston Churchill'),
("The brick walls are there for a reason. The brick walls are not there to keep us out. The brick walls are there to give us a chance to show how badly we want something. Because the brick walls are there to stop the people who don't want it badly enough. They're there to stop the other people.", 'Randy Pausch'),
("A thinker sees his own actions as experiments and questions--as attempts to find out something. Success and failure are for him answers above all.", 'Friedrich Nietzsche'),
("A bad day for your ego is a great day for your soul.", 'Jillian Michaels'),
('Never confuse a single defeat with a final defeat.', 'F. Scott Fitzgerald'),
("You're not obligated to win. You're obligated to keep trying. To the best you can do everyday.", 'Jason Mraz'),
('If you fell down yesterday, stand up today.', 'H.G. Wells'),
('We are all failures- at least the best of us are.', 'J.M. Barrie'),
('Try again. Fail again. Fail better.', 'Samuel Beckett'),
('The only real mistake is the one from which we learn nothing.', 'Henry Ford'),
('Failures are finger posts on the road to achievement.', 'C.S. Lewis'),
('The person who failed often knows how to avoid future failures. The person who knows only success can be more oblivious to all the pitfalls.', 'Randy Pausch'),
('As long as I am breathing, in my eyes, I am just beginning.', 'Criss Jami'),
('The harder you fall, the heavier your heart; the heavier your heart, the stronger you climb; the stronger you climb, the higher your pedestal.', 'Criss Jami'),
("All the time you're saying to yourself, \"I could do that, but I won't,\"--which is just another way of saying that you can't.", "Richard P. Feynman"),
("All men make mistakes, but a good man yields when he knows his course is wrong, and repairs the evil. The only crime is pride.", 'Sophocles'),
("Winners are not afraid of losing. But losers are. Failure is part of the process of success. People who avoid failure also avoid success.", 'Robert T. Kiyosaki'),
("What seems to us as bitter trials are often blessings in disguise", 'Oscar Wilde'),
("Failure should be our teacher, not our undertaker. Failure is delay, not defeat. It is a temporary detour, not a dead end. Failure is something we can avoid only by saying nothing, doing nothing, and being nothing.", 'Denis Waitley'),
("'Almost' only counts in horseshoes and hand grenades.", 'Anonymous'),
("You build on failure. You use it as a stepping sone. Close the door on the past. You don't try to forget the mistakes, but you don't dwell on it. You don't let it have any of your energy, or any of your time, or any of your space.", 'Johnny Cash'),
("We have forty million reasons for failure, but not a single excuse.", 'Rudyard Kipling'),
("Failure? I never encountered it. All I ever met were temporary setbacks.", 'Dottie Walters'),
("The major difference between a thing that might go wrong and a thing that cannot possibly go wrong is that when a thing that cannot possibly go wrong goes wrong it usually turns out to be impossible to get at or repair.", 'Douglas Adams'),
('A man who fails well is greater than one who succeeds badly.', 'Thomas Merton'),
('Our business in this world is not to succeed, but to continue to fail, in good spirits.', 'Robert Louis Stevenson'),
("If at first you don't succeed, try, try, try again.", 'Anonymous')
]
return_data['failure_quote'] = random.choice(failure_quotes)