diff --git a/BUGS.md b/BUGS.md index e5547a0..50a1df9 100644 --- a/BUGS.md +++ b/BUGS.md @@ -1,13 +1,14 @@ BUGS: -- models.py tabela de testes não faz sentido. -- reset ao servidor mantem cookie no broser e rebenta. necessario fazer logout. - generators not working: bcrypt (ver blog) - implementar xsrf. Ver [http://www.tornadoweb.org/en/stable/guide/security.html#cross-site-request-forgery-protection]() - implementar navegacao radio/checkbox. cursor cima/baixo, espaco selecciona, enter submete. SOLVED: +- https. certificados selfsigned, no-ip nao suporta certificados +- reset ao servidor mantem cookie no broser e rebenta. necessario fazer logout. +- models.py tabela de testes não faz sentido. - autenticacao. ver exemplo do blog - primeira pergunta aparece a abanar. - user name na barra de navegação. diff --git a/initdb.py b/initdb.py new file mode 100755 index 0000000..6afb130 --- /dev/null +++ b/initdb.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 + +import csv +import argparse +import re +import string +import sys + +import bcrypt +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker + +from models import Base, Student, Answer + +# SIIUE names have alien strings like "(TE)" and are sometimes capitalized +# We remove them so that students dont keep asking what it means +def fix(name): + return string.capwords(re.sub('\(.*\)', '', name).strip()) + +# =========================================================================== +# Parse command line options +argparser = argparse.ArgumentParser(description='Create new database from a CSV file (SIIUE format)') +argparser.add_argument('--db', default='students.db', type=str, help='database filename') +argparser.add_argument('--demo', action='store_true', help='initialize database with a few fake students') +argparser.add_argument('--pw', default='', type=str, help='default password') +argparser.add_argument('csvfile', nargs='?', type=str, default='', help='CSV filename') +args = argparser.parse_args() + +# =========================================================================== +hashed_pw = bcrypt.hashpw(args.pw.encode('utf-8'), bcrypt.gensalt()) + +engine = create_engine('sqlite:///{}'.format(args.db), echo=False) +Base.metadata.create_all(engine) # Criate schema if needed +Session = sessionmaker(bind=engine) + +# --- start session --- +try: + session = Session() + + # add administrator + session.add(Student(id='0', name='Professor', password=hashed_pw)) + + # add students + if args.csvfile: + # from csv file if available + try: + csvreader = csv.DictReader(open(args.csvfile, encoding='iso-8859-1'), delimiter=';', quotechar='"', skipinitialspace=True) + except EnvironmentError: + print('Error: CSV file "{0}" not found.'.format(args.csvfile)) + session.rollback() + sys.exit(1) + else: + session.add_all([Student(id=r['N.º'], name=fix(r['Nome']), password=hashed_pw) for r in csvreader]) + elif args.demo: + # add a few fake students + fakes = [ + ['1915', 'Alan Turing'], + ['1938', 'Donald Knuth'], + ['1815', 'Ada Lovelace'], + ['1969', 'Linus Torvalds'], + ['1955', 'Tim Burners-Lee'], + ['1916', 'Claude Shannon'], + ['1903', 'John von Neumann'], + ] + session.add_all([Student(id=i, name=name, password=hashed_pw) for i,name in fakes]) + + session.commit() + +except Exception: + print('Error: Database already exists.') + session.rollback() + sys.exit(1) + +else: + n = session.query(Student).count() + print('New database created: {0}\n{1} user(s) inserted:'.format(args.db, n)) + + users = session.query(Student).order_by(Student.id).all() + print(' {0:8} - {1} (administrator)'.format(users[0].id, users[0].name)) + if n > 1: + print(' {0:8} - {1}'.format(users[1].id, users[1].name)) + if n > 3: + print(' ... ...') + if n > 2: + print(' {0:8} - {1}'.format(users[-1].id, users[-1].name)) + +# --- end session --- diff --git a/models.py b/models.py index d3f1a9c..62d7f8d 100644 --- a/models.py +++ b/models.py @@ -10,6 +10,8 @@ from sqlalchemy.orm import relationship Base = declarative_base() # --------------------------------------------------------------------------- +# Registered students +# --------------------------------------------------------------------------- class Student(Base): __tablename__ = 'students' id = Column(String, primary_key=True) @@ -17,69 +19,36 @@ class Student(Base): password = Column(String) # --- - tests = relationship('Test', back_populates='student') - questions = relationship('Question', back_populates='student') + answers = relationship('Answer', back_populates='student') def __repr__(self): - return 'Student:\n id: "{0}"\n name: "{1}"\n password: "{2}"'.format(self.id, self.name, self.password) + return f'''Student: + id: "{self.id}" + name: "{self.name}" + password: "{self.password}"''' # --------------------------------------------------------------------------- -class Test(Base): - __tablename__ = 'tests' - id = Column(Integer, primary_key=True) # auto_increment - ref = Column(String) - title = Column(String) # FIXME depends on ref and should come from another table... - grade = Column(Float) - state = Column(String) # ONGOING, FINISHED, QUIT, NULL - comment = Column(String) - starttime = Column(String) - finishtime = Column(String) - filename = Column(String) - student_id = Column(String, ForeignKey('students.id')) - - # --- - student = relationship('Student', back_populates='tests') - questions = relationship('Question', back_populates='test') - - def __repr__(self): - return 'Test:\n\ - id: "{}"\n\ - ref="{}"\n\ - title="{}"\n\ - grade="{}"\n\ - state="{}"\n\ - comment="{}"\n\ - starttime="{}"\n\ - finishtime="{}"\n\ - filename="{}"\n\ - student_id="{}"\n'.format(self.id, self.ref, self.title, self.grade, self.state, self.comment, self.starttime, self.finishtime, self.filename, self.student_id) - - +# Table with every answer given # --------------------------------------------------------------------------- -class Question(Base): - __tablename__ = 'questions' +class Answer(Base): + __tablename__ = 'answers' id = Column(Integer, primary_key=True) # auto_increment ref = Column(String) grade = Column(Float) starttime = Column(String) finishtime = Column(String) student_id = Column(String, ForeignKey('students.id')) - test_id = Column(String, ForeignKey('tests.id')) # --- - student = relationship('Student', back_populates='questions') - test = relationship('Test', back_populates='questions') + student = relationship('Student', back_populates='answers') def __repr__(self): - return 'Question:\n\ - id: "{}"\n\ - ref: "{}"\n\ - grade: "{}"\n\ - starttime: "{}"\n\ - finishtime: "{}"\n\ - student_id: "{}"\n\ - test_id: "{}"\n'.fotmat(self.id, self.ref, self.grade, self.starttime, self.finishtime, self.student_id, self.test_id) - + return '''Question: + id: "{self.id}" + ref: "{self.ref}" + grade: "{self.grade}" + starttime: "{self.starttime}" + finishtime: "{self.finishtime}" + student_id: "{self.student_id}"''' -# --------------------------------------------------------------------------- diff --git a/serve.py b/serve.py index 908130c..edc9386 100755 --- a/serve.py +++ b/serve.py @@ -11,6 +11,7 @@ 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 @@ -87,6 +88,11 @@ class LearnApp(object): 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') @@ -136,6 +142,7 @@ class WebApplication(tornado.web.Application): # ---------------------------------------------------------------------------- # Base handler common to all handlers. +# ---------------------------------------------------------------------------- class BaseHandler(tornado.web.RequestHandler): @property def learn(self): @@ -144,7 +151,10 @@ class BaseHandler(tornado.web.RequestHandler): def get_current_user(self): cookie = self.get_secure_cookie("user") if cookie: - return cookie.decode('utf-8') + user = cookie.decode('utf-8') + # FIXME if the cookie exists but user is not in learn.online, this will force new login and store new (duplicate?) cookie. is this correct?? + if user in self.learn.online: + return user # # ---------------------------------------------------------------------------- # class MainHandler(BaseHandler): @@ -179,6 +189,7 @@ class LoginHandler(BaseHandler): class LogoutHandler(BaseHandler): @tornado.web.authenticated def get(self): + self.learn.logout(self.current_user) self.clear_cookie('user') self.redirect(self.get_argument('next', '/')) @@ -251,8 +262,13 @@ class QuestionHandler(BaseHandler): # ---------------------------------------------------------------------------- def main(): - server = WebApplication() - server.listen(8080) + webapp = WebApplication() + http_server = tornado.httpserver.HTTPServer(webapp, ssl_options={ + "certfile": "certs/cert.pem", + "keyfile": "certs/key.pem" + }) + http_server.listen(8443) + try: print('--- start ---') tornado.ioloop.IOLoop.current().start() -- libgit2 0.21.2