from flask import Flask, redirect, flash, 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.') def user_link(user): b = html.Builder() with b.line(): if user is None: b("-") else: if g.org: b.a(href=app.url_for(web_org_user.__name__, user_id=user.id))(user.username) else: b(user.username) return b.root_tag 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") else: 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("/game/", methods=['GET']) def web_game(game_id): ses = db.get_session() game = ses.query(db.Game).filter_by(game_id=game_id).one_or_none() teams = ses.query(db.Team).filter_by(game_id=game_id).order_by(db.Team.team_id).all() if game is None: raise werkzeug.exceptions.NotFound() b = BasePage() with b.p().table(_class="data full"): with b.thead(): b.line().th()("Id") b.line().th()("User") if g.org: b.line().th()("Akce") for team in teams: with b.tr(): b.line().td()(team.team_id) b.line().td()(user_link(team.user),": ", team.name) if g.org: with b.td(): with b.div(_class="btn-group", role="group"): b.a(_class="btn btn-xs btn-primary", href=app.url_for(web_org_game_userchange.__name__, game_id=game.game_id, team_id=team.team_id))("Změnit uživatele") #b.a(_class="btn btn-xs btn-primary", href=app.url_for(web_game.__name__, game_id=g.game_id))("Detail") return b.print_file() @app.route("/org/games") def web_org_games(): games = db.get_session().query(db.Game).order_by(db.Game.game_id).all() b = BasePage() with b.p().table(_class="data full"): with b.thead(): b.line().th()("Id") b.line().th()("Kolo") b.line().th()("Akce") for g in games: with b.tr(): b.line().td()(g.game_id) with b.line().td(): if g.working_on_next_state: b.b()(g.current_round, "++") else: b(g.current_round) with b.td(): b.a(_class="btn btn-xs btn-primary", href=app.url_for(web_game.__name__, game_id=g.game_id))("Detail") return b.print_file() class GameUserchangeForm(FlaskForm): name = StringField("Jméno hry z pohledu týmu") set_no_user = SubmitField("Bez uživatele") submit_no_change = wtforms.SubmitField("Bez změny", render_kw={"style": "display: none"}) @app.route("/org/game//team//change_user", methods=['GET', 'POST']) def web_org_game_userchange(game_id, team_id): ses = db.get_session() game = ses.query(db.Game).filter_by(game_id=game_id).one_or_none() team_to_edit = ses.query(db.Team).filter_by(game_id=game_id, team_id=team_id).one_or_none() teams = ses.query(db.Team).filter_by(game_id=game_id).order_by(db.Team.team_id).all() users = db.get_session().query(db.User).order_by(db.User.id).all() if game is None or team_to_edit is None: raise werkzeug.exceptions.NotFound() teams_by_user = {u.id:[] for u in users} for t in teams: if t.user_id is not None: teams_by_user[t.user_id].append(t) form = GameUserchangeForm(obj=team_to_edit) if form.validate_on_submit(): team_to_edit.name = form.name.data if "submit_no_change" not in request.form: team_to_edit.user_id = None for u in users: if f"set_user_{u.id}" in request.form: team_to_edit.user_id = u.id try: ses.commit() except exc.IntegrityError: flash("Duplicitní přiřazení", 'danger') ses.rollback() else: flash("Uživatel změněn", 'success') return redirect(app.url_for(web_game.__name__, game_id=game_id)) b = BasePage() with b.form(action="", method="POST", _class="form form-horizontal", role="form"): b(form.csrf_token) b(form.submit_no_change) with b.div(_class="form-row"): b(jinja_mac.form_field(form.name, size=8)) with b.p().table(_class="data full"): with b.thead(): b.line().th()("Username") b.line().th()("Přiřazení") b.line().th()("Akce") for u in users: with b.tr(): b.line().td(user_link(u)) with b.td(): if len(teams_by_user[u.id]): with b.ul(): for t in teams_by_user[u.id]: b.li(f"{t.team_id} -> {t.name}") b.line().td().input(_class="btn btn-danger" if u.id == team_to_edit.user_id else "btn btn-primary", _id="set_participation_state", name=f"set_user_{u.id}", type="submit", value="Nastavit stav účasti") b(jinja_mac.form_field(form.set_no_user)) return b.print_file() @app.route("/org/users") def web_org_users(): users = db.get_session().query(db.User).order_by(db.User.id).all() b = BasePage() with b.p().table(_class="data full"): with b.thead(): b.line().th()("Username") b.line().th()("Hry") b.line().th()("Akce") for u in users: with b.tr(): b.line().td()(u.username) b.line().td() with b.td(): with b.div(_class="btn-group", role="group"): b.a(_class="btn btn-xs btn-primary", href=app.url_for(web_org_user.__name__, user_id=u.id))("Detail") return b.print_file() @app.route("/org/user/", methods=['GET', 'POST']) def web_org_user(user_id): user = db.get_session().query(db.User).filter_by(id=user_id).one_or_none() if not user: raise werkzeug.exceptions.NotFound() b = BasePage() b.line().h2("Uživatel ", user.username) with b.div(_class="btn-group", role="group"): with b.form(method="POST", _class="btn-group", action=app.url_for(web_org_user_su.__name__, user_id=user.id)): b.button(_class="btn btn-default", type="submit", name="su", value="yes")("Převtělit") return b.print_file() @app.route("/org/user//su", methods=['POST']) def web_org_user_su(user_id): user = db.get_session().query(db.User).filter_by(id=user_id).one_or_none() session['uid'] = user.id flash("Uživatel vtělen!") return redirect('/')