diff --git a/db/mariadb.sql b/db/mariadb.sql index 67e1eb0..74017c0 100644 --- a/db/mariadb.sql +++ b/db/mariadb.sql @@ -484,3 +484,25 @@ CREATE TABLE signalk ( FOREIGN KEY fk_signalk_vessel (vid) REFERENCES vessel(vid) ) ENGINE=InnoDB; + +/* Token + - Python: secrets.token_hex(8) + - PHP: bin2hex(random_bytes(8)) */ +CREATE TABLE recordlock ( + lock_table varchar(40) NOT NULL, + lock_key int(10) NOT NULL, + token varchar(16) NOT NULL, + userid smallint(6) NOT NULL, + created_at timestamp NOT NULL DEFAULT current_timestamp, + last_seen timestamp NOT NULL DEFAULT current_timestamp, + PRIMARY KEY (lock_table, lock_key), + UNIQUE KEY ix_lock_token (token) +) ENGINE=MEMORY; + +/* Use with: UPDATE revision SET changed=CURRENT_TIMESTAMP WHERE revid=? */ +CREATE TABLE revision ( + revid smallint(6) UNSIGNED NOT NULL, + changed timestamp NOT NULL, + PRIMARY KEY (revid), + INDEX ix_revision_ts (changed) -- for select by timestamp +) ENGINE=MEMORY; diff --git a/db/minimal.sql b/db/minimal.sql index 27cb5d1..013e77f 100644 --- a/db/minimal.sql +++ b/db/minimal.sql @@ -11,12 +11,13 @@ UPDATE user SET userid=0 WHERE userid=1; INSERT INTO user (userid, login, pass, displayname, role) VALUES (1, 'captain', '$2y$10$naVMnC4S6bZ3IakxLJPJK.iSazlkYxVLeiRt9CB/j0jSExQZU1SCK', 'Captain', 'captain'); -INSERT INTO settings (userid, sno, valint) VALUES -(0, 0, 1), -- Database schema version -(0, 1, 0), -- No vessel assignment to SYSTEM -(1, 1, 0), -- No vessel assignment to user "captain" -(0, 20, 1280), -- Image width limit -(0, 21, 1024); -- Image height limit +INSERT INTO settings (userid, sno, valint, valstr) VALUES +(0, 0, 1, NULL), -- Database schema version +(0, 1, 0, NULL), -- No vessel assignment to SYSTEM +(1, 1, 0), NULL, -- No vessel assignment to user "captain" +(0, 20, 1280, NULL), -- Image width limit +(0, 21, 1024, NULL), -- Image height limit +(0, 4, NULL, '€'); -- Currency symbol /* setting descriptions */ INSERT INTO settings (userid, sno, valstr) VALUES @@ -24,6 +25,7 @@ INSERT INTO settings (userid, sno, valstr) VALUES (-1, 1, 'vessel id'), (-1, 2, 'date format'), (-1, 3, 'user menu'), +(-1, 4, 'currency symbol e.g. €, $, £, ¥'), (-1, 10, 'rows in table'), (-1, 20, 'maximum image width'), (-1, 21, 'maximum image height'), diff --git a/db/minimal_en.sql b/db/minimal_en.sql index 61331f5..7603dc3 100644 --- a/db/minimal_en.sql +++ b/db/minimal_en.sql @@ -11,18 +11,20 @@ UPDATE user SET userid=0 WHERE userid=1; INSERT INTO user (userid, login, pass, displayname, role) VALUES (1, 'captain', '$2y$10$naVMnC4S6bZ3IakxLJPJK.iSazlkYxVLeiRt9CB/j0jSExQZU1SCK', 'Captain', 'captain'); -INSERT INTO settings (userid, sno, valint) VALUES -(0, 0, 1), -- Database schema version -(0, 1, 0), -- No vessel assignment to SYSTEM -(1, 1, 0), -- No vessel assignment to user "captain" -(0, 20, 1280), -- Image width limit -(0, 21, 1024); -- Image height limit +INSERT INTO settings (userid, sno, valint, valstr) VALUES +(0, 0, 1, NULL), -- Database schema version +(0, 1, 0, NULL), -- No vessel assignment to SYSTEM +(1, 1, 0), NULL, -- No vessel assignment to user "captain" +(0, 20, 1280, NULL), -- Image width limit +(0, 21, 1024, NULL), -- Image height limit +(0, 4, NULL, '€'); -- Currency symbol /* setting descriptions */ INSERT INTO settings (userid, sno, valstr) VALUES (-1, 1, 'vessel id'), (-1, 2, 'date format'), (-1, 3, 'user menu'), +(-1, 4, 'currency symbol e.g. €, $, £, ¥'), (-1, 10, 'rows in table'), (-1, 20, 'maximum image width'), (-1, 21, 'maximum image height'), @@ -63,7 +65,7 @@ INSERT INTO dropdown (ddid, ddval, ddtext) VALUES (0, 5, 'provision category'), (0, 6, 'provision unit'), (0, 7, 'fuse type'), -(0, 8 , 'unit'), +(0, 8, 'unit'), (0, 9, 'box types'), (0, 10, 'fuse blocks'), (0, 11, 'cable types'), diff --git a/yms/database.py b/yms/database.py index 78ee8bf..3b6b479 100644 --- a/yms/database.py +++ b/yms/database.py @@ -1,6 +1,7 @@ import MySQLdb +import secrets -class Database(): +class Database: def __init__(self, hostname, dbname): self.hostname = hostname @@ -48,6 +49,10 @@ class Database(): def is_connected(self): return self.conn.open + def get_timestamp(self): + self.cur.execute("SELECT CURRENT_TIMESTAMP") + return self.cur.fetchone()[0] + def last_error(self): if not self.error: return(0, '') @@ -56,6 +61,70 @@ class Database(): self.error = None return(code, text) + def load_enum(self, table, column): + """ + TODO this is only an example! perhaps other keys needed + Load an enum directly from a database table and provide it as a + dictionary for use in, for example, selection lists. + """ + sql = ( + "SELECT TRIM(TRAILING ')' FROM SUBSTRING(column_type,6)) " + "FROM information_schema.columns " + "WHERE table_schema=DATABASE() AND table_name=%s AND column_name=%s AND data_type='enum'" + ) + self.cur.execute(sql, (table, column)) + row = self.cur.fetchone() + if not row: + return {} + return { + i: _(value.strip("'")) + for i, value in enumerate(row[0].split(','), 1) + } + + def lock_acquire(self, table, key, userid): + token = secrets.token_hex(8) + # first try new lock + sql = ( + "INSERT INTO recordlock (lock_table, lock_key, token, userid)" + "VALUES (%s, %s, %s, %s)" + ) + try: + self.cur.execute(sql, (table, key, token, userid)) + self.conn.commit() + return token + except self.conn.IntegrityError as e: + self.conn.rollback() + if e.errno != 1062: + raise + # lazy locking: lock exists but is old, so we use it + sql = ( + "UPDATE recordlock " + "SET token=%s, userid=%s," + " created_at=CURRENT_TIMESTAMP, last_seen=CURRENT_TIMESTAMP " + "WHERE lock_table=%s AND lock_key=%s AND last_seen + + + + + True + False + 8 + + + True + False + vertical + + + True + True + + + + True + False + + + True + False + vertical + start + + + Clear + True + True + True + + + True + True + 0 + + + + + Apply + True + True + True + + + True + True + 1 + + + + + 2 + 0 + 3 + + + + + True + False + Name + + + 0 + 0 + + + + + True + False + Status + + + 0 + 1 + + + + + True + False + Remarks + + + 0 + 2 + + + + + True + True + + + 1 + 0 + + + + + True + True + + + 1 + 2 + + + + + True + False + + + 1 + 1 + + + + + + + True + False + Filter Cables + + + + + False + True + 0 + + + + + True + True + in + + + True + True + + + + + + + + True + True + 1 + + + + + True + False + end + + + New + True + True + True + + + + True + True + 0 + + + + + Edit + True + True + True + + + + True + True + 1 + + + + + Details + True + True + True + + + + True + True + 2 + + + + + False + True + 2 + + + + + page0 + page0 + + + + + True + False + vertical + + + True + False + Cable Details + + + + + + False + True + 0 + + + + + + True + False + 4 + 4 + + + True + False + Name + + + 0 + 0 + + + + + True + False + Platzhalter + + + 1 + 0 + + + + + True + False + Type + + + 0 + 1 + + + + + True + False + Tech.-name + 0 + + + 0 + 2 + + + + + True + False + Length + + + 0 + 3 + + + + + True + False + Conductors + + + 0 + 4 + + + + + True + False + Diameter + + + 0 + 5 + + + + + True + False + Cross section + + + 0 + 6 + + + + + True + False + Weight + + + 0 + 7 + + + + + True + False + Color + + + 0 + 8 + + + + + True + False + Supplier + + + 0 + 9 + + + + + True + False + Manufacturer + + + 0 + 10 + + + + + True + False + Condition + + + 0 + 11 + + + + + True + False + Remarks + + + 0 + 12 + + + + + True + False + + + 1 + 1 + + + + + True + False + + + 1 + 2 + + + + + True + False + + + 1 + 3 + + + + + True + False + + + 1 + 4 + + + + + True + False + + + 1 + 5 + + + + + True + False + + + 1 + 6 + + + + + True + False + + + 1 + 7 + + + + + True + False + + + 1 + 8 + + + + + True + False + + + 1 + 9 + + + + + True + False + + + 1 + 10 + + + + + True + False + + + 1 + 11 + + + + + True + False + + + 1 + 12 + + + + + True + True + 1 + + + + + True + False + end + + + Delete + True + True + True + + + + True + True + 0 + True + + + + + Edit + True + True + True + + + + True + True + 1 + + + + + Close + True + True + True + + + + True + True + 2 + + + + + False + True + 2 + + + + + page1 + page1 + 1 + + + + + True + False + vertical + + + True + False + label + + + False + True + 0 + + + + + + True + False + + + True + False + Name + + + 0 + 0 + + + + + True + True + + + 1 + 0 + + + + + True + False + Type + + + 0 + 1 + + + + + True + False + Tech.-name + + + 0 + 2 + + + + + True + False + Length + + + 0 + 3 + + + + + True + False + Conductors + + + 0 + 4 + + + + + True + False + Diameter + + + 0 + 5 + + + + + True + False + Cross section + + + 0 + 6 + + + + + True + False + Weight + + + 0 + 7 + + + + + True + False + Color + + + 0 + 8 + + + + + True + False + Supplier + + + 0 + 9 + + + + + True + False + Manufacturer + + + 0 + 10 + + + + + True + False + Condition + + + 0 + 11 + + + + + True + False + Remarks + + + 0 + 12 + + + + + True + False + + + 1 + 1 + + + + + True + False + + + 1 + 2 + + + + + True + True + + + 1 + 3 + + + + + True + True + + + 1 + 4 + + + + + True + True + + + 1 + 5 + + + + + True + True + + + 1 + 6 + + + + + True + True + + + 1 + 7 + + + + + True + True + + + 1 + 8 + + + + + True + False + + + 1 + 9 + + + + + True + False + + + 1 + 10 + + + + + True + False + + + 1 + 11 + + + + + True + True + in + + + True + True + + + + + 1 + 12 + + + + + True + True + 1 + + + + + True + False + end + + + + + + Save + True + True + True + + + + True + True + 1 + + + + + Cancel + True + True + True + + + + True + True + 2 + + + + + False + True + 2 + + + + + page2 + page2 + 2 + + + + diff --git a/yms/ui/equipment.ui b/yms/ui/equipment.ui index edaf6fe..316ebb1 100644 --- a/yms/ui/equipment.ui +++ b/yms/ui/equipment.ui @@ -230,6 +230,7 @@ True True + diff --git a/yms/ui/fuses.ui b/yms/ui/fuses.ui new file mode 100644 index 0000000..89dbb9c --- /dev/null +++ b/yms/ui/fuses.ui @@ -0,0 +1,757 @@ + + + + + + 500 + 1 + 10 + + + 10000 + 1 + 10 + + + True + False + 8 + + + True + False + vertical + + + True + True + + + + True + False + + + True + False + vertical + start + + + Clear + True + True + True + + + True + True + 0 + + + + + Apply + True + True + True + + + True + True + 1 + + + + + 2 + 0 + 3 + + + + + True + False + Name + + + 0 + 0 + + + + + True + False + Status + + + 0 + 1 + + + + + True + False + Remarks + + + 0 + 2 + + + + + True + True + + + 1 + 0 + + + + + True + True + + + 1 + 2 + + + + + True + False + + + 1 + 1 + + + + + + + True + False + Filter Fuses + + + + + False + True + 0 + + + + + True + True + in + + + True + True + + + + + + + + True + True + 1 + + + + + True + False + end + + + New + True + True + True + + + + True + True + 0 + + + + + Edit + True + True + True + + + + True + True + 1 + + + + + Details + True + True + True + + + + True + True + 2 + + + + + False + True + 2 + + + + + page0 + page0 + + + + + True + False + vertical + + + True + False + Fuse Details + + + + + + False + True + 0 + + + + + + True + False + 4 + 4 + + + True + False + Platzhalter + + + 1 + 0 + + + + + True + False + Number + + + 0 + 0 + + + + + True + False + Type + + + 0 + 1 + + + + + True + False + Current + + + 0 + 2 + + + + + True + False + Cable + + + 0 + 3 + + + + + True + False + Equipment + + + 0 + 4 + + + + + True + False + Location + + + 0 + 5 + + + + + True + False + Description + + + 0 + 6 + + + + + True + False + Remarks + + + 0 + 7 + + + + + True + False + + + 1 + 1 + + + + + True + False + + + 1 + 5 + + + + + True + False + + + 1 + 6 + + + + + True + False + + + 1 + 7 + + + + + True + False + + + 1 + 4 + + + + + True + False + + + 1 + 2 + + + + + True + False + + + 1 + 3 + + + + + True + True + 1 + + + + + True + False + end + + + Delete + True + True + True + + + + True + True + 0 + True + + + + + Edit + True + True + True + + + + True + True + 1 + + + + + Close + True + True + True + + + + True + True + 2 + + + + + False + True + 2 + + + + + page1 + page1 + 1 + + + + + True + False + vertical + + + True + False + Fuse Details + + + + + + False + True + 0 + + + + + + True + False + + + True + False + Number + + + 0 + 0 + + + + + True + False + Description + + + 0 + 1 + + + + + True + False + Type + + + 0 + 2 + + + + + True + False + Current + + + 0 + 3 + + + + + True + False + Cable + + + 0 + 4 + + + + + True + False + Equipment + + + 0 + 5 + + + + + True + False + Location + + + 0 + 6 + + + + + True + False + Remarks + + + 0 + 7 + + + + + True + True + adj_fnumber + + + 1 + 0 + + + + + True + True + + + 1 + 1 + + + + + True + False + + + 1 + 2 + + + + + True + True + adj_current + + + 1 + 3 + + + + + True + False + + + 1 + 4 + + + + + True + False + + + 1 + 5 + + + + + True + True + + + 1 + 6 + + + + + True + True + in + + + True + True + + + + + 1 + 7 + + + + + False + True + 1 + + + + + True + False + end + + + + + + Save + True + True + True + + + + True + True + 1 + + + + + Cancel + True + True + True + + + + True + True + 2 + + + + + False + True + 2 + + + + + page2 + page2 + 2 + + + + diff --git a/yms/ui/inventory.ui b/yms/ui/inventory.ui index 8cd6560..d28a405 100644 --- a/yms/ui/inventory.ui +++ b/yms/ui/inventory.ui @@ -162,6 +162,7 @@ True True + @@ -186,6 +187,7 @@ True True True + True @@ -199,6 +201,7 @@ True True True + True @@ -212,6 +215,7 @@ True True True + True @@ -242,6 +246,9 @@ True False Inventory details + + + False @@ -250,7 +257,7 @@ - + True False @@ -387,7 +394,7 @@ - + True False @@ -486,39 +493,6 @@ 10 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True @@ -537,6 +511,7 @@ True True True + True @@ -593,7 +568,19 @@ False vertical - + + True + False + Edit inventory + + + + + + False + True + 0 + @@ -901,6 +888,7 @@ True True True + True diff --git a/yms/view/cables.py b/yms/view/cables.py new file mode 100644 index 0000000..9a20a80 --- /dev/null +++ b/yms/view/cables.py @@ -0,0 +1,64 @@ +# SPDX-License-Identifier: WTFPL + +import os +import gi +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk + +from .view import View +from yms import gtkutils, combobox, treeview +from yms.model import cable + +class CablesView(View): + + def __init__(self, context): + super().__init__(context, _("Cables"), "cables.ui") + + self.cable = cable.Cable(context.db) # current record + self.newrecord = False + + self.lst_cables = Gtk.ListStore(int, str, str) + sql = ( + "SELECT cableid, cablename, cablecond " + "FROM cable " + "WHERE vid=%s " + "ORDER BY cableid" + ) + self.db.dcur.execute(sql, (context.vessel.vid,)) + for row in self.db.dcur.fetchall(): + data = ( + row['cableid'], + row['cablename'], + row['cablecond'] + ) + self.lst_cables.append(data) + + treeview.add_column(self.tree['cables'], _('ID'), 0) + treeview.add_column(self.tree['cables'], _('Name'), 1) + treeview.add_column(self.tree['cables'], _('Condition'), 2) + self.tree['cables'].set_model(self.lst_cables) + + # buttons on list page + def on_but_new_clicked(self, widget): + self.topwidget.set_visible_child(self.box['cable_edit']) + + def on_but_edit_clicked(self, widget): + self.topwidget.set_visible_child(self.box['cable_edit']) + + def on_but_detail_clicked(self, widget): + self.topwidget.set_visible_child(self.box['cable_detail']) + + # buttons on detail page + # edit2 uses edit callback + def on_but_close_clicked(self, widget): + self.topwidget.set_visible_child(self.box['cables']) + + def on_but_delete_clicked(self, widget): + self.topwidget.set_visible_child(self.box['cables']) + + # buttons on edit page + def on_but_save_clicked(self, widget): + pass + + def on_but_cancel_clicked(self, widget): + self.topwidget.set_visible_child(self.box['cables']) diff --git a/yms/view/equipment.py b/yms/view/equipment.py index 78c69e2..fc8dd8f 100644 --- a/yms/view/equipment.py +++ b/yms/view/equipment.py @@ -46,6 +46,15 @@ class EquipmentView(View): self.topwidget.set_visible_child(self.box['equip_edit']) def on_but_detail_clicked(self, widget): + eid = treeview.get_value(self.tree['equipment'], 0) + self.equip.load(eid) + self.display_record() + self.topwidget.set_visible_child(self.box['equip_detail']) + + def on_tvw_equipment_row_activated(self, widget, path, column): + eid = treeview.get_value(widget, 0) + self.equip.load(eid) + self.display_record() self.topwidget.set_visible_child(self.box['equip_detail']) # buttons on detail page @@ -62,3 +71,15 @@ class EquipmentView(View): def on_but_cancel_clicked(self, widget): self.topwidget.set_visible_child(self.box['equipment']) + + def display_record(self): + print(self.equip) + #self.label['name'].set_text(self.equip.sname) + self.label['remarks'].set_text(self.equip.remarks or '') + + def edit_record(self): + self.entry['sname'].set_text(self.storage.sname) + self.spin['x'].set_value(self.storage.x) + self.spin['y'].set_value(self.storage.y) + self.spin['z'].set_value(self.storage.z) + self.text['remarks'].get_buffer().set_text(self.storage.remarks or '') diff --git a/yms/view/fuses.py b/yms/view/fuses.py new file mode 100644 index 0000000..b20ee2c --- /dev/null +++ b/yms/view/fuses.py @@ -0,0 +1,66 @@ +# SPDX-License-Identifier: WTFPL + +import os +import gi +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk + +from .view import View +from yms import gtkutils, combobox, treeview +from yms.model import fuse + +class FusesView(View): + + def __init__(self, context): + super().__init__(context, _("Fuses"), "fuses.ui") + + self.fuse = fuse.Fuse(context.db) # current record + self.newrecord = False + + self.lst_fuses = Gtk.ListStore(int, str, str, str) + sql = ( + "SELECT fuseid, fnumber, current, description " + "FROM fuse " + "WHERE vid=%s " + "ORDER BY fuseid" + ) + self.db.dcur.execute(sql, (context.vessel.vid,)) + for row in self.db.dcur.fetchall(): + data = ( + row['fuseid'], + 'F{}'.format(row['fnumber']), + '{} A'.format(row['current']), # TODO better formatting + row['description'] + ) + self.lst_fuses.append(data) + + treeview.add_column(self.tree['fuses'], _('ID'), 0) + treeview.add_column(self.tree['fuses'], _('Name'), 1) + treeview.add_column(self.tree['fuses'], _('Current'), 2) + treeview.add_column(self.tree['fuses'], _('Description'), 3) + self.tree['fuses'].set_model(self.lst_fuses) + + # buttons on list page + def on_but_new_clicked(self, widget): + self.topwidget.set_visible_child(self.box['fuse_edit']) + + def on_but_edit_clicked(self, widget): + self.topwidget.set_visible_child(self.box['fuse_edit']) + + def on_but_detail_clicked(self, widget): + self.topwidget.set_visible_child(self.box['fuse_detail']) + + # buttons on detail page + # edit2 uses edit callback + def on_but_close_clicked(self, widget): + self.topwidget.set_visible_child(self.box['fuses']) + + def on_but_delete_clicked(self, widget): + self.topwidget.set_visible_child(self.box['fuses']) + + # buttons on edit page + def on_but_save_clicked(self, widget): + pass + + def on_but_cancel_clicked(self, widget): + self.topwidget.set_visible_child(self.box['fuses']) diff --git a/yms/view/inventory.py b/yms/view/inventory.py index b925662..e866785 100644 --- a/yms/view/inventory.py +++ b/yms/view/inventory.py @@ -49,6 +49,15 @@ class InventoryView(View): self.topwidget.set_visible_child(self.box['inv_edit']) def on_but_detail_clicked(self, widget): + invid = treeview.get_value(self.tree['inventory'], 0) + self.inv.load(invid) + self.display_record() + self.topwidget.set_visible_child(self.box['inv_detail']) + + def on_tvw_inventory_row_activated(self, widget, path, column): + invid = treeview.get_value(widget, 0) + self.inv.load(invid) + self.display_record() self.topwidget.set_visible_child(self.box['inv_detail']) # buttons on detail page @@ -65,3 +74,26 @@ class InventoryView(View): def on_but_cancel_clicked(self, widget): self.topwidget.set_visible_child(self.box['inventory']) + + def display_record(self): + # TODO nice formatting + self.label['name'].set_text(self.inv.invname) + self.label['conttype'].set_text(self.inv.conttype) + self.label['storage'].set_text(str(self.inv.sid)) + self.label['box'].set_text(str(self.inv.boxid)) + self.label['equipment'].set_text(str(self.inv.eid)) + self.label['number'].set_text(self.format_int(self.inv.number)) + self.label['weight'].set_text(self.format_float(self.inv.weight, suffix='kg')) + self.label['price'].set_text(self.format_float(self.inv.price, suffix='€')) + self.label['purchdate'].set_text(self.format_date(self.inv.purchdate)) + self.label['condition'].set_text(self.inv.invcond) + self.label['remarks'].set_text(self.inv.remarks or '') + + def edit_record(self): + self.entry['user_login'].set_text(user.login) + self.entry['user_displayname'].set_text(user.displayname) + combobox.activate_row(self.combo['user_role'], user.role) + combobox.activate_row(self.combo['user_lang'], user.language) + self.check['user_locked'].set_active('locked' in user.flags) + self.check['user_deleted'].set_active('deleted' in user.flags) + self.entry['user_pass'].set_text('') diff --git a/yms/view/view.py b/yms/view/view.py index 222e49c..5161323 100644 --- a/yms/view/view.py +++ b/yms/view/view.py @@ -6,6 +6,7 @@ Generic view object SPDX-License-Identifier: WTFPL """ import os +import locale import gi gi.require_version("Gtk", "3.0") from gi.repository import GLib, Gtk, Gdk @@ -26,10 +27,26 @@ class View: self.topwidget = builder.get_object("topobject") gtkutils.init_named_controls(self, builder) - def format_date(self, dateval): - if dateval is None: + def format_int(self, ival, separator=True, suffix=None): + if ival is None: return '' - return dateval.strftime(self.context.datefmt) + formatted = locale.format_string("%d", ival, separator) + if suffix: + formatted += f"\u00A0{suffix}" + return formatted + + def format_float(self, fval, digits=2, grouping=True, suffix=None): + if fval is None: + return '' + formatted = locale.format_string(f"%.{digits}f", fval, grouping) + if suffix: + formatted += f"\u00A0{suffix}" + return formatted + + def format_date(self, dval): + if dval is None: + return '' + return dval.strftime(self.context.datefmt) def on_textview_keypress(self, widget, event): # Generic limit length function diff --git a/ymsgui.py b/ymsgui.py index 6178c58..02dddd0 100755 --- a/ymsgui.py +++ b/ymsgui.py @@ -45,6 +45,8 @@ from yms.view.inventory import InventoryView from yms.view.provisions import ProvisionsView from yms.view.maintenance import MaintenanceView from yms.view.projects import ProjectsView +from yms.view.cables import CablesView +from yms.view.fuses import FusesView from yms.view.documents import DocumentsView from yms.view.status import StatusView @@ -107,12 +109,15 @@ class AppContext: class Frontend: def __init__(self, context): + self.db = context.db self.modules = { "base": BaseView(context), "storage": StorageView(context), "equipment": EquipmentView(context), "inventory": InventoryView(context), "provisions": ProvisionsView(context), + "cables": CablesView(context), + "fuses": FusesView(context), "maintenance": MaintenanceView(context), "projects": ProjectsView(context), "documents": DocumentsView(context), @@ -127,6 +132,10 @@ class Frontend: self.notebook = builder.get_object("ntb_modules") for module in self.modules.values(): self.notebook.append_page(module.topwidget, Gtk.Label(label=module.title)) + + self.db_poll_ts = context.db.get_timestamp() + GLib.timeout_add_seconds(30, self.db_change_poller) + print("Frontend initialized.") def run(self): @@ -134,6 +143,13 @@ class Frontend: self.window.show_all() Gtk.main() + def db_change_poller(self): + # get all list ids which changes since last query + print("db list change poller") + #sql = "SELECT revid FROM revision WHERE changed>%s" + self.db_poll_ts = self.db.get_timestamp() + return True + def on_ntb_modules_switch_page(self, notebook, page, page_num): print("Neue Seite:", page_num) # TODO here re-load lists