-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhandlers.py
643 lines (517 loc) · 23 KB
/
handlers.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
# -*- coding: utf-8 -*-
import logging
# related third party imports
import webapp2
from google.appengine.api import users # we are using this for authentication
from google.appengine.ext import ndb
# local application/library specific imports
import models # we are using this for authorization and storing of further pii
from basehandler import BaseHandler
from decorators import teacher_required, \
student_required, \
active_student_required, \
user_required, same_domain_required
import role_numbers
import re
class MainHandler(BaseHandler):
def get(self):
params = {}
return self.render_template('home.html', **params)
class LoginHandler(BaseHandler):
def get(self):
#self.redirect(self.auth_config['login_url'])
# if we do this, the dest_url will be /login, so you'll
# be stuck in an infinite loop
#self.redirect(users.create_login_url('/'))# home page
self.redirect(users.create_login_url('/login-return'))# LoginReturnHandler
class LoginReturnHandler(BaseHandler):
def get(self, continue_url='/'):
""" Determine if returning login is Teacher or Student
based on Gmail address found:
in Teacher:
TeacherLoginHandler
in Student:
StudentLoginHandler
not found:
TeacherLoginHandler
"""
params = {}
params['continue_url'] = continue_url
if self.user is not None: # user has logged in with their google account
# teacher or student?
# students should be pre-registered by the teachers, so we should
# have some information to go on, right?
student = models.Student.query(
models.Student.email == self.email).get()
if student is not None:
#return StudentLoginHandler(student).check_student()
return self.check_student(student)
teacher = models.Teacher.query(
models.Teacher.user_id == self.user_id).get()
return self.check_teacher(teacher)
return self.redirect_to('home')# no user, go home
def check_teacher(self, teacher):
if teacher is not None:
self.session['is_teacher'] = True
self.session['teacher_key'] = teacher.key.urlsafe()
return self.redirect_to('dashboard')
return self.redirect_to('register')
def check_student(self, student):
if student is not None:
# make sure parental consent has been received AND confirmed
if not student.parental_consent_received or \
not student.parental_consent_confirmed:
message = 'Sorry, but your parental consent has not yet been verified.'
self.add_message(message, 'danger')
return self.redirect_to('home')
self.session['is_student'] = True
self.session['teacher_key'] = student.teacher.urlsafe()
self.session['student_key'] = student.key.urlsafe()
# has the student been activated?
if bool(student.active):
self.session['active'] = True
return self.redirect_to('student-dashboard')
# otherwise, send them to their account confirmation screen
self.session['active'] = False
return self.redirect_to('student-activation')
class StudentActivationHandler(BaseHandler):
""" New student activation """
@student_required
def get(self):
params = {}
return self.render_template('student-activation.html', **params)
@student_required
def post(self):
ok_to_proceed = True
agree_to_terms = self.request.POST.get('agree_to_terms')
error_fields = []
if not bool(agree_to_terms):
ok_to_proceed = False
error_fields.append('agree_to_terms')
if ok_to_proceed:
student = models.Student.query(
models.Student.email == self.email).get()
if student is not None:
student.active = True
student.user_id = self.user_id
student.put()
message = 'Thank you. Your account has been activated.'
self.add_message(message, 'success')
self.session['active'] = True
return self.redirect_to('student-dashboard')
else:
message = 'We\'re sorry, but something has gone terribly awry.'
self.add_message(message, 'danger')
return self.get()
else:
# pass form arguments back to self and re-render template
# seems like there should be a better way, tho
params = {}
for field in self.request.arguments():
params[field] = self.request.POST.get(field)
params['error_fields'] = error_fields
return self.render_template('student-activation.html', **params)
class LogoutHandler(BaseHandler):
def get(self):
# clear session variables
self.session.clear()
# redirect to google account logout
self.redirect(self.auth_config['logout_url'])
class RegisterHandler(BaseHandler):
@user_required
def get(self):
# note to self: turn these two checks into another decorator?
# unregistered_teacher_required or similar?
# if a student logs in, we don't want them to be able to access this page
if self.session.get('is_student') == True:
self.redirect('/', abort=True)
# if a teacher is logged in, and we already have their pii,
# i don't think we want them to be able to access this page
if self.session.get('is_teacher') == True and \
self.session.get('teacher_key') is not None:
self.redirect('dashboard', abort=True)
params = {}
return self.render_template('register.html', **params)
@user_required
@same_domain_required # make sure this is working properly!
def post(self):
# if a student logs in, we don't want them to be able to access this page
if self.session.get('is_student') == True:
self.redirect('/', abort=True)
# if a teacher is logged in, and we already have their pii,
# i don't think we want them to be able to access this page
if self.session.get('is_teacher') == True and \
self.session.get('teacher_key') is not None:
self.redirect('dashboard', abort=True)
first_name = self.request.POST.get('first_name').strip()
last_name = self.request.POST.get('last_name').strip()
phone_number = self.request.POST.get('phone_number').strip()
role_number = self.request.POST.get('role_number').strip()
subject = self.request.POST.get('subject')# they should be able to select multi
agree_to_terms = self.request.POST.get('agree_to_terms')
# validate role number - format, in accepted list, not used already
# first validate all the inputs
ok_to_proceed = True
required_fields, error_fields = [
'first_name',
'last_name',
'phone_number',
'role_number',
'subject'], []
for field in required_fields:
## logging.info('self.request.get(%s) = %s' % (field,
## self.request.get(field)))
if (self.request.POST.get(field) == None or
self.request.POST.get(field) == ''):
ok_to_proceed = False
error_fields.append(field)
if not bool(agree_to_terms):
ok_to_proceed = False
error_fields.append('agree_to_terms')
# validate role number
if (role_numbers.get_role_number(role_number) is None):
error_fields.append('role_number')
ok_to_proceed = False
message = ('Sorry, the Role Number %s is not valid.' % role_number)
self.add_message(message, 'warning')
# see if role_number is already in use
person = models.Teacher.query(models.Teacher.role_number == role_number).get()
if person is not None:
error_fields.append('role_number')
ok_to_proceed = False
message = ('Sorry, the Role Number %s has already been registered.' % role_number)
self.add_message(message, 'warning')
if ok_to_proceed:
teacher = models.Teacher(
user_id = self.user_id,
first_name = first_name,
last_name = last_name,
email = self.email,
phone_number = phone_number,
role_number = role_number,
subjects = (subject,),
active = True,
)
teacher.put()
self.session['teacher_key'] = teacher.key.urlsafe()
self.session['is_teacher'] = True
return self.redirect_to('register-success')
else:
# pass form arguments back to self and re-render template
# seems like there should be a better way, tho
params = {}
for field in self.request.arguments():
params[field] = self.request.POST.get(field)
params['error_fields'] = error_fields
return self.render_template('register.html', **params)
self.redirect_to('dashboard')
class RegisterSuccessHandler(BaseHandler):
def get(self):
params = {}
return self.render_template('register-success.html', **params)
class RegisterStudentHandler(BaseHandler):
""" Teachers register the Students
the logged-in user (Teacher) can pre-register Students
with their first and last names, GMAIL email address, subject and cycle
(grade level)
additionally, they can pre-check that they've received a parental consent
form for this student
forms are bundled and sent to RealNation for additional confirmation
they will need a login to then check AGAIN that the student's parental
consent has been confirmed, at which point the Student will receive an
invitation email
"""
@teacher_required
def get(self):
params = {}
self.render_template('register-student.html', **params)
@teacher_required
def post(self):
first_name = self.request.POST.get('first_name').strip()
last_name = self.request.POST.get('last_name').strip()
email = self.request.POST.get('email').lower()
school_cycle = self.request.POST.get('school_cycle')
subject = self.request.POST.get('subject')
parental_consent_received = self.request.POST.get('parental_consent_received')
# validate fields for content, format, correct values
# first validate all the inputs
ok_to_proceed = True
required_fields, error_fields = [
'first_name',
'last_name',
'email',
'school_cycle',
'subject'], []
for field in required_fields:
## logging.info('self.request.get(%s) = %s' % (field,
## self.request.get(field)))
if (self.request.POST.get(field) == None or
self.request.POST.get(field) == ''):
ok_to_proceed = False
error_fields.append(field)
# check that they are entering a GMAIL address
gmail_regex = r'([a-zA-Z0-9_\.\-\+])+\@g(oogle)?mail\.com'
if re.match(gmail_regex, email, re.IGNORECASE) is None:
ok_to_proceed = False
message = ('%s is not a valid GMAIL address.' % email)
self.add_message(message, 'danger')
error_fields.append('email')
if ok_to_proceed:
# check for this email already in the system somewhere
student = models.Student.query(models.Student.email == email).get()
teacher = models.Teacher.query(models.Teacher.email == email).get()
if student is None and teacher is None:
student = models.Student(
teacher = ndb.Key(urlsafe=self.session['teacher_key']),
first_name = first_name,
last_name = last_name,
email = email,
school_cycle = school_cycle,
subject = subject,
parental_consent_received = bool(parental_consent_received),
)
student.put()
message = ('%s %s has been registered.' % (first_name, last_name))
self.add_message(message, 'success')
else:
message = ('%s has already been registered.' % (email))
self.add_message(message, 'danger')
else:
# pass form arguments back to self and re-render template
# seems like there should be a better way, tho
params = {}
for field in self.request.arguments():
params[field] = self.request.POST.get(field)
params['error_fields'] = error_fields
return self.render_template('register-student.html', **params)
return self.redirect_to('dashboard')
class EditStudentHandler(BaseHandler):
@teacher_required
def get(self, student_id):
params = {}
student = models.Student.get_by_id(long(student_id))
#logging.info(student)
if student is not None:
# if student has already been parentally consented, then we don't
# want them to be editable
if student.parental_consent_confirmed == True:
message = 'The selected student has already been confirmed, \
and is no longer editable.'
self.add_message(message, 'info')
return self.redirect_to('dashboard')
params['student'] = student
return self.render_template('edit-student.html', **params)
return self.redirect_to('dashboard')
@teacher_required
def post(self, student_id):
# should incorporate same validation here as when registering
student = models.Student.get_by_id(long(student_id))
if student is not None:
first_name = self.request.POST.get('first_name').strip()
last_name = self.request.POST.get('last_name').strip()
email = self.request.POST.get('email').lower()
school_cycle = self.request.POST.get('school_cycle').strip()
subject = self.request.POST.get('subject').strip()
parental_consent_received = self.request.POST.get('parental_consent_received')
# if the teacher is updating the email address, make sure
# it doesnt exist somewhere else in the system
# check for this email already in the system somewhere
other_student = models.Student.query(ndb.AND(
models.Student.email == email,
models.Student.user_id != student.user_id)).get()
teacher = models.Teacher.query(models.Teacher.email == email).get()
# validate fields for content, format, correct values
# first validate all the inputs
ok_to_proceed = True
required_fields, error_fields = [
'first_name',
'last_name',
'email',
'school_cycle',
'subject'], []
for field in required_fields:
## logging.info('self.request.get(%s) = %s' % (field,
## self.request.get(field)))
if (self.request.POST.get(field) == None or
self.request.POST.get(field) == ''):
ok_to_proceed = False
error_fields.append(field)
# check that they are entering a GMAIL address
gmail_regex = r'([a-zA-Z0-9_\.\-\+])+\@g(oogle)?mail\.com'
if re.match(gmail_regex, email, re.IGNORECASE) is None:
ok_to_proceed = False
message = ('%s is not a valid GMAIL address.' % email)
self.add_message(message, 'danger')
error_fields.append('email')
if teacher is not None or other_student is not None:
ok_to_proceed = False
message = ('The email address %s is already registered \
elsewhere in the system.' % email)
self.add_message(message, 'warning')
if ok_to_proceed:
student.first_name = first_name
student.last_name = last_name
student.school_cycle = school_cycle
student.subject = subject
student.parental_consent_received = bool(parental_consent_received)
student.put()
message = ('Student updated successfully.')
self.add_message(message, 'success')
else:
# pass form arguments back to self and re-render template
# seems like there should be a better way, tho
params = {}
for field in self.request.arguments():
params[field] = self.request.POST.get(field)
params['error_fields'] = error_fields
params['student'] = student
return self.render_template('edit-student.html', **params)
return self.redirect_to('dashboard')
class DashboardHandler(BaseHandler):
@teacher_required
def get(self):
params = {}
# get teachers students
students = models.Student.query(models.Student.teacher ==
ndb.Key(urlsafe=self.session['teacher_key']))
params['students'] = students
return self.render_template('dashboard.html', **params)
class StudentDashboardHandler(BaseHandler):
@active_student_required
def get(self):
params = {}
return self.render_template('student-dashboard.html', **params)
class CodeforcesIFrameHandler(BaseHandler):
@active_student_required
def get(self):
params = {}
from hashlib import sha1
import hmac
import time
from config import config
# get the students permanent record
rec = models.Student.query(
models.Student.user_id == self.user_id).get()
ts = int(time.time())# note: need to make sure this is UTC timestamp
key = config['push_phrase']
msg = str(ts) + str(self.user_id)
hashed = hmac.new(key, msg, sha1)
auth_token = hashed.hexdigest()
params['ts'] = ts
params['student_id'] = self.user_id
params['auth_token'] = auth_token
params['school_cycle'] = rec.school_cycle
return self.render_template('cf-iframe.html', **params)
def post(self):
""" may allow cf to post back to this same endpoint to handle
student competition results. i just dont know yet. """
pass
class RealNationDashboardHandler(BaseHandler):
def get(self):
params = {}
# pending students
pending_students = models.Student.get_consent_pending()
# received consent students
returned_students = models.Student.get_consent_received()
# confirmed consent students
confirmed_students = models.Student.get_consent_confirmed()
params['pending_students'] = pending_students
params['returned_students'] = returned_students
params['confirmed_students'] = confirmed_students
return self.render_template('rn-dashboard.html', **params)
## API ENDPOINTS ##
class ValidateStudentHandler(webapp2.RequestHandler):
""" external api end-point
when passed a student_id, we will return True if the student is found,
has full parental consent,
and is active
and False otherwise """
def get(self, student_id):
# example: /util/validate-student/120417220130410410718
self.response.headers['Content-Type'] = 'text/plain'
student = models.Student.query(ndb.AND(
models.Student.user_id == student_id,
models.Student.parental_consent_received == True,
models.Student.parental_consent_confirmed == True,
models.Student.active == True
)).get()
if student is not None:
self.response.write('True')
# self.response.write('student.user_id: %s' % str(student.user_id))
else:
self.response.write('False')
## EMAIL HANDLERS ##
##class InviteStudentEmailHandler(webapp2.RequestHandler):
##
## """ this is ugly, and can (should) be done better """
##
## def get(self, student_key):
## # example: /util/invite-student/ahBkZXZ-c3RhcnRlci10ZXN0chQLEgdTdHVkZW50GICAgICAgNAJDA
## self.response.headers['Content-Type'] = 'text/plain'
## student_key = ndb.Key(urlsafe=student_key)
## student = student_key.get()
## if student is not None:
## # self.response.write('True')
## if (student.parental_consent_received == True
## and student.parental_consent_confirmed == True
## and (student.invitation_email_sent == None
## or student.invitation_email_sent == '')):
## # NOTE TO SELF: SENDER_ADDRESS NEEDS TO BE UPDATED!
## sender_address = 'Call to Code Support <[email protected]>'
## subject = 'Welcome to Call to Code!'
## body = """
##Thank you for your interest in Call to Code!
##Your parental consent has been confirmed, and you are ready to activate your account!
##Please login at the link below:
##
##http://www.calltocode.ie/
##
##Thank you and good luck in the competition!
##
##The Call to Code Support Team
##"""
##
## mail.send_mail(sender_address, student.email, subject, body)
## student.invitation_email_sent = datetime.datetime.now()
## student.put()
## self.response.write('Invitation email sent to %s.' % student.email)
## self.response.write(body)
##
## else:
## self.response.write('Invitation email already sent to %s.' % student.email)
##
## else:
## self.response.write('False')
## STATIC TEMPLATE HANDLERS ##
class AboutHandler(BaseHandler):
def get(self):
params = {}
return self.render_template('about.html', **params)
class ContactHandler(BaseHandler):
def get(self):
params = {}
return self.render_template('contact.html', **params)
class AnnouncementsHandler(BaseHandler):
def get(self):
params = {}
return self.render_template('announcements.html', **params)
class FAQsHandler(BaseHandler):
def get(self):
params = {}
return self.render_template('faqs.html', **params)
class ResourcesHandler(BaseHandler):
def get(self):
params = {}
return self.render_template('resources.html', **params)
class NewsHandler(BaseHandler):
def get(self):
params = {}
return self.render_template('news.html', **params)
class PrivacyHandler(BaseHandler):
def get(self):
params = {}
return self.render_template('privacy.html', **params)
class RulesHandler(BaseHandler):
def get(self):
params = {}
return self.render_template('rules.html', **params)