diff --git a/app.py b/app.py new file mode 100644 index 0000000..e98b024 --- /dev/null +++ b/app.py @@ -0,0 +1,115 @@ + +import random +from contextlib import contextmanager # `with` statement in db sessions + +# libs +import bcrypt +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker, scoped_session + +# this project +import questions +from models import Student + + +# ============================================================================ +# LearnApp - application logic +# ============================================================================ +class LearnApp(object): + def __init__(self): + print('LearnApp.__init__') + self.factory = questions.QuestionFactory() + self.factory.load_files(['questions.yaml'], 'demo') # FIXME + self.online = {} + + # connect to database and check registered students + engine = create_engine('sqlite:///{}'.format('students.db'), echo=False) + self.Session = scoped_session(sessionmaker(bind=engine)) + try: + with self.db_session() as s: + n = s.query(Student).filter(Student.id != '0').count() + except Exception as e: + print('Database not usable.') + raise e + else: + print('Database has {} students registered.'.format(n)) + + # ------------------------------------------------------------------------ + def login_ok(self, uid, try_pw): + print('LearnApp.login') + + with self.db_session() as s: + student = s.query(Student).filter(Student.id == uid).one_or_none() + + if student is None or student in self.online: + # student does not exist + return False + + # hashedtry = yield executor.submit(bcrypt.hashpw, + # try_pw.encode('utf-8'), student.password) + hashedtry = bcrypt.hashpw(try_pw.encode('utf-8'), student.password) + + if hashedtry != student.password: + # wrong password + return False + + # success + self.online[uid] = { + 'name': student.name, + 'number': student.id, + 'current': None, + } + print(self.online) + return True + + # ------------------------------------------------------------------------ + # logout + def logout(self, uid): + del self.online[uid] # FIXME save current question? + + # ------------------------------------------------------------------------ + # given the currect state, generates a new question for the student + def new_question_for(self, uid): + questions = list(self.factory) + nextquestion = self.factory.generate(random.choice(questions)) + self.online[uid]['current'] = nextquestion + return nextquestion + + # ------------------------------------------------------------------------ + def get_current_question(self, uid): + return self.online[uid].get('current', None) + + # ------------------------------------------------------------------------ + def get_student_name(self, uid): + return self.online[uid].get('name', '') + + # ------------------------------------------------------------------------ + # check answer and if correct returns new question, otherise returns None + def check_answer(self, uid, answer): + question = self.get_current_question(uid) + print('------------------------------') + print(question) + print(answer) + + if question is not None: + grade = question.correct(answer) # correct answer + correct = grade > 0.99999 + if correct: + print('CORRECT') + return self.new_question_for(uid) + else: + print('WRONG') + return None + else: + print('FIRST QUESTION') + return self.new_question_for(uid) + + # ------------------------------------------------------------------------ + # helper to manage db sessions using the `with` statement, for example + # with self.db_session() as s: s.query(...) + @contextmanager + def db_session(self): + try: + yield self.Session() + finally: + self.Session.remove() diff --git a/serve.py b/serve.py index edc9386..444abde 100755 --- a/serve.py +++ b/serve.py @@ -3,25 +3,17 @@ # python standard library import os import json -import random -from contextlib import contextmanager # `with` statement in db sessions # installed libraries -import bcrypt import markdown import tornado.ioloop import tornado.web import tornado.httpserver from tornado import template, gen import concurrent.futures -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker, scoped_session # this project -import questions -from models import Student # DataBase, - - +from app import LearnApp # markdown helper def md(text): @@ -34,83 +26,9 @@ def md(text): 'markdown.extensions.sane_lists' ]) -# A thread pool to be used for password hashing with bcrypt. +# A thread pool to be used for password hashing with bcrypt. FIXME and other things? executor = concurrent.futures.ThreadPoolExecutor(2) -# ============================================================================ -# LearnApp - application logic -# ============================================================================ -class LearnApp(object): - def __init__(self): - print('LearnApp.__init__') - self.factory = questions.QuestionFactory() - self.factory.load_files(['questions.yaml'], 'demo') # FIXME - self.online = {} - - # connect to database and check registered students - engine = create_engine('sqlite:///{}'.format('students.db'), echo=False) - self.Session = scoped_session(sessionmaker(bind=engine)) - try: - with self.db_session() as s: - n = s.query(Student).filter(Student.id != '0').count() - except Exception as e: - print('Database not usable.') - raise e - else: - print('Database has {} students registered.'.format(n)) - - # ------------------------------------------------------------------------ - def login_ok(self, uid, try_pw): - print('LearnApp.login') - - with self.db_session() as s: - student = s.query(Student).filter(Student.id == uid).one_or_none() - - if student is None or student in self.online: - # student does not exist - return False - - # hashedtry = yield executor.submit(bcrypt.hashpw, - # try_pw.encode('utf-8'), student.password) - hashedtry = bcrypt.hashpw(try_pw.encode('utf-8'), student.password) - - if hashedtry != student.password: - # wrong password - return False - - # success - self.online[uid] = { - 'name': student.name, - 'number': student.id, - 'current': None, - } - print(self.online) - return True - - # ------------------------------------------------------------------------ - # logout - def logout(self, uid): - del self.online[uid] # FIXME save current question? - - # ------------------------------------------------------------------------ - # returns dictionary - def next_question(self, uid): - # print('next question') - # q = self.factory.generate('math-expressions') - questions = list(self.factory) - q = self.factory.generate(random.choice(questions)) - self.online[uid]['current'] = q - return q - - # ------------------------------------------------------------------------ - # helper to manage db sessions using the `with` statement, for example - # with self.db_session() as s: s.query(...) - @contextmanager - def db_session(self): - try: - yield self.Session() - finally: - self.Session.remove() # ============================================================================ # WebApplication - Tornado Web Server @@ -121,7 +39,6 @@ class WebApplication(tornado.web.Application): (r'/', LearnHandler), (r'/login', LoginHandler), (r'/logout', LogoutHandler), - # (r'/learn', LearnHandler), (r'/question', QuestionHandler), ] settings = { @@ -156,34 +73,26 @@ class BaseHandler(tornado.web.RequestHandler): if user in self.learn.online: return user -# # ---------------------------------------------------------------------------- -# class MainHandler(BaseHandler): -# @tornado.web.authenticated -# def get(self): -# self.redirect('/learn') - # ---------------------------------------------------------------------------- # /auth/login and /auth/logout # ---------------------------------------------------------------------------- class LoginHandler(BaseHandler): def get(self): - self.render('login.html') + self.render('login.html', error='') # @gen.coroutine def post(self): uid = self.get_body_argument('uid') pw = self.get_body_argument('pw') - print(f'login.post: user={uid}, pw={pw}') + # print(f'login.post: user={uid}, pw={pw}') - x = self.learn.login_ok(uid, pw) - print(x) - if x: # hashedtry == student.password: + if self.learn.login_ok(uid, pw): print('login ok') self.set_secure_cookie("user", str(uid)) self.redirect(self.get_argument("next", "/")) else: print('login failed') - self.render("login.html", error="incorrect password") + self.render("login.html", error='Número ou senha incorrectos') # ---------------------------------------------------------------------------- class LogoutHandler(BaseHandler): @@ -199,65 +108,51 @@ class LogoutHandler(BaseHandler): class LearnHandler(BaseHandler): @tornado.web.authenticated def get(self): - print('GET /learn') - user = self.current_user - name = self.application.learn.online[user]['name'] - print(' user = '+user) - print(self.learn.online[user]['name']) - self.render('learn.html', name=name, uid=user) # FIXME - # self.learn.online[user]['name'] + uid = self.current_user + self.render('learn.html', + uid=uid, + name=self.learn.get_student_name(uid) + ) + # ---------------------------------------------------------------------------- # respond to AJAX to get a JSON question class QuestionHandler(BaseHandler): + templates = { + 'checkbox': 'question-checkbox.html', + 'radio': 'question-radio.html', + 'text': 'question-text.html', + 'text_regex': 'question-text.html', + 'text_numeric': 'question-text.html', + 'textarea': 'question-textarea.html', + } + @tornado.web.authenticated def get(self): self.redirect('/') @tornado.web.authenticated def post(self): - print('---------------> question.post') - # experiment answering one question and correct it - ref = self.get_body_arguments('question_ref') - # print('Reference' + str(ref)) - + print('================= POST ==============') + # ref = self.get_body_arguments('question_ref') user = self.current_user - userdata = self.learn.online[user] - question = userdata['current'] # get current question - print('=====================================') - print(' ' + str(question)) - print('-------------------------------------') - - if question is not None: - answer = self.get_body_arguments('answer') - print(' answer = ' + str(answer)) - # question['answer'] = ans # insert answer - grade = question.correct(answer) # correct answer - print(' grade = ' + str(grade)) - - correct = grade > 0.99999 - if correct: - question = self.application.learn.next_question(user) - + answer = self.get_body_arguments('answer') + + next_question = self.learn.check_answer(user, answer) + + if next_question is not None: + html_out = self.render_string(self.templates[next_question['type']], + question=next_question, # dictionary with the question + md=md, # function that renders markdown to html + ) + self.write({ + 'html': tornado.escape.to_unicode(html_out), + 'correct': True, + }) else: - correct = True # to animate correctly - question = self.application.learn.next_question(user) - - templates = { - 'checkbox': 'question-checkbox.html', - 'radio': 'question-radio.html', - 'text': 'question-text.html', - 'text_regex': 'question-text.html', - 'text_numeric': 'question-text.html', - 'textarea': 'question-textarea.html', - } - html_out = self.render_string(templates[question['type']], - question=question, # the dictionary with the question?? - md=md, # passes function that renders markdown to html - ) - self.write({ - 'html': tornado.escape.to_unicode(html_out), - 'correct': correct, - }) + self.write({ + 'html': 'None', + 'correct': False + }) # ---------------------------------------------------------------------------- @@ -276,5 +171,6 @@ def main(): tornado.ioloop.IOLoop.current().stop() print('\n--- stop ---') +# ---------------------------------------------------------------------------- if __name__ == "__main__": main() \ No newline at end of file diff --git a/templates/learn.html b/templates/learn.html index 1e0f4cd..1769d57 100644 --- a/templates/learn.html +++ b/templates/learn.html @@ -78,7 +78,7 @@ - + @@ -111,26 +111,27 @@ $.fn.extend({ // } function updateQuestion(response){ - $("#question_div").html(response["html"]); - MathJax.Hub.Queue(["Typeset",MathJax.Hub,"question"]); - if (response["correct"]) + if (response["correct"]) { + $("#question_div").html(response["html"]); + MathJax.Hub.Queue(["Typeset",MathJax.Hub,"question_div"]); + + $("input:text").keypress(function (e) { + if (e.keyCode == 13) { + e.preventDefault(); + getQuestion(); + } + }); + $("textarea").keydown(function (e) { + if (e.keyCode == 13 && e.shiftKey) { + e.preventDefault(); + getQuestion(); + } + }); $('#question_div').animateCSS('pulse'); + } else $('#question_div').animateCSS('shake'); - - $("input:text").keypress(function (e) { - if (e.keyCode == 13) { - e.preventDefault(); - getQuestion(); - } - }); - $("textarea").keydown(function (e) { - if (e.keyCode == 13 && e.shiftKey) { - e.preventDefault(); - getQuestion(); - } - }); } function getQuestion() { diff --git a/templates/login.html b/templates/login.html index 050d60b..e4c2596 100644 --- a/templates/login.html +++ b/templates/login.html @@ -30,6 +30,7 @@
{{ error }}