diff --git a/server/bin/control_game b/server/bin/control_game index a0fe2bc..203086e 100755 --- a/server/bin/control_game +++ b/server/bin/control_game @@ -7,8 +7,6 @@ import argparse from sqlalchemy import exc, update import sys -g = db.Game(game_mode="", configuration={}, teams_count=1) - parser = argparse.ArgumentParser() parser.add_argument("game_id") parser.add_argument("--step", action="store_true") diff --git a/server/bin/create_game b/server/bin/create_game index 9e75e1d..e3507f5 100755 --- a/server/bin/create_game +++ b/server/bin/create_game @@ -6,20 +6,32 @@ import hra.lib as lib import sys from sqlalchemy import exc, update +ses = db.get_session() mode = "occupy" teams_count = 6 -configuration = {} +configuration = { + "teams_width": 2, + "teams_height": 3, + "width_per_team": 10, + "height_per_team": 10, +} g = db.Game(game_mode=mode, configuration=configuration, teams_count=teams_count) -db.get_session().add(g) -db.get_session().commit() +ses.add(g) +ses.commit() + +g.lock() s = db.State(game_id=g.game_id, round=0, state=g.get_logic().zero_state()) +ses.add(s) + +for i in range(teams_count): + t = db.Team(team_id=i, game_id=g.game_id, name="") + ses.add(t) -db.get_session().add(s) g.current_round = 0 g.working_on_next_state = False -db.get_session().commit() +ses.commit() print(f"Přidána hra {g.game_id}. ") diff --git a/server/hra/db.py b/server/hra/db.py index 1e28fac..8467c58 100644 --- a/server/hra/db.py +++ b/server/hra/db.py @@ -78,7 +78,25 @@ class Game(Base): return hra.game.logic_by_mode[self.game_mode](self.teams_count, self.configuration) def lock(self) -> 'Game': - return get_session().query(Game).filter_by(game_id=self.game_id).with_for_update().first() + ses = get_session() + ses.expire_all() + return ses.query(Game).filter_by(game_id=self.game_id).with_for_update().first() + + +class Team(Base): + __tablename__ = 'base' + __table_args__ = ( + UniqueConstraint('game_id', 'team_id'), + UniqueConstraint('user_id', 'name'), + ) + + game_id = Column(Integer, ForeignKey('games.game_id'), nullable=False, primary_key=True) + team_id = Column(Integer, nullable=False, primary_key=True) + user_id = Column(Integer, ForeignKey('users.id'), nullable=True) + name = Column(String(80), nullable=False) + + game = relationship('Game', primaryjoin='Team.game_id == Game.game_id') + user = relationship('User', primaryjoin='Team.user_id == User.id') class State(Base): diff --git a/server/hra/web/__init__.py b/server/hra/web/__init__.py index f4548a8..7ee6154 100644 --- a/server/hra/web/__init__.py +++ b/server/hra/web/__init__.py @@ -70,22 +70,18 @@ def init_request(): if not user or not user.org: raise werkzeug.exceptions.Forbidden() g.user = user + g.org = g.user and g.user.org g.menu = [ MenuItem('/', "Domů"), - MenuItem('/bonuses', "Bonusy"), ] - if g.user and g.user.org: + if g.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é"), + MenuItem(app.url_for(pages.web_org_games.__name__), "Hry"), + MenuItem(app.url_for(pages.web_org_users.__name__), "Uživatelé") ] else: g.menu += [ - MenuItem('/submitted', "Odevzdané kódy"), ] @@ -95,5 +91,5 @@ app.before_request(init_request) -import hra.web.pages +import hra.web.pages as pages import hra.web.api diff --git a/server/hra/web/api.py b/server/hra/web/api.py index d994d7a..12e7449 100644 --- a/server/hra/web/api.py +++ b/server/hra/web/api.py @@ -23,17 +23,18 @@ def args_get(name, type, optional=False, default=None): def get_context(): if g.user is None: raise NeedLoginError - game_id = args_get("game", int, True, 1) - team_id = args_get('team', int, True, 0) - game = db.get_session().query(db.Game).filter_by(game_id=game_id).first() - if game is None: + game_name = args_get("game", str, True, "main") + team = db.get_session().query(db.Team).filter_by(user_id=g.user.id, name=game_name).one_or_none() + if team is None: raise werkzeug.exceptions.NotFound("No such game") - return game, team_id + return team @app.route("/api/state", methods=['GET']) def api_state(): - game, team_id = get_context() + team = get_context() + game = team.game + team_id = team.team_id state = game.current_state() if state is None: return json.dumps({ @@ -53,8 +54,9 @@ def api_action(): return json.dumps({ "status": "too-late", }) - - game, team_id = get_context() + team = get_context() + game = team.game + team_id = team.team_id j = request.get_json() state = game.current_state() round_id = args_get('round', int) diff --git a/server/hra/web/html.py b/server/hra/web/html.py index 6604e74..4a9e26e 100644 --- a/server/hra/web/html.py +++ b/server/hra/web/html.py @@ -95,6 +95,17 @@ class Bucket: self.builder.current_tag = self.before_tag self.before_tag = None + def serialize_append_to_list(self, out, indent): + for i in self.content: + if isinstance(i, Bucket): + i.serialize_append_to_list(out, indent) + elif isinstance(i, Markup): + for j in i.__html__().split("\n"): + out.append(indent_str(indent, j)) + else: + for j in escape(str(i)).split("\n"): + out.append(indent_str(indent, j)) + class Tag(Bucket): name: str @@ -114,19 +125,7 @@ class Tag(Bucket): def serialize_append_to_list(self, out, indent): if self.is_paired: out.append(indent_str(indent, f"<{escape_tag_name(self.name)} {self.format_attributes()}>")) - if indent is not None: - indent += 1 - for i in self.content: - if isinstance(i, Bucket): - i.serialize_append_to_list(out, indent) - elif isinstance(i, Markup): - for j in i.__html__().split("\n"): - out.append(indent_str(indent, j)) - else: - for j in escape(str(i)).split("\n"): - out.append(indent_str(indent, j)) - if indent is not None: - indent -= 1 + super().serialize_append_to_list(out, indent + 1 if indent is not None else None) out.append(indent_str(indent, f"")) else: out.append(indent_str(indent, f"<{escape_tag_name(self.name)} {self.format_attributes()} />")) @@ -134,21 +133,21 @@ class Tag(Bucket): class Line(Bucket): def serialize_append_to_list(self, out, indent): - out.append(indent_str(indent,"")[:-1]) - for i in self.content: - if isinstance(i, Bucket): - i.serialize_append_to_list(out, None) - elif isinstance(i, Markup): - out.append(i.__html__()) - out.append(escape(str(i))) - out.append("\n") + if indent is None: + super().serialize_append_to_list(out, None) + else: + out.append(indent_str(indent,"")[:-1]) + super().serialize_append_to_list(out, None) + out.append("\n") class Builder: current_tag: Bucket root_tag: Bucket - def __init__(self, tag: Bucket): + def __init__(self, tag: Bucket = None): + if tag is None: + tag = Bucket(self) self.root_tag = tag self.current_tag = tag diff --git a/server/hra/web/jinja_mac.py b/server/hra/web/jinja_mac.py index 079830f..48e49ce 100644 --- a/server/hra/web/jinja_mac.py +++ b/server/hra/web/jinja_mac.py @@ -1,3 +1,4 @@ from hra.web import app quick_form = app.jinja_env.get_template("bootstrap/wtf.html").module.quick_form +form_field = app.jinja_env.get_template("bootstrap/wtf.html").module.form_field diff --git a/server/hra/web/pages.py b/server/hra/web/pages.py index 03b36d6..e869628 100644 --- a/server/hra/web/pages.py +++ b/server/hra/web/pages.py @@ -71,7 +71,18 @@ class OptionalIntField(wtforms.IntegerField): 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): @@ -169,10 +180,120 @@ def web_index(): 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_users(): - users = db.get_session().query(db.User).all() +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(): @@ -181,10 +302,11 @@ def web_users(): b.line().th()("Akce") for u in users: with b.tr(): - b.line().th()(u.username) - b.line().th() - with b.th(): - b.a(_class="btn btn-xs btn-primary", href=app.url_for(web_org_user.__name__, user_id=u.id))("Detail") + 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'])