summaryrefslogtreecommitdiff
path: root/web/kpov_judge/kpov_judge.py
diff options
context:
space:
mode:
Diffstat (limited to 'web/kpov_judge/kpov_judge.py')
-rwxr-xr-xweb/kpov_judge/kpov_judge.py289
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')