You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
250 lines
8.5 KiB
250 lines
8.5 KiB
from flask import Flask, redirect, flash, render_template, session, g, request, get_flashed_messages
|
|
from wtforms import Form, BooleanField, StringField, PasswordField, validators, SubmitField, IntegerField, DateTimeField
|
|
from wtforms.validators import ValidationError
|
|
from flask_wtf import FlaskForm
|
|
from flask_bootstrap import Bootstrap
|
|
import time
|
|
from datetime import datetime
|
|
from flask_sqlalchemy import SQLAlchemy
|
|
from sqlalchemy import exc, update
|
|
import hashlib
|
|
import bcrypt
|
|
import os
|
|
import werkzeug.exceptions
|
|
import wtforms
|
|
from wtforms.fields import EmailField
|
|
from wtforms.widgets import NumberInput
|
|
|
|
import hra.config as config
|
|
import hra.web.html as html
|
|
import hra.db as db
|
|
from hra.web import app
|
|
import hra.web.jinja_mac as jinja_mac
|
|
from hra.util import hash_passwd
|
|
|
|
@html.WrapAfterBuilder_decorator
|
|
def BasePage(b, content):
|
|
b._current_tag = html.Tag(b, "html", [])
|
|
b._root_tag = b._current_tag
|
|
with b.head():
|
|
b.title()("KSP hra")
|
|
b.meta(name="viewport", content="width=device-width, initial-scale=1.0")
|
|
b.link(rel="stylesheet", href=app.url_for('static', filename='bootstrap.min.css'), type='text/css', media="all")
|
|
b.link(rel="stylesheet", href=app.url_for('static', filename='ksp-mhd.css'), type='text/css', media="all")
|
|
b.link(rel="icon", type="image/png", sizes="16x16", href="static/favicon.ico")
|
|
b.link(rel="shortcut icon", href=app.url_for('static', filename='img/favicon.ico'))
|
|
with b.body() as body:
|
|
with b.header(_class=f"flavor-{config.WEB_FLAVOR}"):
|
|
with b.div(_class="content"):
|
|
with b.a(href="/", title="Na hlavní stránku"):
|
|
b.img(src=app.url_for('static', filename='hippo.png'), style="width: 60px;height: auto;", alt="KSP")
|
|
b.h1()("Hra na soustředění KSP")
|
|
with b.div(id="nav-wrapper"):
|
|
with b.nav(id="main-menu", _class="content"):
|
|
for item in g.menu:
|
|
b.a(href=item.url)(item.name)
|
|
if g.user:
|
|
b.a(_class="right", href="/")(f"Přihlášen: {g.user.username}")
|
|
b.a(_class="right", href="/logout")(f"Odhlásit")
|
|
else:
|
|
b.a(_class="right", href="/login")(f"Přihlásit")
|
|
b.a(_class="right", href="/registration")(f"Registrovat")
|
|
with b.main():
|
|
messages = get_flashed_messages(with_categories=True)
|
|
if messages:
|
|
for category, message in messages:
|
|
if category == "message":
|
|
category = warning
|
|
b.div(_class=f"alert alert-{category}", role="alert")(message)
|
|
b(*content)
|
|
|
|
|
|
class OptionalIntField(wtforms.IntegerField):
|
|
widget = NumberInput()
|
|
|
|
def process_formdata(self, valuelist):
|
|
self.data = None
|
|
if valuelist:
|
|
if valuelist[0]:
|
|
try:
|
|
self.data = int(valuelist[0])
|
|
except ValueError:
|
|
raise wtforms.ValidationError('Nejedná se o číslo.')
|
|
|
|
|
|
|
|
|
|
class RegistrationForm(FlaskForm):
|
|
username = StringField('Jméno týmu', [validators.Length(min=2, max=25), validators.DataRequired()])
|
|
captcha = StringField('Captcha', validators=[validators.DataRequired()])
|
|
passwd = PasswordField('Heslo', [
|
|
validators.DataRequired(),
|
|
validators.EqualTo('confirm', message='Passwords must match')
|
|
])
|
|
confirm = PasswordField('Heslo znovu', validators=[validators.DataRequired()])
|
|
submit = SubmitField("Založit")
|
|
|
|
def validate_captcha(form, field):
|
|
if field.data != config.CAPTCHA:
|
|
raise ValidationError("Chyba!")
|
|
|
|
|
|
class LoginForm(FlaskForm):
|
|
username = StringField('Jméno týmu', [validators.DataRequired()])
|
|
passwd = PasswordField('Heslo', [validators.DataRequired()])
|
|
submit = SubmitField("Přihlásit")
|
|
|
|
|
|
|
|
@app.route("/registration", methods=['GET', 'POST'])
|
|
def registration():
|
|
f = RegistrationForm()
|
|
if f.validate_on_submit():
|
|
u = db.User(org=False, username=f.username.data, passwd=hash_passwd(f.passwd.data))
|
|
u.gen_token()
|
|
try:
|
|
db.get_session().add(u)
|
|
db.get_session().commit()
|
|
except exc.IntegrityError:
|
|
flash("Uživatelské jméno již existuje")
|
|
return render_template('registration.html', form=f)
|
|
flash("Přidán nový uživatel.", 'success')
|
|
return redirect("login")
|
|
|
|
b = BasePage()
|
|
b(jinja_mac.quick_form(f, form_type='horizontal'))
|
|
return b._print_file()
|
|
|
|
@app.route("/login", methods=['GET', 'POST'])
|
|
def login():
|
|
f = LoginForm()
|
|
if f.validate_on_submit():
|
|
p_hash=hash_passwd(f.passwd.data)
|
|
user = db.get_session().query(db.User).filter_by(username=f.username.data).one_or_none()
|
|
print(user, p_hash)
|
|
if user and user.passwd == p_hash:
|
|
session.clear()
|
|
session['uid'] = user.id
|
|
flash("Přihlášení hotovo.", 'success')
|
|
return redirect("/")
|
|
flash("Chybné jméno nebo heslo.", 'danger')
|
|
|
|
b = BasePage()
|
|
b(jinja_mac.quick_form(f, form_type='horizontal'))
|
|
return b._print_file()
|
|
|
|
|
|
@app.route("/logout")
|
|
def logout():
|
|
session.clear()
|
|
return redirect('/')
|
|
|
|
|
|
@app.template_filter()
|
|
def none_as_minus(x):
|
|
return x if x is not None else '-'
|
|
|
|
|
|
@app.template_filter()
|
|
def round_points(x):
|
|
if x is None:
|
|
return None
|
|
return round(x,3)
|
|
|
|
|
|
@app.template_filter()
|
|
def print_time(t):
|
|
if t == None:
|
|
return "-"
|
|
return ("-" if t<0 else "") + str(abs(t)//1000//60) + ":" + ("00{0:d}".format(abs(t)//1000%60))[-2:]
|
|
|
|
|
|
|
|
|
|
@app.route("/", methods=['GET', 'POST'])
|
|
def web_index():
|
|
b = BasePage()
|
|
if g.user:
|
|
with b.p():
|
|
b(f"Váš token je: {g.user.token}")
|
|
return b._print_file()
|
|
|
|
|
|
@app.route("/org/users")
|
|
def web_users():
|
|
users = db.get_session().query(db.User).all()
|
|
return render_template("org_users.html", users=users)
|
|
|
|
@app.route("/org/user/<int:user_id>", methods=['GET', 'POST'])
|
|
def web_orgf_user(user_id):
|
|
user = db.get_session().query(db.User).filter_by(id=user_id).one_or_none()
|
|
f = FindingForm()
|
|
del f.user
|
|
if f.validate_on_submit():
|
|
f.fill_empty()
|
|
find = Findings(user=user.id, code=f.f_code, time=f.f_time, round=get_round_id())
|
|
db.get_session().add(find)
|
|
db.get_session().commit()
|
|
flash(f"Kód {find.code}… přijat", 'success')
|
|
f.code.data = ""
|
|
return redirect(f"/org/user/{user_id}")
|
|
|
|
if not user:
|
|
raise werkzeug.exceptions.NotFound()
|
|
calc_point(user)
|
|
findings = db.get_session().query(Findings).filter_by(user=user.id, round=get_round_id()).order_by(Findings.time).all()
|
|
return render_template("org_user.html", user=user, findings=findings, form=f)
|
|
|
|
@app.route("/org/admin", methods=['GET', 'POST'])
|
|
def web_admin():
|
|
obj_round = get_round()
|
|
f_round = FormRound(obj=obj_round, prefix="r")
|
|
if f_round.validate_on_submit():
|
|
f_round.populate_obj(obj_round)
|
|
db.get_session().commit()
|
|
|
|
return render_template("org_admin.html", f_round=f_round)
|
|
|
|
@app.route("/org/act", methods=['GET', 'POST'])
|
|
def web_act():
|
|
obj_act_round = db.get_session().query(ActualRound).one()
|
|
f_act_round = FormActRound(obj=obj_act_round, prefix="act")
|
|
if f_act_round.validate_on_submit():
|
|
f_act_round.populate_obj(obj_act_round)
|
|
db.get_session().commit()
|
|
return render_template("org_act.html", f_act_round=f_act_round)
|
|
|
|
|
|
class SuForm(FlaskForm):
|
|
username = StringField('Uživatel')
|
|
time_move = OptionalIntField('Časový posun')
|
|
round = OptionalIntField('Kolo')
|
|
submit = SubmitField("Změnit")
|
|
|
|
def validate_username(form, field):
|
|
form.f_user = db.get_session().query(db.User).filter_by(username=field.data).one_or_none()
|
|
if form.f_user == None and field.data:
|
|
raise ValidationError("Uživatel neexistuje.")
|
|
|
|
|
|
|
|
|
|
@app.route("/org/su", methods=['GET', 'POST'])
|
|
def web_su():
|
|
f = SuForm()
|
|
if not f.is_submitted():
|
|
f.username.data = g.user.username
|
|
|
|
if f.validate_on_submit():
|
|
if not f.f_user or session['uid'] != f.f_user.id:
|
|
session['uid'] = f.f_user.id if f.f_user else None
|
|
flash("Uživatel vtělen!")
|
|
if f.time_move.data is not None:
|
|
session['time_move'] = f.time_move.data * 1000
|
|
flash("Čas vtělen!")
|
|
if f.round.data is not None:
|
|
session['round'] = f.round.data
|
|
flash("Kolo vtěleno!")
|
|
return redirect('/')
|
|
|
|
return render_template("org_su.html", f=f)
|
|
|