diff --git a/dsst/dsst_gtk3/dialogs.py b/dsst/dsst_gtk3/dialogs.py index 6bcbcb6..d150bb9 100644 --- a/dsst/dsst_gtk3/dialogs.py +++ b/dsst/dsst_gtk3/dialogs.py @@ -1,3 +1,6 @@ +""" +This module contains UI functions for displaying different dialogs +""" import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk @@ -42,7 +45,7 @@ def show_episode_dialog(builder: Gtk.Builder, title: str, season_id: int, episod dialog = builder.get_object("edit_episode_dialog") # type: Gtk.Dialog dialog.set_transient_for(builder.get_object("main_window")) dialog.set_title(title) - with sql.connection.atomic(): + with sql.db.atomic(): if not episode: nxt_number = len(sql.Season.get_by_id(season_id).episodes) + 1 episode = sql.Episode.create(seq_number=nxt_number, number=nxt_number, date=datetime.today(), @@ -61,7 +64,7 @@ def show_episode_dialog(builder: Gtk.Builder, title: str, season_id: int, episod dialog.hide() if result != Gtk.ResponseType.OK: - sql.connection.rollback() + sql.db.rollback() return False # Save all changes to Database @@ -79,6 +82,10 @@ def show_episode_dialog(builder: Gtk.Builder, title: str, season_id: int, episod def show_manage_players_dialog(builder: Gtk.Builder, title: str): + """Show a dialog for managing player base data. + :param builder: Gtk.Builder object + :param title: Title for the dialog + """ dialog = builder.get_object("manage_players_dialog") # type: Gtk.Dialog dialog.set_transient_for(builder.get_object("main_window")) dialog.set_title(title) @@ -109,9 +116,15 @@ def show_manage_drinks_dialog(builder: Gtk.Builder): def show_edit_death_dialog(builder: Gtk.Builder, episode_id: int, death: sql.Death=None): + """Show a dialog for editing or creating death events. + :param builder: A Gtk.Builder object + :param episode_id: ID to witch the death event belongs to + :param death: (Optional) Death event witch should be edited + :return: Gtk.ResponseType of the dialog + """ dialog = builder.get_object("edit_death_dialog") # type: Gtk.Dialog dialog.set_transient_for(builder.get_object("main_window")) - with sql.connection.atomic(): + with sql.db.atomic(): if death: index = util.Util.get_index_of_combo_model(builder.get_object('edit_death_enemy_combo'), 0, death.enemy.id) builder.get_object('edit_death_enemy_combo').set_active(index) @@ -126,10 +139,10 @@ def show_edit_death_dialog(builder: Gtk.Builder, episode_id: int, death: sql.Dea # Run the dialog result = dialog.run() dialog.hide() - if result != Gtk.ResponseType.OK: - sql.connection.rollback() - return False + sql.db.rollback() + return result + # Collect info from widgets and save to database player_id = util.Util.get_combo_value(builder.get_object('edit_death_player_combo'), 0) enemy_id = util.Util.get_combo_value(builder.get_object('edit_death_enemy_combo'), 3) @@ -143,4 +156,4 @@ def show_edit_death_dialog(builder: Gtk.Builder, episode_id: int, death: sql.Dea drink_id = sql.Drink.get(sql.Drink.name == entry[2]) sql.Penalty.create(size=size, player=entry[3], death=death.id, drink=drink_id) - return True + return result diff --git a/dsst/dsst_gtk3/gtk_ui.py b/dsst/dsst_gtk3/gtk_ui.py index 61dfaa7..051a4ce 100644 --- a/dsst/dsst_gtk3/gtk_ui.py +++ b/dsst/dsst_gtk3/gtk_ui.py @@ -1,17 +1,13 @@ import gi -import os - gi.require_version('Gtk', '3.0') from gi.repository import Gtk from dsst_gtk3.handlers import handlers from dsst_gtk3 import util - from dsst_sql import sql, sql_func class GtkUi: """ The main UI class """ - def __init__(self): # Load Glade UI files self.ui = Gtk.Builder() @@ -26,13 +22,16 @@ class GtkUi: self.ui.connect_signals(self.handlers) # Show all widgets self.ui.get_object('main_window').show_all() + # Initialize the database + # TODO User input to select database + sql.db.init('dsst', user='dsst', password='dsst') # Create database if not exists - sql.create_tables() + sql_func.create_tables() self.reload_base_data() - self.reload_seasons() def reload_base_data(self): + """Reload function for all base data witch is not dependant on selected season or episode""" # Rebuild all players store self.ui.get_object('all_players_store').clear() for player in sql.Player.select(): @@ -41,8 +40,6 @@ class GtkUi: self.ui.get_object('drink_store').clear() for drink in sql.Drink.select(): self.ui.get_object('drink_store').append([drink.id, drink.name, str(drink.vol)]) - - def reload_seasons(self): # Rebuild seasons store store = self.ui.get_object('seasons_store') store.clear() @@ -51,6 +48,7 @@ class GtkUi: # Reload after season was changed ################################################################################## def reload_for_season(self): + """Reload all data that is dependant on a selected season""" season_id = self.get_selected_season_id() if season_id is None or season_id == -1: return @@ -82,6 +80,7 @@ class GtkUi: # Reload after episode was changed ################################################################################# def reload_for_episode(self): + """Reload all data that is dependant on a selected episode""" episode_id = self.get_selected_episode_id() if not episode_id: return @@ -90,11 +89,17 @@ class GtkUi: for player in sql.Episode.get(sql.Episode.id == self.get_selected_episode_id()).players: store.append([player.id, player.name, player.hex_id]) - def get_selected_season_id(self): + def get_selected_season_id(self) -> int: + """Read ID of the selected season from the UI + :return: ID of the selected season + """ season_id = util.Util.get_combo_value(self.ui.get_object('season_combo_box'), 0) return season_id if season_id != -1 else None def get_selected_episode_id(self): + """Parse ID of the selected episode from the UI + :return: ID of the selected episode + """ (model, tree_iter) = self.ui.get_object('episodes_tree_view').get_selection().get_selected() return model.get_value(tree_iter, 0) if tree_iter else None diff --git a/dsst/dsst_gtk3/handlers/players.py b/dsst/dsst_gtk3/handlers/base_data_handlers.py similarity index 81% rename from dsst/dsst_gtk3/handlers/players.py rename to dsst/dsst_gtk3/handlers/base_data_handlers.py index 214936c..5ec083c 100644 --- a/dsst/dsst_gtk3/handlers/players.py +++ b/dsst/dsst_gtk3/handlers/base_data_handlers.py @@ -2,7 +2,8 @@ from dsst_gtk3 import dialogs, gtk_ui from dsst_sql import sql -class PlayerHandlers: +class BaseDataHandlers: + """Callback handlers for signals related to the manipulation of base data (players, drinks, ...)""" def __init__(self, app: 'gtk_ui.GtkUi'): self.app = app diff --git a/dsst/dsst_gtk3/handlers/center_handlers.py b/dsst/dsst_gtk3/handlers/death_handlers.py similarity index 82% rename from dsst/dsst_gtk3/handlers/center_handlers.py rename to dsst/dsst_gtk3/handlers/death_handlers.py index 290ef8b..dfcf981 100644 --- a/dsst/dsst_gtk3/handlers/center_handlers.py +++ b/dsst/dsst_gtk3/handlers/death_handlers.py @@ -1,7 +1,8 @@ from dsst_gtk3 import dialogs, gtk_ui -class CenterHandlers: +class DeathHandlers: + """Callback handlers for signals related to managing death events""" def __init__(self, app: 'gtk_ui.GtkUi'): self.app = app diff --git a/dsst/dsst_gtk3/handlers/dialog_handlers.py b/dsst/dsst_gtk3/handlers/dialog_handlers.py index 153f812..6237e66 100644 --- a/dsst/dsst_gtk3/handlers/dialog_handlers.py +++ b/dsst/dsst_gtk3/handlers/dialog_handlers.py @@ -3,6 +3,7 @@ from dsst_sql import sql class DialogHandlers: + """ Callback handlers for signals emitted from dialogs of the main window""" def __init__(self, app: 'gtk_ui.GtkUi'): self.app = app diff --git a/dsst/dsst_gtk3/handlers/handlers.py b/dsst/dsst_gtk3/handlers/handlers.py index 5b2594e..789bcc2 100644 --- a/dsst/dsst_gtk3/handlers/handlers.py +++ b/dsst/dsst_gtk3/handlers/handlers.py @@ -1,26 +1,25 @@ import gi +import sql_func gi.require_version('Gtk', '3.0') from gi.repository import Gtk -from dsst_gtk3.handlers.left_column_handlers import LeftColumnHandlers -from dsst_gtk3.handlers.players import PlayerHandlers +from dsst_gtk3.handlers.season_handlers import SeasonHandlers +from dsst_gtk3.handlers.base_data_handlers import BaseDataHandlers from dsst_gtk3.handlers.dialog_handlers import DialogHandlers -from dsst_gtk3.handlers.center_handlers import CenterHandlers - -from dsst_sql import sql +from dsst_gtk3.handlers.death_handlers import DeathHandlers -class Handlers(LeftColumnHandlers, PlayerHandlers, DialogHandlers, CenterHandlers): - """ Class containing all signal handlers for the GTK GUI """ +class Handlers(SeasonHandlers, BaseDataHandlers, DialogHandlers, DeathHandlers): + """Single callback handler class derived from specialized handler classes""" def __init__(self, app): """ Initialize handler class :param app: reference to the main application object """ self.app = app # Call constructors of superclasses - LeftColumnHandlers.__init__(self, app) - PlayerHandlers.__init__(self, app) + SeasonHandlers.__init__(self, app) + BaseDataHandlers.__init__(self, app) DialogHandlers.__init__(self, app) - CenterHandlers.__init__(self, app) + DeathHandlers.__init__(self, app) @staticmethod def do_delete_event(*args): @@ -33,5 +32,5 @@ class Handlers(LeftColumnHandlers, PlayerHandlers, DialogHandlers, CenterHandler @staticmethod def do_delete_database(*_): - sql.drop_tables() - sql.create_tables() \ No newline at end of file + sql_func.drop_tables() + sql_func.create_tables() \ No newline at end of file diff --git a/dsst/dsst_gtk3/handlers/left_column_handlers.py b/dsst/dsst_gtk3/handlers/season_handlers.py similarity index 89% rename from dsst/dsst_gtk3/handlers/left_column_handlers.py rename to dsst/dsst_gtk3/handlers/season_handlers.py index e2fa4ac..77228fc 100644 --- a/dsst/dsst_gtk3/handlers/left_column_handlers.py +++ b/dsst/dsst_gtk3/handlers/season_handlers.py @@ -2,7 +2,8 @@ from dsst_sql import sql from dsst_gtk3 import dialogs, gtk_ui -class LeftColumnHandlers: +class SeasonHandlers: + """Callback handlers related to signals for managing seasonal data""" def __init__(self, app: 'gtk_ui.GtkUi'): self.app = app diff --git a/dsst/dsst_gtk3/util.py b/dsst/dsst_gtk3/util.py index 1c96755..3cc9b75 100644 --- a/dsst/dsst_gtk3/util.py +++ b/dsst/dsst_gtk3/util.py @@ -1,3 +1,7 @@ +""" +This modules contains general utilities for the GTK application to use. +""" + import os from zipfile import ZipFile @@ -5,7 +9,11 @@ from zipfile import ZipFile class Util: @staticmethod def get_combo_value(combo, index: int): - """ Retrieve the selected value of a combo box at the selected index in the model """ + """ Retrieve the selected value of a combo box at the selected index in the model + :param combo: Any Gtk Widget that supports 'get_active_iter()' + :param index: Index of the value in the widgets model to be retrieved + :return: The value of the model at the selected index (Default -1) + """ tree_iter = combo.get_active_iter() if tree_iter: return combo.get_model().get_value(tree_iter, index) @@ -13,8 +21,14 @@ class Util: return -1 @staticmethod - def get_index_of_combo_model(combo, column: int, value: int): - model = combo.get_model() + def get_index_of_combo_model(widget, column: int, value: int): + """Get the index of a value within a Gtk widgets model based on column an value + :param widget: Any Gtk widget that can be bound to a ListStore or TreeStore + :param column: Column in the model where to look for the value + :param value: Value to look for in the model + :return: List of the indexes where the value occurs + """ + model = widget.get_model() return [model.index(entry) for entry in model if entry[column] == value] @staticmethod diff --git a/dsst/dsst_sql/core.py b/dsst/dsst_sql/core.py deleted file mode 100644 index eeba37b..0000000 --- a/dsst/dsst_sql/core.py +++ /dev/null @@ -1,56 +0,0 @@ -from dsst_sql import sql - - -class DSSTCore: - def __init__(self): - # Set DB Connection - sql.create_tables() - - @staticmethod - def insert_player(model: models.Player): - return sql.Player.create(name=model.name, hex_id=model.hex_id) - - @staticmethod - def insert_enemy(enemy: models.Enemy): - return sql.Enemy.create(name=enemy.name) - - @staticmethod - def insert_drink(model: models.Drink): - return sql.Drink.create(name=model.name, vol=model.vol) - - @staticmethod - def insert_season(season: models.Season): - return sql.Season.create(number=season.number, game_name=season.game_name, start_date=season.start_date, - end_date=season.end_date) - - @staticmethod - def insert_death(death: models.Death): - return sql.Death.create(info=death.info, player=death.player.id, enemy=death.enemy.id, penalty=death.penalty.id) - - @staticmethod - def insert_victory(victory: models.Victory): - return sql.Death.create(info=victory.info, player=victory.player.id, enemy=victory.enemy.id) - - @staticmethod - def insert_episode(season_id: int, episode: models.Episode): - with sql.connection.atomic(): - # Insert Episode Row - new_ep = sql.Episode.create(seq_number=episode.seq_number, number=episode.number, date=episode.date, - season=season_id) - # Insert participating players - for player in episode.players: - sql.EpisodePlayer.insert(episode=new_ep.id, player=player.id) - # Insert deaths in this episode - if episode.deaths: - for death in episode.deaths: - new_death = DSSTCore.insert_death(death) - sql.EpisodeDeath.create(death=new_death.id, episode=new_ep.id) - # Insert victories in this episode - if episode.victories: - for victory in episode.victories: - new_vic = DSSTCore.insert_victory(victory) - sql.EpisodeVictory.create(victory=new_vic.id, episode=new_ep.id) - - -if __name__ == '__main__': - core = DSSTCore() \ No newline at end of file diff --git a/dsst/dsst_sql/sql.py b/dsst/dsst_sql/sql.py index fb57799..5ea2d01 100644 --- a/dsst/dsst_sql/sql.py +++ b/dsst/dsst_sql/sql.py @@ -1,6 +1,14 @@ +""" +This module contains the ORM class definitions for peewee. +To access the database import this module an run queries on the classes +Example: +from sql import Episode +query = Episode.select().where(Episode.name == 'MyName') +""" + from peewee import * -connection = MySQLDatabase('dsst', user='dsst', password='dsst') +db = MySQLDatabase(None) class Season(Model): @@ -11,7 +19,7 @@ class Season(Model): end_date = DateField(null=True) class Meta: - database = connection + database = db class Player(Model): @@ -20,7 +28,7 @@ class Player(Model): hex_id = CharField(null=True) class Meta: - database = connection + database = db class Episode(Model): @@ -32,7 +40,7 @@ class Episode(Model): players = ManyToManyField(Player, backref='episodes') class Meta: - database = connection + database = db class Drink(Model): @@ -41,7 +49,7 @@ class Drink(Model): vol = DecimalField() class Meta: - database = connection + database = db class Enemy(Model): @@ -50,7 +58,7 @@ class Enemy(Model): season = ForeignKeyField(Season, backref='enemies') class Meta: - database = connection + database = db class Death(Model): @@ -61,7 +69,7 @@ class Death(Model): episode = ForeignKeyField(Episode, backref='deaths') class Meta: - database = connection + database = db class Penalty(Model): @@ -72,7 +80,7 @@ class Penalty(Model): ForeignKeyField(Death, backref='penalties') class Meta: - database = connection + database = db class Victory(Model): @@ -83,15 +91,4 @@ class Victory(Model): episode = ForeignKeyField(Episode, backref='victories') class Meta: - database = connection - - -def create_tables(): - models = [Season, Episode, Player, Drink, Enemy, Death, Victory, Penalty, Episode.players.get_through_model()] - for model in models: - model.create_table() - - -def drop_tables(): - models = [Season, Episode, Player, Drink, Enemy, Death, Victory, Penalty, Episode.players.get_through_model()] - connection.drop_tables(models) + database = db diff --git a/dsst/dsst_sql/sql_func.py b/dsst/dsst_sql/sql_func.py index 12c4031..11139ee 100644 --- a/dsst/dsst_sql/sql_func.py +++ b/dsst/dsst_sql/sql_func.py @@ -1,7 +1,14 @@ +""" +This module contains shorthand functions for common queries to ease access from the UI +""" from dsst_sql.sql import * -def get_episodes_for_season(season_id): +def get_episodes_for_season(season_id: int) -> list: + """Load list of episodes for a specific season + :param season_id: ID of a season + :return: List of sql.Episode or empty list + """ try: return list(Season.get(Season.id == season_id).episodes) except Episode.DoesNotExist: @@ -9,6 +16,11 @@ def get_episodes_for_season(season_id): def get_player_deaths_for_season(season_id: int, player_id: int) -> int: + """Load all the aggregate count of all deaths for a player in a given season + :param season_id: ID of a season + :param player_id: ID of a player + :return: Number of deaths of the player in the season + """ deaths = 0 for episode in list(Season.get(Season.id == season_id).episodes): deaths = deaths + len([death for death in list(episode.deaths) if death.player.id == player_id]) @@ -16,7 +28,25 @@ def get_player_deaths_for_season(season_id: int, player_id: int) -> int: def get_player_victories_for_season(season_id: int, player_id: int) -> int: + """Load all the aggregate count of all victories for a player in a given season + :param season_id: ID of a season + :param player_id: ID of a player + :return: Number of all victories of the player in the season + """ victories = 0 for episode in list(Season.get(Season.id == season_id).episodes): victories = victories + len([vic for vic in list(episode.victories) if vic.player.id == player_id]) return victories + + +def create_tables(): + """Create all database tables""" + models = [Season, Episode, Player, Drink, Enemy, Death, Victory, Penalty, Episode.players.get_through_model()] + for model in models: + model.create_table() + + +def drop_tables(): + """Drop all data in database""" + models = [Season, Episode, Player, Drink, Enemy, Death, Victory, Penalty, Episode.players.get_through_model()] + db.drop_tables(models) \ No newline at end of file