diff options
Diffstat (limited to 'web/kpov_judge/kpov_judge.py')
-rwxr-xr-x | web/kpov_judge/kpov_judge.py | 289 |
1 files changed, 289 insertions, 0 deletions
diff --git a/web/kpov_judge/kpov_judge.py b/web/kpov_judge/kpov_judge.py new file mode 100755 index 0000000..47e413e --- /dev/null +++ b/web/kpov_judge/kpov_judge.py @@ -0,0 +1,289 @@ +#!/usr/bin/env python3 + +import collections +import datetime +import json +import random +import settings +import traceback +import uuid + +from kpov_draw_setup import draw_setup +import kpov_util + +import pymongo +import flask +from flask import Flask, g, session, redirect, url_for, abort, render_template, flash, app, request, Response +from flask.ext.babel import Babel, gettext, ngettext, format_datetime, _ +import jinja2 + +app = Flask(__name__) +app.config.from_object(settings) +babel = Babel(app) + +@babel.localeselector +def get_locale(): + # terrible hack, should store as user preference in the DB + if '/en/' in request.path: + return 'en' + if '/si/' in request.path: + return 'sl' + return request.accept_languages.best_match(['sl', 'en']) + + +@app.before_request +def before_request(): + g.db = pymongo.MongoClient(app.config['DB_URI']).get_default_database() + + +@app.route('/') +@app.route('/classes/') +def index(): + student_id = flask.app.request.environ.get('REMOTE_USER', 'Nobody') + classes = g.db.classes.find({}, {'class_id': 1, 'name': 1}).sort('class_id') + return render_template('index.html', student_id=student_id, classes=classes) + + +@app.route('/classes/<class_id>/') +def class_tasks(class_id): + student_id = flask.app.request.environ.get('REMOTE_USER', 'Nobody') + clas = g.db.classes.find_one({'class_id': class_id}) + tasks = g.db.tasks.find({'class_id': class_id}, {'task_id': 1}).sort('task_id') + if tasks is not None: + task_list = [i['task_id'] for i in tasks] + else: + task_list = [] + return render_template('class_tasks.html', student_id=student_id, tasks=task_list, clas=clas) + + +@app.route('/tasks/<class_id>/<task_id>/<lang>/setup.<ending>', methods=['GET']) +def setup_svg(class_id, task_id, lang, ending): + db = g.db + fmt, mimetype = { + 'svg':('svg', 'image/svg+xml'), + 'png':('png', 'image/png'), + }[ending] + networks = list(db.networks.find({'class_id': class_id, 'task_id': task_id})) + computers = list(db.computers_meta.find({'class_id': class_id, 'task_id': task_id})) + return Response(draw_setup(computers, networks, format=fmt, + icon_path=app.config['STATIC_DIR']), + mimetype=mimetype) + + +@app.route('/tasks/<class_id>/<task_id>/task.py') +def task_source(class_id, task_id): + db = g.db + try: + return db.tasks.find_one({'class_id': class_id, 'task_id': task_id})['source'] + except: + return '' + + +@app.route('/tasks/<class_id>/<task_id>/task.html') +def task_html(class_id, task_id): + return render_template('task.html', task=task_source(class_id, task_id)) + + +def get_params(class_id, task_id, student_id, db): + try: + meta = db.task_params_meta.find_one({'class_id': class_id, 'task_id': task_id})['params'] + except Exception: + return {'mama': 'ZAKVAJ?'}, {'mama': {'public': True}} + + params = db.task_params.find_one({'class_id': class_id, 'task_id': task_id, 'student_id': student_id}) + if params is None or 'params' not in params: # TODO try with $exists: params or smth. + try: + gen_params_source = db.gen_params.find_one({'class_id': class_id, 'task_id': task_id})['source'] + gen_params_code = compile(gen_params_source, 'generator.py', 'exec') + d = {} + exec(gen_params_code, globals(), d) + params = d['gen_params'](student_id, meta) + db.task_params.update({'class_id': class_id, 'task_id': task_id, 'student_id': student_id}, + {'$set': {'params': params}}, upsert=True) + params = d['gen_params'](student_id, meta) # TODO this is repeated, is it necessary? + for computer in db.computers_meta.find({'class_id': class_id, 'task_id': task_id}): + try: + name = computer.pop('name') + del computer['_id'] + del computer['task_id'] + except Exception: + pass + db.student_computers.update({'class_id': class_id, 'task_id': task_id, 'student_id': student_id, 'name': name}, + {'$set': computer}, upsert=True) + except Exception as e: + meta = {'crash': {'public': True}} + params = {'crash': "Parameter creator crashed or missing:\n{}".format( + traceback.format_exc())} + else: + params = params['params'] + return params, meta + + +@app.route('/tasks/<class_id>/<task_id>/') +def task_lang_redirect(class_id, task_id): + return redirect(url_for('task_greeting', class_id=class_id, task_id=task_id, lang=app.config['DEFAULT_LANG'])) + + +@app.route('/tasks/<class_id>/<task_id>/<lang>/howto/') +def task_howto(class_id, task_id, lang): + db = g.db + return db.howtos.find_one({'class_id': class_id, 'task_id': task_id, 'lang': lang}).get('text', '') + + +@app.route('/tasks/<class_id>/<task_id>/<lang>/images/<fname>') +def task_image(class_id, task_id, lang, fname): + db = g.db + return db.howto_images.find_one({'class_id': class_id, 'task_id': task_id, 'fname': fname}).get('data', '') + + +@app.route('/tasks/<class_id>/<task_id>/<lang>/') +def task_greeting(class_id, task_id, lang): + student_id = flask.app.request.environ.get('REMOTE_USER', 'Nobody') + db = g.db + # generate the parameters as soon as the student visits + params, meta = get_params(class_id, task_id, student_id, db) + instr_ok = True + try: + instructions = db.task_instructions.find_one({'class_id': class_id, 'task_id': task_id}) + instructions = instructions.get(lang, instructions[app.config['DEFAULT_LANG']]) + except Exception: + try: + instructions = list(instructions.values())[0] + except Exception as e: + instructions = str(e) + instr_ok = False + if instr_ok: + try: + public_params = [] + for k, v in meta.items(): + if v.get('public', False): + public_params += [{ + 'name': k, + 'value': params.get(k), + 'description': v.get('descriptions', {}).get(lang) + }] + except Exception as e: + instructions = str(e) + + computer_list = list(db.student_computers.find({'class_id': class_id, 'task_id': task_id, 'student_id': student_id})) + + backing_files = collections.defaultdict(set) + for computer in computer_list: + if 'disk_urls' not in computer: + continue + for name, disk in computer['disk_urls'].items(): + for fmt in disk['formats']: + backing_files[fmt] |= set(disk[fmt][1:]) + + if request.args.get('narediStack', 'false') == 'true': + #db.student_tasks.update({'task_id': task_id, 'student_id': student_id}, {'$set': {'create_openstack': True}}, upsert = True) + openstackCreated = False # Spremeni na True, ko odkomentiras zgornjo vrstico. + else: + if db.student_tasks.find({'class_id': class_id, 'task_id': task_id, 'student_id': student_id, 'openstack_created': True}).count() > 0: + openstackCreated = True + elif db.student_tasks.find({'class_id': class_id, 'task_id': task_id, 'student_id': student_id, 'create_openstack': True}).count() > 0: + openstackCreated = True + else: + openstackCreated = False + + try: + result = db.results.find_one( + {'$query': {'class_id': class_id, 'task_id': task_id, 'student_id': student_id}, + '$orderby': collections.OrderedDict([('result', -1), ('time', 1)])}, + {'result': 1, 'status': 1, 'hints': 1, 'time': True, '_id': 0}) + result['time'] = format_datetime(result['time']) + print(result) + except Exception: + result = None + + return render_template('task_greeting.html', + disk_base_url='/'.join([app.config['STUDENT_DISK_URL'], student_id, class_id, task_id, '']), + class_id=class_id, + task_id=task_id, + computers=sorted((c for c in computer_list if 'disk_urls' in c), key=lambda c: c['name']), + backing_files={fmt: sorted(images) for fmt, images in backing_files.items()}, + lang='sl' if lang == 'si' else lang, # TODO s/si/sl in all tasks (and maybe elsewhere) + openstack=openstackCreated, + instructions=jinja2.Template(instructions), + params=public_params, + result=result, + **{p['name']: p['value'] for p in public_params}) + + +@app.route('/tasks/<class_id>/<task_id>/token.json') +def get_token(class_id, task_id): + db = g.db + student_id = flask.app.request.environ.get('REMOTE_USER', 'Nobody') + token = str(uuid.uuid4()) + db.task_params.update({'class_id': class_id, 'task_id': task_id, 'student_id': student_id}, + {'$set': {'token': token}}, upsert=True) + return json.dumps({'token': token}) + + +@app.route('/tasks/<class_id>/<task_id>/params.json', methods=['POST']) +def params_json(class_id, task_id): + db = g.db + token = flask.app.request.form['token'] + record = db.task_params.find_one({'class_id': class_id, 'task_id': task_id, 'token': token}) + if not record: + return json.dumps({}) + params, meta = get_params(record['class_id'], record['task_id'], record['student_id'], db) + shown_params = {} + for name, param in params.items(): + if meta.get(name, {'public': False})['public']: + shown_params[name] = param + return json.dumps(shown_params) + + +@app.route('/tasks/<class_id>/<task_id>/results.json', methods=['POST']) +def results_json(class_id, task_id): + db = g.db + token = flask.app.request.form.get('token', '') + task = db.task_params.find_one({'class_id': class_id, 'task_id': task_id, 'token': token}) + if not task: + return json.dumps({'result': 0, 'hints': ['invalid token'], 'status': 'NOT OK'}) + + params = task['params'] + if params is None: + return json.dumps({'result': 0, 'hints': ['no parameters found for task'], 'status': 'NOT OK'}) # no such task + + results = json.loads(flask.app.request.form['results']) + user_params = json.loads(flask.app.request.form['params']) + + meta = db.task_params_meta.find_one({'task_id': task_id}) + if meta is None: + meta = {} + else: + meta = meta['params'] + for param_name, param_meta in meta.items(): + if param_meta.get('w', False) and param_name in user_params: + params[param_name] = user_params[param_name] + + # hack to get token into task_check function + # TODO rethink the API + params['token'] = token + try: + task_check_source = db.task_checkers.find_one({'class_id': class_id, 'task_id': task_id})['source'] + d = {} + exec(compile(task_check_source, 'checker.py', 'exec'), globals(), d) + res, hints = d['task_check'](collections.defaultdict(str, results), params) + except Exception as e: + hints = ["Checker died: " + str(e)] + res = 0 + if (isinstance(res, int) or isinstance(res, float)) and res > 0: + res_status = 'OK' + else: + res_status = 'NOT OK' + + db.results.insert({ + 'class_id': class_id, 'task_id': task_id, + 'result': res, 'hints': hints, 'status': res_status, + 'student_id': task['student_id'], + 'response': results, + 'time': datetime.datetime.now() + }) + return json.dumps({'result': res, 'hints': hints, 'status': res_status}) + + +if __name__ == '__main__': + app.run(host='0.0.0.0') |