diff --git a/estusshots/choices.py b/estusshots/choices.py index b6ff8f3..bec7dc0 100644 --- a/estusshots/choices.py +++ b/estusshots/choices.py @@ -1,16 +1,16 @@ -from estusshots import orm, models, db -from estusshots.orm import Season +from estusshots.orm import new_session, EventType, Season, Player, Drink, Enemy def event_choices(): - return [(member.value, member.name) for member in models.EventType] + return [(member.value, member.name) for member in EventType] def season_choices(): - """ Query the database for available seasons. + """ + Query the database for available seasons. This returns a list of tuples with the season ID and a display string. """ - db = orm.new_session() + db = new_session() seasons = db.query(Season).order_by(Season.code).all() choices = [(s.id, f"{s.code}: {s.game}") for s in seasons] choices.insert(0, (-1, "No Season")) @@ -21,8 +21,8 @@ def player_choice(): """ Query database for a list of available players to bind them to a select box """ - sql, args = db.load_players() - players = db.query_db(sql, args, cls=models.Player) + db = new_session() + players = sorted(db.query(Player).all(), key=lambda x: x.name) return [(p.id, p.name) for p in players] @@ -30,8 +30,8 @@ def drink_choice(): """ Query database for a list of all available drinks to select from """ - sql, args = db.load_drinks() - drinks = db.query_db(sql, args, cls=models.Drink) + db = new_session() + drinks = db.query(Drink).order_by(Drink.name).all() choices = [(d.id, d.name) for d in drinks] choices.insert(0, (-1, "None")) return choices @@ -41,9 +41,13 @@ def enemy_choice_for_season(season_id: int): """ Query database for all available enemies in this season """ - sql, args = db.load_enemies_for_season(season_id) - enemies = db.query_db(sql, args, cls=models.Enemy) - return [(e.id, e.name) for e in enemies] + db = new_session() + season: Season = db.query(Season).get(season_id) + global_enemies = db.query(Enemy).filter(Enemy.season_id == -1).all() + if not season and not global_enemies: + return [] + combined = global_enemies + season.enemies + return [(e.id, e.name) for e in combined] class IterableBase: diff --git a/estusshots/orm.py b/estusshots/orm.py index 630053e..eaa4158 100644 --- a/estusshots/orm.py +++ b/estusshots/orm.py @@ -1,4 +1,6 @@ import enum +from typing import Iterable, List + import sqlalchemy from sqlalchemy import create_engine, ForeignKey, Table from sqlalchemy.ext.declarative import declarative_base @@ -10,6 +12,13 @@ from estusshots import util, forms engine = create_engine('sqlite:///../databases/test.db') Base = declarative_base() +player_episode = Table( + 'player_episode', + Base.metadata, + Column('player_id', ForeignKey('players.id'), primary_key=True), + Column('episode_id', ForeignKey('episodes.id'), primary_key=True) +) + class EventType(enum.Enum): Pause = 0 @@ -27,6 +36,7 @@ class Player(Base): anon = Column(Boolean, default=False) events = relationship("Event", back_populates="player") + episodes = relationship("Episode", secondary=player_episode, back_populates="players") @property def name(self) -> str: @@ -61,8 +71,8 @@ class Season(Base): start = Column(Date) end = Column(Date) - episodes = relationship("Episode", back_populates="season") - enemies = relationship("Enemy", back_populates="season") + episodes: Iterable["Episode"] = relationship("Episode", back_populates="season") + enemies: Iterable["Enemy"] = relationship("Enemy", back_populates="season") def populate_from_form(self, form: "forms.SeasonForm"): self.code = str(form.code.data) @@ -82,7 +92,7 @@ class Enemy(Base): season_id = Column(Integer, ForeignKey('seasons.id')) season = relationship("Season", back_populates="enemies") - events = relationship('Event', back_populates="enemy") + events: Iterable["Event"] = relationship('Event', back_populates="enemy") def populate_from_form(self, form: "forms.EnemyForm"): self.name = str(form.name.data) @@ -103,18 +113,26 @@ class Episode(Base): season_id = Column(Integer, ForeignKey('seasons.id')) season = relationship("Season", back_populates="episodes") - events = relationship('Event', back_populates='episode') + events: List["Event"] = relationship('Event', back_populates='episode') + players = relationship("Player", secondary=player_episode, back_populates="episodes") @property def playtime(self): - return util.timedelta(self.start, self.end) + return util.compute_timedelta(self.start, self.end) + + def populate_from_form(self, form: "forms.EpisodeForm"): + self.code = str(form.code.data) + self.title = str(form.title.data) + self.date = form.date.data + self.start = form.start.data + self.end = form.end.data class Event(Base): __tablename__ = 'events' id = Column(Integer, primary_key=True) - type = Column(Enum(EventType)) + type: EventType = Column(Enum(EventType)) time = Column(Time) comment = Column(String) @@ -127,7 +145,15 @@ class Event(Base): enemy_id = Column(Integer, ForeignKey('enemies.id')) enemy = relationship('Enemy', back_populates='events') - penalties = relationship('Penalty', back_populates='event') + penalties: List["Penalty"] = relationship('Penalty', back_populates='event') + + def populate_from_form(self, form: "forms.EventForm"): + self.episode_id = int(form.episode_id.data) + self.type = EventType(form.event_type.data) + self.time = form.time.data + self.comment = str(form.comment.data) if form.comment.data else None + self.player_id = int(form.player.data) if form.player.data else None + self.enemy_id = int(form.enemy.data) if form.enemy.data else None class Penalty(Base): diff --git a/estusshots/templates/episode_details.html b/estusshots/templates/episode_details.html index b6c7b38..9b9b99a 100644 --- a/estusshots/templates/episode_details.html +++ b/estusshots/templates/episode_details.html @@ -34,23 +34,23 @@
  • Start: - {{ model.episode.start|format_time }} + {{ model.episode.start|format_time or "Not started yet" }}
  • End: - {{ model.episode.end|format_time }} + {{ model.episode.end|format_time or "Not ended yet"}}
  • Play Time: - {{ model.episode.playtime }} + {{ model.episode.playtime or 0 }} Hours
  • Enemies Defeated: - {{ model.events.victory_count }} + {{ 0 }}
  • Deaths: - {{ model.events.defeat_count }} + {{ model.deaths|length or 0}}
  • @@ -88,46 +88,48 @@ +
    - {% if model.events %} + +
    - Event List + Deaths
    + + {% if model.deaths %} + - - - - + + + - {% for entry in model.events.entries %} + {% for entry in model.deaths %} - - - - + + + {% endfor %}
    TimeTypePlayerEnemyTimeEnemyPlayer
    {{ entry.time }}{{ entry.type.name }}{{ entry.type.player_name }}{{ entry.type.enemy_name }}{{ entry.time|format_time }}{{ entry.enemy.name }}{{ entry.player.name }}
    + + {% else %} + +
    Nothing did happen yet
    + + {% endif %}
    - {% else %} -
    -
    - Event List -
    -
    -
    Nothing did happen yet
    -
    -
    - {% endif %} + + +
    {% endblock %} diff --git a/estusshots/util.py b/estusshots/util.py index 6ab2e3f..91ef43f 100644 --- a/estusshots/util.py +++ b/estusshots/util.py @@ -37,17 +37,19 @@ def timedelta_to_str(data: timedelta) -> str: ) -def timedelta(start: time, end: time) -> float: - startDateTime = datetime.combine(date.today(), start) +def compute_timedelta(start: time, end: time) -> float: + if not start or not end: + return 0 + s = datetime.combine(date.today(), start) # Check if the the end is still on the same day if start.hour > end.hour: base = date.today() + timedelta(days=1) else: base = date.today() - endDateTime = datetime.combine(base, end) - difference = startDateTime - endDateTime + e = datetime.combine(base, end) + difference = s - e difference_hours = difference.total_seconds() / 3600 - return difference_hours + return abs(difference_hours) def combine_datetime(date: datetime.date, time: datetime.time): diff --git a/estusshots/views/enemies.py b/estusshots/views/enemies.py index ee0479d..99eafc2 100644 --- a/estusshots/views/enemies.py +++ b/estusshots/views/enemies.py @@ -1,4 +1,4 @@ -from flask import render_template, request, redirect +from flask import render_template, request, redirect, url_for from estusshots import app from estusshots import forms, models, orm @@ -18,11 +18,12 @@ def enemy_list(): @app.route("/enemy/new", methods=["GET"]) @authorize -def enemy_new(preselect_season=None): +def enemy_new(): form = forms.EnemyForm() - if preselect_season: - form.season_id.default = preselect_season + if "preselect" in request.args: + form.season_id.process_data(request.args['preselect']) + form.is_boss.data = True model = models.GenericFormModel( page_title="Enemies", @@ -64,9 +65,8 @@ def enemy_edit(enemy_id: int): enemy.populate_from_form(form) db.commit() if form.submit_continue_button.data: - form.name.data = None - return enemy_new(preselect_season=enemy.season_id) - return redirect("/enemy") + return redirect(url_for("enemy_new", preselect=form.season_id.data)) + return redirect(url_for("enemy_list")) model.form_title = "Incorrect Data" return render_template("generic_form.html", model=model, form=form) diff --git a/estusshots/views/episodes.py b/estusshots/views/episodes.py index bb6074b..31171e1 100644 --- a/estusshots/views/episodes.py +++ b/estusshots/views/episodes.py @@ -1,45 +1,24 @@ -from typing import List - -from flask import render_template, request, redirect +from flask import render_template, request, redirect, url_for from estusshots import app -from estusshots import forms, models, db +from estusshots import forms, models, orm from estusshots.util import authorize +from estusshots.orm import Season, Episode, Player, Event @app.route("/season//episode/") @authorize def episode_detail(season_id: int, episode_id: int): - sql, args = db.load_season(season_id) - season = db.query_db(sql, args, one=True, cls=models.Season) - sql, args = db.load_episode(episode_id) - episode = db.query_db(sql, args, one=True, cls=models.Episode) - sql, args = db.load_episode_players(episode_id) - ep_players = db.query_db(sql, args, cls=models.Player) - sql, args = db.load_events(episode_id) - ep_events: List[models.Event] = db.query_db(sql, args, cls=models.Event) - sql, args = db.load_enemies(season_id) - enemies = db.query_db(sql, args, cls=models.Enemy) - - deaths = [ev for ev in ep_events if ev.type == models.EventType.Death] - entries = [] - for death in deaths: - entries.append({ - "time": death.time.time(), - "type": death.type, - "player_name": [p.name for p in ep_players if p.id == death.player_id], - "enemy_name": [e.name for e in enemies if e.id == death.enemy_id] - }) - events = None - if ep_events: - events = {"entries": death, "victory_count": 0, "defeat_count": 0} + db = orm.new_session() + episode: Episode = db.query(Episode).get(episode_id) + deaths = [event for event in episode.events if event.type == orm.EventType.Death] model = { - "title": f"{season.code}{episode.code}", + "title": f"{episode.season.code}{episode.code}", "episode": episode, - "season": season, - "players": ep_players, - "events": events, + "season": episode.season, + "players": episode.players, + "deaths": sorted(deaths, key=lambda x: x.time), } return render_template("episode_details.html", model=model) @@ -48,12 +27,9 @@ def episode_detail(season_id: int, episode_id: int): @app.route("/season//episode", methods=["GET"]) @authorize def episode_list(season_id: int): - sql, args = db.load_season(season_id) - season = db.query_db(sql, args, one=True, cls=models.Season) - sql, args = db.load_episodes(season_id) - episodes = db.query_db(sql, args, cls=models.Episode) - - model = {"season_id": season_id, "season_code": season.code} + db = orm.new_session() + season = db.query(Season).filter(Season.id == season_id).first() + model = {"season_id": season.id, "season_code": season.code} return render_template("episode_list.html", model=model) @@ -65,7 +41,6 @@ def episode_new(season_id: int): form_title="Create New Episode", post_url=f"/season/{season_id}/episode/null/edit", ) - form = forms.EpisodeForm(request.form) form.season_id.data = season_id return render_template("generic_form.html", model=model, form=form) @@ -79,15 +54,10 @@ def episode_edit(season_id: int, episode_id: int): form_title="Edit Episode", post_url=f"/season/{season_id}/episode/{episode_id}/edit", ) - + form = forms.EpisodeForm() + db = orm.new_session() + episode: Episode = db.query(Episode).get(episode_id) if request.method == "GET": - sql, args = db.load_episode(episode_id) - episode: models.Episode = db.query_db(sql, args, one=True, cls=models.Episode) - - sql, args = db.load_episode_players(episode_id) - ep_players = db.query_db(sql, args, cls=models.Player) - - form = forms.EpisodeForm() form.season_id.data = episode.season_id form.episode_id.data = episode.id form.code.data = episode.code @@ -95,43 +65,24 @@ def episode_edit(season_id: int, episode_id: int): form.start.data = episode.start form.end.data = episode.end form.title.data = episode.title - form.players.data = [p.id for p in ep_players] - + form.players.data = [p.id for p in episode.players] model.form_title = f"Edit Episode '{episode.code}: {episode.title}'" return render_template("generic_form.html", model=model, form=form) else: - form = forms.EpisodeForm() - if not form.validate_on_submit(): model.errors = form.errors return render_template("generic_form.html", model=model, form=form) - - errors = False - episode = models.Episode.from_form(form) - sql, args = db.save_episode(episode) - - last_key = db.update_db(sql, args, return_key=True) - - episode_id = episode.id if episode.id else last_key - - form_ids = form.players.data - - sql, args = db.load_episode_players(episode_id) - ep_players = db.query_db(sql, args, cls=models.Player) - pids = [p.id for p in ep_players] - - new_ids = [pid for pid in form_ids if pid not in pids] - removed_ids = [pid for pid in pids if pid not in form_ids] - - if removed_ids: - sql, args = db.remove_episode_player(episode_id, removed_ids) - errors = db.update_db(sql, args) - - if new_ids: - sql, args = db.save_episode_players(episode_id, new_ids) - errors = db.update_db(sql, args) - + if not episode: + episode = Episode() + db.add(episode) + season: Season = db.query(Season).get(season_id) + episode.populate_from_form(form) + episode.season = season + player_ids = list(form.players.data) + players = db.query(Player).filter(Player.id.in_(player_ids)).all() + episode.players = players + errors = db.commit() if errors: model.errors = {"Error saving episode": [errors]} return render_template("generic_form.html", model=model, form=form) - return redirect(url_for("season_overview", season_id=season_id)) + return redirect(url_for("episode_detail", season_id=season_id, episode_id=episode_id)) diff --git a/estusshots/views/events.py b/estusshots/views/events.py index e2bac8e..c7927e4 100644 --- a/estusshots/views/events.py +++ b/estusshots/views/events.py @@ -2,9 +2,10 @@ from collections import namedtuple from flask import render_template, request, redirect -from estusshots import app -from estusshots import forms, models, db, choices +from estusshots import app, orm +from estusshots import forms, models, choices from estusshots.util import authorize +from estusshots.orm import new_session, EventType, Event, Episode, Enemy, Penalty @app.route("/season//episode//event/new", methods=["GET"]) @@ -15,19 +16,16 @@ def event_new(s_id: int, ep_id: int): "form_title": "Create New Event", "post_url": f"/season/{s_id}/episode/{ep_id}/event/null/edit", } - sql, args = db.load_episode(ep_id) - episode: models.Episode = db.query_db(sql, args, one=True, cls=models.Episode) - - sql, args = db.load_episode_players(ep_id) - ep_players = db.query_db(sql, args, cls=models.Player) + db = new_session() + episode: Episode = db.query(Episode).get(ep_id) form = forms.EventForm() form.episode_id.data = ep_id form.enemy.choices = choices.enemy_choice_for_season(s_id) - form.event_type.data = 1 + form.event_type.data = EventType.Death.value Penalty = namedtuple("Penalty", ["penalty_id", "player_id", "player", "drink"]) - for player in ep_players: + for player in episode.players: form.penalties.append_entry(Penalty(None, player.id, player.name, 1)) return render_template("event_editor.html", model=model, form=form) @@ -41,16 +39,43 @@ def event_edit(s_id: int, ep_id: int, ev_id: int): "form_title": "Edit Event", "post_url": f"/season/{s_id}/episode/{ep_id}/event/{ev_id}/edit", } + db = new_session() + event: Event = db.query(Event).get(ev_id) + form = forms.EventForm() + form.enemy.choices = choices.enemy_choice_for_season(s_id) + if request.method == "GET": + form.episode_id.process_data(event.episode_id) + form.event_type.process_data(event.type.value) + form.enemy.process_data(event.enemy_id) + form.player.process_data(event.player_id) + form.time.process_data(event.time) + form.comment.process_data(event.comment) + Penalty = namedtuple("Penalty", ["penalty_id", "player_id", "player", "drink"]) + for penalty in event.penalties: + form.penalties.append_entry(Penalty(penalty.id, penalty.player_id, penalty.player.name, penalty.drink_id)) return render_template("event_editor.html", model=model) else: - form = forms.EventForm() - form.enemy.choices = choices.enemy_choice_for_season(s_id) if not form.validate_on_submit(): model["errors"] = form.errors return render_template("event_editor.html", model=model, form=form) + if not event: + event = Event() + for entry in form.penalties: + penalty = orm.Penalty() + penalty.player_id = entry.player_id.data + penalty.drink_id = entry.drink.data + db.add(penalty) + event.penalties.append(penalty) + db.add(event) + else: + for event in event.penalties: + penalty = next((p for p in form.penalties.data if p["penalty_id"] == event.id), None) + if not penalty: + continue + penalty.player_id = form.player_id.data + penalty.drink_id = form.drink.data - event = models.Event.from_form(form) - sql, args = db.save_event(event) - errors = db.update_db(sql, args) + event.populate_from_form(form) + db.commit() return redirect(f"/season/{s_id}/episode/{ep_id}")