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.
 
 
 
 
 

347 lines
11 KiB

from flask import Flask, redirect, flash, render_template, session, g, request
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
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.')
import logging
logging.basicConfig()
logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
static_dir = os.path.abspath('static')
app = Flask(__name__, static_folder=static_dir)
app.config.from_object(config)
Bootstrap(app)
db = SQLAlchemy(app)
class NeedLoginError(werkzeug.exceptions.Forbidden):
description = 'Need to log in'
class MenuItem:
url: str
name: str
def __init__(self, url: str, name: str):
self.url = url
self.name = name
def init_request():
path = request.path
# XXX: Když celá aplikace běží v adresáři, request.path je relativní ke kořeni aplikace, ne celého webu
if path.startswith('/static/') or path.startswith('/assets/'):
# Pro statické soubory v development nasazení nepotřebujeme nastavovat
# nic dalšího (v ostrém nasazení je servíruje uwsgi)
return
if 'uid' in session:
user = db.session.query(Users).filter_by(id=session['uid']).first()
else:
user = None
path = request.path
if path.startswith('/org/'):
if not user:
raise werkzeug.exceptions.Forbidden()
if not user.org:
raise werkzeug.exceptions.Forbidden()
g.user = user
g.menu = [
MenuItem('/', "Domů"),
MenuItem('/bonuses', "Bonusy"),
]
if g.user and g.user.org:
g.menu += [
MenuItem('/org/ranking', "Výsledky"),
MenuItem('/org/act', "Aktuální kolo"),
MenuItem('/org/admin', "Admin"),
MenuItem('/org/su', "Vtělování se"),
MenuItem('/org/users', "Uživatelé"),
]
else:
g.menu += [
MenuItem('/submitted', "Odevzdané kódy"),
]
app.before_request(init_request)
class Users(db.Model):
id = db.Column(db.Integer, primary_key=True)
org = db.Column(db.Boolean)
username = db.Column(db.String(80), unique=True, nullable=False)
passwd = db.Column(db.String(80), nullable=False)
def __repr__(self):
return '<User %r>' % self.username
def code_points(code, time):
r = 1
if code in bonus:
for b in bonus[code]:
if not b.is_out(time):
r = max(r, b.eval(time))
return r
class Findings(db.Model):
id = db.Column(db.Integer, primary_key=True)
user = db.Column(db.Integer, db.ForeignKey('users.id'))
round = db.Column(db.Integer)
code = db.Column(db.String(10))
time = db.Column(db.Integer)
delete = db.Column(db.Boolean, nullable=False, default=False)
def points(self):
return code_points(self.code, self.time)
class ActualRound(db.Model):
id = db.Column(db.Integer, primary_key=True)
class Round(db.Model):
id = db.Column(db.Integer, primary_key=True)
start_time = db.Column(db.DateTime)
# db.create_all()
# if db.session.query(ActualRound).one_or_none() is None:
# db.session.add(ActualRound(id=0))
# db.session.commit()
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")
def hash_passwd(a):
salt = b'$2b$12$V2aIKSJC/uozaodwYnQX3e'
hashed = bcrypt.hashpw(a.encode('utf-8'), salt)
return hashed.decode('us-ascii')
@app.route("/registration", methods=['GET', 'POST'])
def registration():
f = RegistrationForm()
if f.validate_on_submit():
u = Users(org=False, username=f.username.data, passwd=hash_passwd(f.passwd.data))
try:
db.session.add(u)
db.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")
return render_template('registration.html', form=f)
@app.route("/login", methods=['GET', 'POST'])
def login():
f = LoginForm()
if f.validate_on_submit():
p_hash=hash_passwd(f.passwd.data)
user = db.session.query(Users).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')
return render_template('login.html', form=f)
@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():
return render_template('index.html')
@app.route("/org/users")
def web_users():
users = db.session.query(Users).all()
return render_template("org_users.html", users=users)
@app.route("/org/user/<int:user_id>", methods=['GET', 'POST'])
def web_org_user(user_id):
user = db.session.query(Users).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.session.add(find)
db.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.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.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.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.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.session.query(Users).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)
from hra.web.html import *
@app.route("/test", methods=['GET', 'POST'])
def test():
b = HtmlBuilder()
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="stylesheet", href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css", integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm", crossorigin="anonymous")
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'))
# b.link(rel="stylesheet", href="https://mo.mff.cuni.cz/osmo/assets/a373a8f2/mo.css", type='text/css', media="all")
with b.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()(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")
return b._print_file()