Delete functions on client.

This commit is contained in:
luxick
2018-05-05 15:52:12 +02:00
parent b3eca219ac
commit cb8646bae2
14 changed files with 311 additions and 100 deletions

View File

@@ -9,8 +9,8 @@ import shutil
INTERPRETER = '/usr/bin/env python3'
CLIENT_VERSION = '0.1'
SERVER_VERSION = '0.1'
CLIENT_VERSION = '0.2'
SERVER_VERSION = '0.2'
try:
build_mode = sys.argv[1]

View File

@@ -5,10 +5,26 @@ try:
import cPickle as pickle
except ImportError:
import pickle
from dsst_gtk3 import gtk_ui
class Access:
def __init__(self, conn_dict):
def gui_handled(func):
def wrapper(*args, **kwargs):
self.app.ui.get_object('status_bar').push(0, 'Connecting to server')
try:
yield func(*args, **kwargs)
except Exception as e:
print(e)
self.app.ui.get_object('status_bar').push(0, str(e))
else:
self.app.ui.get_object('status_bar').push(0, '')
self.app.full_reload()
return wrapper
class DataClient:
def __init__(self, app: 'gtk_ui.GtkUi', conn_dict):
self.app = app
self.host = conn_dict.get('host')
self.port = conn_dict.get('port')
self.buffer = conn_dict.get('buffer_size')
@@ -32,10 +48,144 @@ class Access:
return message.get('data')
@gui_handled
def update_enemy(self, enemy: 'models.Enemy'):
self.send_request('update_enemy', enemy)
def update_player(self, player: 'models.Player'):
self.send_request('update_player', player)
def update_drink(self, drink: 'models.Drink'):
self.send_request('update_drink', drink)
def save_death(self, death: 'models.Death'):
self.send_request('save_death', death)
def delete_death(self, death_id: int):
with util.network_operation(self):
self.data_client.send_request('delete_death', death_id)
self.full_reload()
def save_victory(self, victory: 'models.Victory'):
with util.network_operation(self):
self.data_client.send_request('save_victory', victory)
self.full_reload()
def delete_victory(self, victory_id: int):
with util.network_operation(self):
self.data_client.send_request('delete_victory', victory_id)
self.full_reload()
def update_season(self, season: 'models.Season'):
with util.network_operation(self):
self.data_client.send_request('update_season', season)
self.seasons.valid = False
def update_episode(self, episode: 'models.Episode'):
with util.network_operation(self):
self.data_client.send_request('update_episode', episode)
self.episodes.valid = False
self.season_stats.valid = False
if __name__ == '__main__':
access = Access({'host': 'europa', 'port': 12345, 'buffer_size': 1024, 'auth_token': 'a'})
access = DataClient({'host': 'europa', 'port': 12345, 'buffer_size': 1024, 'auth_token': 'a'})
action = 'load_seasons'
response = access.send_request(action)
pp = pprint.PrettyPrinter(indent=1)
for s in response:
pp.pprint(s.__dict__)
import pprint
import socket
from common import util, models
from functools import wraps
try:
import cPickle as pickle
except ImportError:
import pickle
from dsst_gtk3 import gtk_ui
def reload_after_update(method):
""" Method decorator to handle GUI while updating data.
Adds error display in case of an exception and causes data reloading after an successful change.
:param method: The method to decorate
:return: The decorated function
"""
@wraps(method)
def wrapper(self, *args, **kwargs):
# Set info in statusbar
self.app.ui.get_object('status_bar').push(0, 'Connecting to server')
try:
method(self, *args, **kwargs)
except Exception as e:
print(e)
self.app.ui.get_object('status_bar').push(0, str(e))
else:
self.app.ui.get_object('status_bar').push(0, '')
# Cause local data to be reloaded, if method was executed successfully
self.app.full_reload()
return wrapper
class DataClient:
""" The access class for reading and writing data from and to the dsst API """
def __init__(self, app: 'gtk_ui.GtkUi', conn_dict):
self.app = app
self.host = conn_dict.get('host')
self.port = conn_dict.get('port')
self.buffer = conn_dict.get('buffer_size')
self.auth_token = conn_dict.get('auth_token')
def send_request(self, action: str, *args):
request = {'auth_token': self.auth_token,
'action': action,
'args': args}
request = pickle.dumps(request)
soc = socket.socket()
try:
soc.connect((self.host, self.port))
util.send_msg(soc, request)
message = util.recv_msg(soc)
message = pickle.loads(message)
if not message.get('success'):
raise Exception(message.get('message'))
finally:
soc.close()
return message.get('data')
@reload_after_update
def update_enemy(self, enemy: 'models.Enemy'):
self.send_request('update_enemy', enemy)
@reload_after_update
def update_player(self, player: 'models.Player'):
self.send_request('update_player', player)
@reload_after_update
def update_drink(self, drink: 'models.Drink'):
self.send_request('update_drink', drink)
@reload_after_update
def save_death(self, death: 'models.Death'):
self.send_request('save_death', death)
@reload_after_update
def delete_death(self, death_id: int):
self.send_request('delete_death', death_id)
@reload_after_update
def save_victory(self, victory: 'models.Victory'):
self.send_request('save_victory', victory)
@reload_after_update
def delete_victory(self, victory_id: int):
self.send_request('delete_victory', victory_id)
@reload_after_update
def update_season(self, season: 'models.Season'):
self.send_request('update_season', season)
@reload_after_update
def update_episode(self, episode: 'models.Episode'):
self.send_request('update_episode', episode)

View File

@@ -6,6 +6,7 @@ from dsst_gtk3.handlers import handlers
from dsst_gtk3 import util, reload, client
from common import models
class GtkUi:
""" The main UI class """
def __init__(self, config: dict):
@@ -28,7 +29,7 @@ class GtkUi:
self.ui.get_object('main_window').show_all()
# Connect to data server
config = config['servers'][0]
self.data_client = client.Access(config)
self.data_client = client.DataClient(self, config)
# Create local data caches
self.players = util.Cache()
self.drinks = util.Cache()
@@ -62,42 +63,6 @@ class GtkUi:
def reload(self):
pass
def update_enemy(self, enemy: 'models.Enemy'):
with util.network_operation(self):
self.data_client.send_request('update_enemy', enemy)
self.full_reload()
def update_player(self, player: 'models.Player'):
with util.network_operation(self):
self.data_client.send_request('update_player', player)
self.full_reload()
def update_drink(self, drink: 'models.Drink'):
with util.network_operation(self):
self.data_client.send_request('update_drink', drink)
self.full_reload()
def save_death(self, death: 'models.Death'):
with util.network_operation(self):
self.data_client.send_request('save_death', death)
self.full_reload()
def save_victory(self, victory: 'models.Victory'):
with util.network_operation(self):
self.data_client.send_request('save_victory', victory)
self.full_reload()
def update_season(self, season: 'models.Season'):
with util.network_operation(self):
self.data_client.send_request('update_season', season)
self.seasons.valid = False
def update_episode(self, episode: 'models.Episode'):
with util.network_operation(self):
self.data_client.send_request('update_episode', episode)
self.episodes.valid = False
self.season_stats.valid = False
def update_status_bar_meta(self):
self.ui.get_object('connection_label').set_text(self.meta.get('connection'))
self.ui.get_object('db_label').set_text(self.meta.get('database') or '')

View File

@@ -9,7 +9,7 @@ class BaseDataHandlers:
def do_add_player(self, entry):
if entry.get_text():
self.app.update_player(models.Player({'name': entry.get_text()}))
self.app.data_client.update_player(models.Player({'name': entry.get_text()}))
entry.set_text('')
def on_player_name_edited(self, _, index, value):
@@ -17,29 +17,29 @@ class BaseDataHandlers:
player = models.Player({'id': row[0],
'name': value,
'hex_id': row[2]})
self.app.update_player(player)
self.app.data_client.update_player(player)
def on_player_hex_edited(self, _, index, value):
row = self.app.ui.get_object('all_players_store')[index]
player = models.Player({'id': row[0],
'name': row[1],
'hex_id': value})
self.app.update_player(player)
self.app.data_client.update_player(player)
def do_add_drink(self, entry):
if entry.get_text():
drink = models.Drink({'name': entry.get_text(), 'vol': 0.00})
self.app.update_drink(drink)
self.app.data_client.update_drink(drink)
entry.set_text('')
def on_drink_name_edited(self, _, index, value):
row = self.app.ui.get_object('drink_store')[index]
drink = [d for d in self.app.drinks.data if d.id == row[0]][0]
drink.name = value
self.app.update_drink(drink)
self.app.data_client.update_drink(drink)
def on_drink_vol_edited(self, _, index, value):
row = self.app.ui.get_object('drink_store')[index]
drink = [d for d in self.app.drinks.data if d.id == row[0]][0]
drink.vol = value
self.app.update_drink(drink)
self.app.data_client.update_drink(drink)

View File

@@ -1,5 +1,5 @@
from gi.repository import Gtk
from dsst_gtk3 import dialogs, gtk_ui
from dsst_gtk3 import dialogs, gtk_ui, util
class DeathHandlers:
@@ -13,7 +13,18 @@ class DeathHandlers:
return
death = dialogs.create_death(self.app)
if death:
self.app.save_death(death)
self.app.data_client.save_death(death)
def on_penalty_drink_changed(self, _, path, text):
self.app.ui.get_object('player_penalties_store')[path][2] = text
def death_tree_clicked(self, widget, event):
if event.button == 3: # right click
popup = self.app.ui.get_object('p_death')
util.show_context_menu(widget, event, popup)
return True
def do_delete_death(self, *_):
death_id = util.get_tree_selection_value(self.app.ui.get_object('episode_deaths_tree_view'), 0)
self.app.data_client.delete_death(death_id)

View File

@@ -36,20 +36,20 @@ class DialogHandlers:
self.app.ui.get_object('enemy_optional_ckeck').set_active(False)
entry.set_text('')
self.app.update_enemy(enemy)
self.app.data_client.update_enemy(enemy)
def on_enemy_name_edited(self, _, index, value):
row = self.app.ui.get_object('enemy_season_store')[index]
enemy = [enemy for enemy in self.app.enemies.data if enemy.id == row[4]][0]
enemy.name = value
self.app.update_enemy(enemy)
self.app.data_client.update_enemy(enemy)
def on_enemy_optional_edited(self, renderer, index):
new_optional_value = not renderer.get_active()
row = self.app.ui.get_object('enemy_season_store')[index]
enemy = [enemy for enemy in self.app.enemies.data if enemy.id == row[4]][0]
enemy.boss = new_optional_value
self.app.update_enemy(enemy)
self.app.data_client.update_enemy(enemy)
def do_show_date_picker(self, entry: 'Gtk.Entry', *_):
dialog = self.app.ui.get_object('date_picker_dialog')

View File

@@ -9,7 +9,7 @@ class SeasonHandlers:
def do_add_season(self, *_):
season = dialogs.edit_season(self.app.ui)
if season:
self.app.update_season(season)
self.app.data_client.update_season(season)
self.app.full_reload()
def do_season_selected(self, *_):
@@ -23,7 +23,7 @@ class SeasonHandlers:
return
ep = dialogs.edit_episode(self.app, season_id)
if ep:
self.app.update_episode(ep)
self.app.data_client.update_episode(ep)
self.app.full_reload()
def on_selected_episode_changed(self, *_):

View File

@@ -1,5 +1,5 @@
from gi.repository import Gtk
from dsst_gtk3 import dialogs, gtk_ui
from dsst_gtk3 import dialogs, gtk_ui, util
class VictoryHandlers:
@@ -13,4 +13,14 @@ class VictoryHandlers:
return
victory = dialogs.create_victory(self.app)
if victory:
self.app.save_victory(victory)
self.app.data_client.save_victory(victory)
def victory_tree_clicked(self, widget, event):
if event.button == 3: # right click
popup = self.app.ui.get_object('p_victory')
util.show_context_menu(widget, event, popup)
return True
def do_delete_victory(self, *_):
victory_id = util.get_tree_selection_value(self.app.ui.get_object('episode_victories_tree_view'), 0)
self.app.data_client.delete_victory(victory_id)

View File

@@ -82,7 +82,7 @@ def reload_episode_stats(app: 'gtk_ui.GtkUi'):
penalties = ['{}x {}'.format(number, drink) for drink, number in Counter(penalties).items()]
penalty_string = ', '.join(penalties)
time_string = '{:02d}:{:02d}'.format(death.time.hour, death.time.minute)
store.append([death.id, death.player.name, death.enemy.name, penalty_string, time_string])
store.append([death.id, death.player.name, death.enemy.name, penalty_string, time_string, death.info])
# Reload victory store for notebook view
store = app.ui.get_object('episode_victories_store')
store.clear()

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.4 -->
<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.16"/>
<object class="GtkListStore" id="all_players_store">
@@ -53,6 +53,8 @@
<column type="gchararray"/>
<!-- column-name time -->
<column type="gchararray"/>
<!-- column-name info -->
<column type="gchararray"/>
</columns>
</object>
<object class="GtkListStore" id="episode_players_store">
@@ -101,6 +103,31 @@
<property name="step_increment">1</property>
<property name="page_increment">1</property>
</object>
<object class="GtkMenu" id="p_death">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkMenuItem" id="del_death">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Delete</property>
<property name="use_underline">True</property>
<signal name="activate" handler="do_delete_death" swapped="no"/>
</object>
</child>
</object>
<object class="GtkMenu" id="p_victory">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkMenuItem" id="del_victory">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Delete</property>
<signal name="activate" handler="do_delete_victory" swapped="no"/>
</object>
</child>
</object>
<object class="GtkListStore" id="player_penalties_store">
<columns>
<!-- column-name penalty_id -->
@@ -137,6 +164,9 @@
<property name="default_width">1200</property>
<property name="default_height">700</property>
<signal name="delete-event" handler="do_delete_event" swapped="no"/>
<child type="titlebar">
<placeholder/>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
@@ -710,12 +740,6 @@
<property name="top_attach">7</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
@@ -776,6 +800,7 @@
<property name="can_focus">True</property>
<property name="model">episode_deaths_store</property>
<property name="search_column">0</property>
<signal name="button-press-event" handler="death_tree_clicked" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
@@ -823,6 +848,17 @@
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Comment</property>
<child>
<object class="GtkCellRendererText"/>
<attributes>
<attribute name="text">5</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
@@ -856,6 +892,7 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">episode_victories_store</property>
<signal name="button-press-event" handler="victory_tree_clicked" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
@@ -1294,9 +1331,6 @@
</child>
</object>
</child>
<child type="titlebar">
<placeholder/>
</child>
</object>
<object class="GtkDialog" id="date_picker_dialog">
<property name="can_focus">False</property>
@@ -1306,6 +1340,9 @@
<property name="type_hint">dialog</property>
<property name="deletable">False</property>
<property name="transient_for">main_window</property>
<child>
<placeholder/>
</child>
<child internal-child="vbox">
<object class="GtkBox">
<property name="can_focus">False</property>
@@ -1408,9 +1445,6 @@
<action-widget response="-5">button3</action-widget>
<action-widget response="-6">button6</action-widget>
</action-widgets>
<child>
<placeholder/>
</child>
</object>
<object class="GtkDialog" id="edit_episode_dialog">
<property name="can_focus">False</property>
@@ -1420,6 +1454,9 @@
<property name="type_hint">dialog</property>
<property name="deletable">False</property>
<property name="transient_for">main_window</property>
<child>
<placeholder/>
</child>
<child internal-child="vbox">
<object class="GtkBox">
<property name="can_focus">False</property>
@@ -1716,9 +1753,6 @@
<action-widget response="-5">button1</action-widget>
<action-widget response="-6">button2</action-widget>
</action-widgets>
<child>
<placeholder/>
</child>
</object>
<object class="GtkDialog" id="edit_season_dialog">
<property name="can_focus">False</property>
@@ -1728,6 +1762,9 @@
<property name="deletable">False</property>
<property name="transient_for">main_window</property>
<property name="attached_to">main_window</property>
<child>
<placeholder/>
</child>
<child internal-child="vbox">
<object class="GtkBox">
<property name="can_focus">False</property>
@@ -1937,9 +1974,6 @@
<action-widget response="-5">button4</action-widget>
<action-widget response="-6">button5</action-widget>
</action-widgets>
<child>
<placeholder/>
</child>
</object>
<object class="GtkDialog" id="edit_victory_dialog">
<property name="can_focus">False</property>
@@ -1949,6 +1983,9 @@
<property name="type_hint">dialog</property>
<property name="deletable">False</property>
<property name="transient_for">main_window</property>
<child>
<placeholder/>
</child>
<child internal-child="vbox">
<object class="GtkBox">
<property name="can_focus">False</property>
@@ -2220,9 +2257,6 @@
<action-widget response="-5">okButtonRename5</action-widget>
<action-widget response="-6">cancelButtonRename1</action-widget>
</action-widgets>
<child>
<placeholder/>
</child>
</object>
<object class="GtkDialog" id="manage_drinks_dialog">
<property name="can_focus">False</property>
@@ -2233,6 +2267,9 @@
<property name="type_hint">dialog</property>
<property name="deletable">False</property>
<property name="transient_for">main_window</property>
<child>
<placeholder/>
</child>
<child internal-child="vbox">
<object class="GtkBox">
<property name="can_focus">False</property>
@@ -2390,9 +2427,6 @@
<action-widgets>
<action-widget response="-5">okButtonRename3</action-widget>
</action-widgets>
<child>
<placeholder/>
</child>
</object>
<object class="GtkDialog" id="manage_enemies_dialog">
<property name="can_focus">False</property>
@@ -2403,6 +2437,9 @@
<property name="type_hint">dialog</property>
<property name="deletable">False</property>
<property name="transient_for">main_window</property>
<child>
<placeholder/>
</child>
<child internal-child="vbox">
<object class="GtkBox">
<property name="can_focus">False</property>
@@ -2573,9 +2610,6 @@
<action-widgets>
<action-widget response="-5">okButtonRename1</action-widget>
</action-widgets>
<child>
<placeholder/>
</child>
</object>
<object class="GtkDialog" id="manage_players_dialog">
<property name="can_focus">False</property>
@@ -2586,6 +2620,9 @@
<property name="type_hint">dialog</property>
<property name="deletable">False</property>
<property name="transient_for">main_window</property>
<child>
<placeholder/>
</child>
<child internal-child="vbox">
<object class="GtkBox">
<property name="can_focus">False</property>
@@ -2742,9 +2779,6 @@
<action-widgets>
<action-widget response="-5">okButtonRename2</action-widget>
</action-widgets>
<child>
<placeholder/>
</child>
</object>
<object class="GtkAdjustment" id="shot_size_adjustment">
<property name="upper">100</property>
@@ -2759,6 +2793,9 @@
<property name="type_hint">dialog</property>
<property name="deletable">False</property>
<property name="transient_for">main_window</property>
<child>
<placeholder/>
</child>
<child internal-child="vbox">
<object class="GtkBox">
<property name="can_focus">False</property>
@@ -3120,8 +3157,5 @@
<action-widget response="-5">okButtonRename4</action-widget>
<action-widget response="-6">cancelButtonRename4</action-widget>
</action-widgets>
<child>
<placeholder/>
</child>
</object>
</interface>

View File

@@ -75,6 +75,17 @@ def get_combo_value(combo, index: int):
return -1
def get_tree_selection_value(tree: 'Gtk.TreeView', column: int):
""" Retrieve the a cell value of a tree view based on selected row and the choosen column
:param tree: Gtk.TreeView widget
:param column: Number of the column from which to retrieve the value
"""
(model, pathlist) = tree.get_selection().get_selected_rows()
for path in pathlist:
tree_iter = model.get_iter(path)
return model.get_value(tree_iter, 0)
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
@@ -147,3 +158,17 @@ def save_config(config: dict, config_path: str):
os.mkdir(path)
with open(config_path, 'wb') as file:
file.write(json.dumps(config, sort_keys=True, indent=4, separators=(',', ': ')).encode('utf-8'))
def show_context_menu(tree: 'Gtk.TreeView', event, popup: 'Gtk.Menu'):
path = tree.get_path_at_pos(int(event.x), int(event.y))
# Get the selection
selection = tree.get_selection()
# Get the selected path(s)
rows = selection.get_selected_rows()
# If not clicked on selection, change selected rows
if path:
if path[0] not in rows[1]:
selection.unselect_all()
selection.select_path(path[0])
popup.popup(None, None, None, None, 0, event.time)

View File

@@ -12,7 +12,7 @@ try:
from peewee import *
except ImportError:
print('peewee package not installed')
sys.exit(0)
sys.exit(1)
db = MySQLDatabase(None)
@@ -43,7 +43,7 @@ class Episode(Model):
number = CharField()
name = CharField(null=True)
date = DateField(null=True)
season = ForeignKeyField(Season, backref='episodes')
season = ForeignKeyField(Season, backref='episodes', on_delete='CASCADE')
players = ManyToManyField(Player, backref='episodes')
class Meta:
@@ -63,7 +63,7 @@ class Enemy(Model):
id = AutoField()
name = CharField()
boss = BooleanField()
season = ForeignKeyField(Season, backref='enemies')
season = ForeignKeyField(Season, backref='enemies', on_delete='CASCADE')
class Meta:
database = db
@@ -75,7 +75,7 @@ class Death(Model):
time = TimeField(default=datetime.time(0, 0))
player = ForeignKeyField(Player)
enemy = ForeignKeyField(Enemy)
episode = ForeignKeyField(Episode, backref='deaths')
episode = ForeignKeyField(Episode, backref='deaths', on_delete='CASCADE')
class Meta:
database = db
@@ -85,8 +85,8 @@ class Penalty(Model):
id = AutoField()
size = DecimalField()
drink = ForeignKeyField(Drink)
player = ForeignKeyField(Player, backref='penalties')
death = ForeignKeyField(Death, backref='penalties')
player = ForeignKeyField(Player, backref='penalties', on_delete='CASCADE')
death = ForeignKeyField(Death, backref='penalties', on_delete='CASCADE')
class Meta:
database = db
@@ -98,7 +98,7 @@ class Victory(Model):
time = TimeField(default=datetime.time(0, 0))
player = ForeignKeyField(Player)
enemy = ForeignKeyField(Enemy)
episode = ForeignKeyField(Episode, backref='victories')
episode = ForeignKeyField(Episode, backref='victories', on_delete='CASCADE')
class Meta:
database = db

View File

@@ -0,0 +1,15 @@
from dsst_server.data_access import sql
from dsst_server.auth import check_write
class DeleteFunctions:
@staticmethod
@check_write
def delete_death(death_id: int):
return sql.Death.delete().where(sql.Death.id == death_id).execute()
@staticmethod
@check_write
def delete_victory(victory_id: int):
return sql.Victory.delete().where(sql.Death.id == victory_id).execute()

View File

@@ -1,6 +1,7 @@
from dsst_server.func_write import WriteFunctions
from dsst_server.func_read import ReadFunctions
from dsst_server.func_delete import DeleteFunctions
class FunctionProxy(WriteFunctions, ReadFunctions):
class FunctionProxy(WriteFunctions, ReadFunctions, DeleteFunctions):
pass