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 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:

View File

@@ -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):

View File

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

View File

@@ -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):

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 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)

View File

@@ -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/<season_id>/episode/<episode_id>")
@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/<season_id>/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))

View File

@@ -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/<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",
"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}")