serve.py 6.29 KB
#!/usr/bin/env python3.4
# -*- coding: utf-8 -*-

# The program runs a web server where students login to answer a sequence of questions.
# Their grades are automatically calculated and stored in a sqlite3 database.


import cherrypy
from mako.lookup import TemplateLookup
import sqlite3
import yaml
import argparse
from datetime import datetime
import os.path
from myauth import AuthController, require

import test

mylookup = TemplateLookup(directories=['templates'], input_encoding='utf-8')

# get results from previous tests
def allgrades(db, uid):
    conn = sqlite3.connect(db)
    grades = conn.execute('SELECT test_id,grade,finish_time FROM tests WHERE student_id==?', [uid]).fetchall()
    conn.commit()
    conn.close()
    return grades


# ============================================================================
#   Classes that respond to HTTP
# ============================================================================
class Root(object):

    def __init__(self, testconf):
        '''The values defined here are common to all students.
        Data is stored in the student's respective sessions.'''
        self.testconf = testconf
        self.auth = AuthController(database=testconf['database'])

    # --- DEFAULT ------------------------------------------------------------
    # any path, e.g. /xpto/aargh is redirected to the test
    # (and possibly through the login first)
    @cherrypy.expose
    def default(self, *args):
        raise cherrypy.HTTPRedirect('/test')

    @cherrypy.expose
    def results(self):
        '''Show list of students'''
        conn = sqlite3.connect(self.testconf['database'])

        cmd = 'SELECT student_id,name,grade FROM students INNER JOIN tests ON students.number=tests.student_id WHERE test_id==? ORDER BY grade DESC;'
        results = conn.execute(cmd, [self.testconf['ref']]).fetchall()
        conn.commit()
        conn.close()

        print(str(results))
        template = mylookup.get_template('results.html')
        return template.render(results=results)


    # --- SEQUENCE------------------------------------------------------------
    # @cherrypy.expose
    # @require()
    # def question(self):
    #     uid = cherrypy.session.get('userid')
    #     name = cherrypy.session.get('name')
    #     t = cherrypy.session.get('test', None)
    #     if t is None:
    #         cherrypy.session['test'] = t = test.Test(self.testconf)
    #         t['number'] = uid
    #         t['name'] = name
    #         t['current_question'] = 0
    #     else:
    #         t['current_question'] += 1

    #     # Generate question
    #     template = mylookup.get_template('question.html')
    #     return template.render(t=t)


    # --- TEST ---------------------------------------------------------------
    @cherrypy.expose
    @require()
    def test(self):
        # Get student number and assigned questions from current session.
        # If it's the first time, create instance of the test and register the
        # time.
        uid = cherrypy.session.get('userid')
        name = cherrypy.session.get('name')
        t = cherrypy.session.get('test', None)
        if t is None:
            cherrypy.session['test'] = t = test.Test(self.testconf)
            t['number'] = uid
            t['name'] = name

        # Generate question
        template = mylookup.get_template('test.html')
        return template.render(t=t, questions=t['questions'])

    # --- CORRECT ------------------------------------------------------------
    @cherrypy.expose
    @require()
    def correct(self, **kwargs):
        '''Receives a dictionary {'question_id': 'answer', ...} submited by
        the student, corrects and saves the answers, computes the final grade
        of the test and stores in the database.'''

        # if there is no test, redirect to the test
        t = cherrypy.session.get('test')
        if t is None:
            raise cherrypy.HTTPRedirect('/test')

        # each question that is marked to be classified must have an answer.
        # ans contains the answers to be corrected
        ans = {}
        for q in t['questions']:
            if 'answered-' + q['ref'] in kwargs:
                ans[q['ref']] = kwargs.get(q['ref'], None)

        # store the answers provided by the student and correct the test
        t.update_answers(ans)
        t.correct()
        if t['save_answers']:
            t.save_json(self.testconf['answers_dir'])
        t.save_database(self.testconf['database'])
        grades = allgrades(self.testconf['database'], t['number'])

        # ---- Expire session ----
        cherrypy.lib.sessions.expire()  # session coockie expires client side
        cherrypy.session['userid'] = cherrypy.request.login = None
        cherrypy.log.error('Student %s terminated with grade = %.2f points.' %
                           (t['number'], t['grade']), 'APPLICATION')

        # ---- Show result to student ----
        template = mylookup.get_template('grade.html')
        return template.render(t=t, allgrades=grades)

# ============================================================================
def parse_arguments():
    argparser = argparse.ArgumentParser(description='Server for online tests. Enrolled students and tests have to be previously configured. Please read the documentation included with this software before running the server.')
    argparser.add_argument('--server', default='conf/server.conf', type=str, help='server configuration file')
    argparser.add_argument('testfile', type=str, help='test in YAML format.')
    return argparser.parse_args()

# ============================================================================
if __name__ == '__main__':
    # --- parse command line arguments and build base test
    arg = parse_arguments()
    testconf = test.read_configuration(arg.testfile)

    print('=' * 79)
    print('- Title:      %s' % testconf['title'])
    print('- StudentsDB: %s' % testconf['database'])
    print('- Questions: ', ', '.join(testconf['files']))
    print('    (%i questions read)' % len(testconf['questions']))
    print('-' * 79)
    print('- Starting server...')

    # --- controller
    root = Root(testconf)

    # --- Mount and run server.
    cherrypy.quickstart(root, '/', config=arg.server)
    cherrypy.log.error('Terminated OK ------------------------', 'APPLICATION')
    print('\n- Server terminated OK')
    print('=' * 79)