Finalize migration to SQLAlchemy.

This commit is contained in:
2019-10-16 22:37:22 +02:00
parent fccd75d6c5
commit eafcfffc1f
7 changed files with 158 additions and 148 deletions

View File

@@ -1,16 +1,16 @@
from estusshots import orm, models, db from estusshots.orm import new_session, EventType, Season, Player, Drink, Enemy
from estusshots.orm import Season
def event_choices(): 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(): 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. 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() seasons = db.query(Season).order_by(Season.code).all()
choices = [(s.id, f"{s.code}: {s.game}") for s in seasons] choices = [(s.id, f"{s.code}: {s.game}") for s in seasons]
choices.insert(0, (-1, "No Season")) 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 Query database for a list of available players to bind them to a select box
""" """
sql, args = db.load_players() db = new_session()
players = db.query_db(sql, args, cls=models.Player) players = sorted(db.query(Player).all(), key=lambda x: x.name)
return [(p.id, p.name) for p in players] 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 Query database for a list of all available drinks to select from
""" """
sql, args = db.load_drinks() db = new_session()
drinks = db.query_db(sql, args, cls=models.Drink) drinks = db.query(Drink).order_by(Drink.name).all()
choices = [(d.id, d.name) for d in drinks] choices = [(d.id, d.name) for d in drinks]
choices.insert(0, (-1, "None")) choices.insert(0, (-1, "None"))
return choices return choices
@@ -41,9 +41,13 @@ def enemy_choice_for_season(season_id: int):
""" """
Query database for all available enemies in this season Query database for all available enemies in this season
""" """
sql, args = db.load_enemies_for_season(season_id) db = new_session()
enemies = db.query_db(sql, args, cls=models.Enemy) season: Season = db.query(Season).get(season_id)
return [(e.id, e.name) for e in enemies] 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: class IterableBase:

View File

@@ -1,4 +1,6 @@
import enum import enum
from typing import Iterable, List
import sqlalchemy import sqlalchemy
from sqlalchemy import create_engine, ForeignKey, Table from sqlalchemy import create_engine, ForeignKey, Table
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
@@ -10,6 +12,13 @@ from estusshots import util, forms
engine = create_engine('sqlite:///../databases/test.db') engine = create_engine('sqlite:///../databases/test.db')
Base = declarative_base() 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): class EventType(enum.Enum):
Pause = 0 Pause = 0
@@ -27,6 +36,7 @@ class Player(Base):
anon = Column(Boolean, default=False) anon = Column(Boolean, default=False)
events = relationship("Event", back_populates="player") events = relationship("Event", back_populates="player")
episodes = relationship("Episode", secondary=player_episode, back_populates="players")
@property @property
def name(self) -> str: def name(self) -> str:
@@ -61,8 +71,8 @@ class Season(Base):
start = Column(Date) start = Column(Date)
end = Column(Date) end = Column(Date)
episodes = relationship("Episode", back_populates="season") episodes: Iterable["Episode"] = relationship("Episode", back_populates="season")
enemies = relationship("Enemy", back_populates="season") enemies: Iterable["Enemy"] = relationship("Enemy", back_populates="season")
def populate_from_form(self, form: "forms.SeasonForm"): def populate_from_form(self, form: "forms.SeasonForm"):
self.code = str(form.code.data) self.code = str(form.code.data)
@@ -82,7 +92,7 @@ class Enemy(Base):
season_id = Column(Integer, ForeignKey('seasons.id')) season_id = Column(Integer, ForeignKey('seasons.id'))
season = relationship("Season", back_populates="enemies") 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"): def populate_from_form(self, form: "forms.EnemyForm"):
self.name = str(form.name.data) self.name = str(form.name.data)
@@ -103,18 +113,26 @@ class Episode(Base):
season_id = Column(Integer, ForeignKey('seasons.id')) season_id = Column(Integer, ForeignKey('seasons.id'))
season = relationship("Season", back_populates="episodes") 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 @property
def playtime(self): 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): class Event(Base):
__tablename__ = 'events' __tablename__ = 'events'
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
type = Column(Enum(EventType)) type: EventType = Column(Enum(EventType))
time = Column(Time) time = Column(Time)
comment = Column(String) comment = Column(String)
@@ -127,7 +145,15 @@ class Event(Base):
enemy_id = Column(Integer, ForeignKey('enemies.id')) enemy_id = Column(Integer, ForeignKey('enemies.id'))
enemy = relationship('Enemy', back_populates='events') 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): class Penalty(Base):

View File

@@ -34,23 +34,23 @@
</li> </li>
<li class="list-group-item"> <li class="list-group-item">
<span class="font-weight-bold">Start:</span> <span class="font-weight-bold">Start:</span>
{{ model.episode.start|format_time }} {{ model.episode.start|format_time or "Not started yet" }}
</li> </li>
<li class="list-group-item"> <li class="list-group-item">
<span class="font-weight-bold">End:</span> <span class="font-weight-bold">End:</span>
{{ model.episode.end|format_time }} {{ model.episode.end|format_time or "Not ended yet"}}
</li> </li>
<li class="list-group-item"> <li class="list-group-item">
<span class="font-weight-bold">Play Time:</span> <span class="font-weight-bold">Play Time:</span>
{{ model.episode.playtime }} {{ model.episode.playtime or 0 }} Hours
</li> </li>
<li class="list-group-item"> <li class="list-group-item">
<span class="font-weight-bold">Enemies Defeated:</span> <span class="font-weight-bold">Enemies Defeated:</span>
{{ model.events.victory_count }} {{ 0 }}
</li> </li>
<li class="list-group-item"> <li class="list-group-item">
<span class="font-weight-bold">Deaths:</span> <span class="font-weight-bold">Deaths:</span>
{{ model.events.defeat_count }} {{ model.deaths|length or 0}}
</li> </li>
</ul> </ul>
</div> </div>
@@ -88,46 +88,48 @@
<!--endregion--> <!--endregion-->
</div> </div>
<div class="col-sm-6"> <div class="col-sm-6">
{% if model.events %} <!--region Deaths Card-->
<div class="card"> <div class="card">
<div class="card-header text-center"> <div class="card-header text-center">
Event List Deaths
</div> </div>
<div class="card-body"> <div class="card-body">
{% if model.deaths %}
<table class="table table-striped table-bordered"> <table class="table table-striped table-bordered">
<thead> <thead>
<tr> <tr>
<th scope="col" class="col-sm-auto text-center">Time</th> <th scope="col" class="col-xs-auto text-center">Time</th>
<th scope="col" class="col-sm-auto text-center">Type</th> <th scope="col" class="col-xs text-center">Enemy</th>
<th scope="col" class="col-sm-auto text-center">Player</th> <th scope="col" class="col-xs text-center">Player</th>
<th scope="col" class="col-sm-auto text-center">Enemy</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for entry in model.events.entries %} {% for entry in model.deaths %}
<tr> <tr>
<td class="col-sm-auto text-center">{{ entry.time }}</td> <td class="col-xs-auto text-center">{{ entry.time|format_time }}</td>
<td class="col-sm-auto text-center">{{ entry.type.name }}</td> <td class="col-xs text-center">{{ entry.enemy.name }}</td>
<td class="col-sm-auto text-center">{{ entry.type.player_name }}</td> <td class="col-xs text-center">{{ entry.player.name }}</td>
<td class="col-sm-auto text-center">{{ entry.type.enemy_name }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
{% else %}
<div class="alert alert-info">Nothing did happen yet</div>
{% endif %}
</div> </div>
</div> </div>
{% else %}
<div class="card"> <!--endregion-->
<div class="card-header text-center">
Event List
</div>
<div class="card-body">
<div class="alert alert-info">Nothing did happen yet</div>
</div>
</div>
{% endif %}
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -37,17 +37,19 @@ def timedelta_to_str(data: timedelta) -> str:
) )
def timedelta(start: time, end: time) -> float: def compute_timedelta(start: time, end: time) -> float:
startDateTime = datetime.combine(date.today(), start) 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 # Check if the the end is still on the same day
if start.hour > end.hour: if start.hour > end.hour:
base = date.today() + timedelta(days=1) base = date.today() + timedelta(days=1)
else: else:
base = date.today() base = date.today()
endDateTime = datetime.combine(base, end) e = datetime.combine(base, end)
difference = startDateTime - endDateTime difference = s - e
difference_hours = difference.total_seconds() / 3600 difference_hours = difference.total_seconds() / 3600
return difference_hours return abs(difference_hours)
def combine_datetime(date: datetime.date, time: datetime.time): def combine_datetime(date: datetime.date, time: datetime.time):

View File

@@ -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 app
from estusshots import forms, models, orm from estusshots import forms, models, orm
@@ -18,11 +18,12 @@ def enemy_list():
@app.route("/enemy/new", methods=["GET"]) @app.route("/enemy/new", methods=["GET"])
@authorize @authorize
def enemy_new(preselect_season=None): def enemy_new():
form = forms.EnemyForm() form = forms.EnemyForm()
if preselect_season: if "preselect" in request.args:
form.season_id.default = preselect_season form.season_id.process_data(request.args['preselect'])
form.is_boss.data = True
model = models.GenericFormModel( model = models.GenericFormModel(
page_title="Enemies", page_title="Enemies",
@@ -64,9 +65,8 @@ def enemy_edit(enemy_id: int):
enemy.populate_from_form(form) enemy.populate_from_form(form)
db.commit() db.commit()
if form.submit_continue_button.data: if form.submit_continue_button.data:
form.name.data = None return redirect(url_for("enemy_new", preselect=form.season_id.data))
return enemy_new(preselect_season=enemy.season_id) return redirect(url_for("enemy_list"))
return redirect("/enemy")
model.form_title = "Incorrect Data" model.form_title = "Incorrect Data"
return render_template("generic_form.html", model=model, form=form) return render_template("generic_form.html", model=model, form=form)

View File

@@ -1,45 +1,24 @@
from typing import List from flask import render_template, request, redirect, url_for
from flask import render_template, request, redirect
from estusshots import app from estusshots import app
from estusshots import forms, models, db from estusshots import forms, models, orm
from estusshots.util import authorize from estusshots.util import authorize
from estusshots.orm import Season, Episode, Player, Event
@app.route("/season/<season_id>/episode/<episode_id>") @app.route("/season/<season_id>/episode/<episode_id>")
@authorize @authorize
def episode_detail(season_id: int, episode_id: int): def episode_detail(season_id: int, episode_id: int):
sql, args = db.load_season(season_id) db = orm.new_session()
season = db.query_db(sql, args, one=True, cls=models.Season) episode: Episode = db.query(Episode).get(episode_id)
sql, args = db.load_episode(episode_id) deaths = [event for event in episode.events if event.type == orm.EventType.Death]
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}
model = { model = {
"title": f"{season.code}{episode.code}", "title": f"{episode.season.code}{episode.code}",
"episode": episode, "episode": episode,
"season": season, "season": episode.season,
"players": ep_players, "players": episode.players,
"events": events, "deaths": sorted(deaths, key=lambda x: x.time),
} }
return render_template("episode_details.html", model=model) return render_template("episode_details.html", model=model)
@@ -48,12 +27,9 @@ def episode_detail(season_id: int, episode_id: int):
@app.route("/season/<season_id>/episode", methods=["GET"]) @app.route("/season/<season_id>/episode", methods=["GET"])
@authorize @authorize
def episode_list(season_id: int): def episode_list(season_id: int):
sql, args = db.load_season(season_id) db = orm.new_session()
season = db.query_db(sql, args, one=True, cls=models.Season) season = db.query(Season).filter(Season.id == season_id).first()
sql, args = db.load_episodes(season_id) model = {"season_id": season.id, "season_code": season.code}
episodes = db.query_db(sql, args, cls=models.Episode)
model = {"season_id": season_id, "season_code": season.code}
return render_template("episode_list.html", model=model) return render_template("episode_list.html", model=model)
@@ -65,7 +41,6 @@ def episode_new(season_id: int):
form_title="Create New Episode", form_title="Create New Episode",
post_url=f"/season/{season_id}/episode/null/edit", post_url=f"/season/{season_id}/episode/null/edit",
) )
form = forms.EpisodeForm(request.form) form = forms.EpisodeForm(request.form)
form.season_id.data = season_id form.season_id.data = season_id
return render_template("generic_form.html", model=model, form=form) 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", form_title="Edit Episode",
post_url=f"/season/{season_id}/episode/{episode_id}/edit", 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": 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.season_id.data = episode.season_id
form.episode_id.data = episode.id form.episode_id.data = episode.id
form.code.data = episode.code form.code.data = episode.code
@@ -95,43 +65,24 @@ def episode_edit(season_id: int, episode_id: int):
form.start.data = episode.start form.start.data = episode.start
form.end.data = episode.end form.end.data = episode.end
form.title.data = episode.title 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}'" model.form_title = f"Edit Episode '{episode.code}: {episode.title}'"
return render_template("generic_form.html", model=model, form=form) return render_template("generic_form.html", model=model, form=form)
else: else:
form = forms.EpisodeForm()
if not form.validate_on_submit(): if not form.validate_on_submit():
model.errors = form.errors model.errors = form.errors
return render_template("generic_form.html", model=model, form=form) return render_template("generic_form.html", model=model, form=form)
if not episode:
errors = False episode = Episode()
episode = models.Episode.from_form(form) db.add(episode)
sql, args = db.save_episode(episode) season: Season = db.query(Season).get(season_id)
episode.populate_from_form(form)
last_key = db.update_db(sql, args, return_key=True) episode.season = season
player_ids = list(form.players.data)
episode_id = episode.id if episode.id else last_key players = db.query(Player).filter(Player.id.in_(player_ids)).all()
episode.players = players
form_ids = form.players.data errors = db.commit()
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 errors: if errors:
model.errors = {"Error saving episode": [errors]} model.errors = {"Error saving episode": [errors]}
return render_template("generic_form.html", model=model, form=form) 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))

View File

@@ -2,9 +2,10 @@ from collections import namedtuple
from flask import render_template, request, redirect from flask import render_template, request, redirect
from estusshots import app from estusshots import app, orm
from estusshots import forms, models, db, choices from estusshots import forms, models, choices
from estusshots.util import authorize from estusshots.util import authorize
from estusshots.orm import new_session, EventType, Event, Episode, Enemy, Penalty
@app.route("/season/<s_id>/episode/<ep_id>/event/new", methods=["GET"]) @app.route("/season/<s_id>/episode/<ep_id>/event/new", methods=["GET"])
@@ -15,19 +16,16 @@ def event_new(s_id: int, ep_id: int):
"form_title": "Create New Event", "form_title": "Create New Event",
"post_url": f"/season/{s_id}/episode/{ep_id}/event/null/edit", "post_url": f"/season/{s_id}/episode/{ep_id}/event/null/edit",
} }
sql, args = db.load_episode(ep_id) db = new_session()
episode: models.Episode = db.query_db(sql, args, one=True, cls=models.Episode) episode: Episode = db.query(Episode).get(ep_id)
sql, args = db.load_episode_players(ep_id)
ep_players = db.query_db(sql, args, cls=models.Player)
form = forms.EventForm() form = forms.EventForm()
form.episode_id.data = ep_id form.episode_id.data = ep_id
form.enemy.choices = choices.enemy_choice_for_season(s_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"]) 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)) form.penalties.append_entry(Penalty(None, player.id, player.name, 1))
return render_template("event_editor.html", model=model, form=form) 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", "form_title": "Edit Event",
"post_url": f"/season/{s_id}/episode/{ep_id}/event/{ev_id}/edit", "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": 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) return render_template("event_editor.html", model=model)
else: else:
form = forms.EventForm()
form.enemy.choices = choices.enemy_choice_for_season(s_id)
if not form.validate_on_submit(): if not form.validate_on_submit():
model["errors"] = form.errors model["errors"] = form.errors
return render_template("event_editor.html", model=model, form=form) 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) event.populate_from_form(form)
sql, args = db.save_event(event) db.commit()
errors = db.update_db(sql, args)
return redirect(f"/season/{s_id}/episode/{ep_id}") return redirect(f"/season/{s_id}/episode/{ep_id}")