Work on native gui: views and database model

This commit is contained in:
2026-09-09 12:20:57 +02:00
parent 66f017567e
commit b21a6beffd
35 changed files with 5649 additions and 3266 deletions
View File
+14
View File
@@ -0,0 +1,14 @@
# SPDX-License-Identifier: WTFPL
from .dbrecord import DbRecord
class Box(DbRecord):
table = "box"
pk_field = "boxid"
fields = ('boxid', 'sid', 'boxtype', 'parent', 'label', 'color', 'content',
'weight', 'remarks')
def __init__(self, db, boxid=None):
super().__init__(db, boxid)
self.title = _("Box({})")
+15
View File
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: WTFPL
from .dbrecord import DbRecord
class Cable(DbRecord):
table = "cable"
pk_field = "cableid"
fields = ('cableid', 'vid', 'cablename', 'cabletype', 'techname', 'length',
'conductors', 'diameter', 'xsection', 'weight', 'color', 'supplier',
'manufacturer', 'cablecond', 'remarks')
def __init__(self, db, cableid=None):
super().__init__(db, cableid)
self.title = _("Cable({})")
+15
View File
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: WTFPL
from .dbrecord import DbRecord
class Company(DbRecord):
table = "company"
pk_field = "compid"
fields = ('compid', 'compname', 'comptype', 'comptype2', 'shortname',
'street', 'zip', 'city', 'country', 'contact', 'phone', 'email',
'web', 'customerno', 'contractno', 'remarks', 'flags' )
def __init__(self, db, compid=None):
super().__init__(db, compid)
self.title = _("Company({})")
+137
View File
@@ -0,0 +1,137 @@
# SPDX-License-Identifier: WTFPL
"""
No commits here: Has to be done elsewhere to support transactions!
"""
from datetime import datetime
class DbRecord:
def __init__(self, db, record_id=None):
if not hasattr(self, "table"):
raise TypeError("DbRecord subclass must define 'table'")
if not hasattr(self, "pk_field"):
raise TypeError("DbRecord subclass must define 'pk_field'")
if not hasattr(self, "fields"):
raise TypeError("DbRecord subclass must define 'fields'")
self.db = db
self.dcur = db.dcur
self.conn = db.conn
self.last_update = None
self.title = _("DbRecord({})") # for pretty print
if record_id is not None:
self.pk_value = int(record_id)
self.load()
else:
self.pk_value = None
self.clear()
def clear(self):
for field in self.fields:
setattr(self, field, None)
self.data = {} # for old/new checks
def load(self, record_id=None):
if record_id is not None:
self.pk_value = int(record_id)
if self.pk_value is None:
self.clear()
return
sql = ("SELECT {} FROM {} WHERE {}=%s").format(', '.join(self.fields), self.table, self.pk_field)
try:
self.dcur.execute(sql, (self.pk_value,))
self.data = self.dcur.fetchone()
except Exception as e:
self.db.error = e
self.clear()
return False
if self.data:
for field, value in self.data.items():
setattr(self, field, value)
return True
else:
self.clear()
return False
def save(self):
self.last_update = datetime.now()
if self.pk_value is not None:
return self._update()
else:
return self._insert()
def _update(self):
sqlfield = []
values = []
for field in self.fields:
newdata = getattr(self, field)
if self.data[field] != newdata:
sqlfield.append("{}=%s".format(field))
values.append(newdata)
if values:
# only save if there are changes
sql = (
f"UPDATE {self.table} SET " + ",".join(sqlfield) +
f" WHERE {self.pk_field}=%s"
)
values.append(self.pk_value)
try:
self.dcur.execute(sql, values)
except Exception as e:
self.db.error = e
return False
for field in self.fields:
self.data[field] = getattr(self, field)
return True
def _insert(self):
sql = (
"INSERT INTO {} ({}) "
"VALUES ({})"
).format(
self.table,
','.join(self.fields),
','.join(["%s"] * len(self.fields))
)
values = []
for field in self.fields:
newdata = getattr(self, field)
values.append(newdata)
self.data[field] = newdata
try:
self.dcur.execute(sql, values)
except Exception as e:
self.db.error = e
return False
self.pk_value = self.dcur.lastrowid
return True
def delete(self, record_id=None):
if record_id is None:
if self.pk_value is None:
return False
record_id = self.pk_value
sql = "DELETE FROM {} WHERE {}=%s".format(self.table, self.pk_field)
try:
self.dcur.execute(sql, (record_id,))
except Exception as e:
self.db.error = e
return False
if self.dcur.rowcount != 1:
return False
if self.pk_value == record_id:
self.pk_value = None
self.clear()
return True
def __str__(self):
out = [self.title.format(self.pk_value)]
for field in self.fields:
value = getattr(self, field, None)
out.append(" {:15}: {}".format(field, value))
return "\n".join(out)
+15
View File
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: WTFPL
from .dbrecord import DbRecord
class Document(DbRecord):
table = "document"
pk_field = "docid"
fields = ('docid', 'doctype', 'mimetype', 'filename', 'extension',
'hash', 'docsize', 'doctime', 'docdate', 'title', 'pages', 'remarks',
'roleaccess')
def __init__(self, db, dicid=None):
super().__init__(db, docid)
self.title = _("Document({})")
+15
View File
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: WTFPL
from .dbrecord import DbRecord
class Equipment(DbRecord):
table = "equipment"
pk_field = "eid"
fields = ('eid', 'vid' 'ename', 'shortname', 'model', 'ecat', 'serial',
'supplier', 'manufacturer', 'purchdate', 'price', 'weight', 'remarks',
'flags')
def __init__(self, db, eid=None):
super().__init__(db, eid)
self.title = _("Equipment({})")
+14
View File
@@ -0,0 +1,14 @@
# SPDX-License-Identifier: WTFPL
from .dbrecord import DbRecord
class Fuse(DbRecord):
table = "fuse"
pk_field = "fuseid"
fields = ('fuseid', 'vid', 'fnumber', 'ftype', 'current', 'cableid',
'eid', 'location', 'description', 'remarks')
def __init__(self, db, cableid=None):
super().__init__(db, fuseid)
self.title = _("Fuse({})")
+14
View File
@@ -0,0 +1,14 @@
# SPDX-License-Identifier: WTFPL
from .dbrecord import DbRecord
class Inventory(DbRecord):
table = "inventory"
pk_field = "invid"
fields = ('invid', 'invname', 'conttype', 'sid', 'boxid', 'eid', 'number',
'weight', 'price', 'purchdate', 'invcond', 'remarks', 'flags')
def __init__(self, db, invid=None):
super().__init__(db, invid)
self.title = _("Inventory({})")
+15
View File
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: WTFPL
from .dbrecord import DbRecord
class Maintenance(DbRecord):
table = "maintenance"
pk_field = "maintid"
fields = ('maintid', 'vid', 'eid', 'series', 'activities', 'plan_date',
'completion_date', 'interval_hours', 'last_hours', 'remarks',
'maintstate')
def __init__(self, db, maintid=None):
super().__init__(db, maintid)
self.title = _("Maintenance({})")
+14
View File
@@ -0,0 +1,14 @@
# SPDX-License-Identifier: WTFPL
from .dbrecord import DbRecord
class Measurement(DbRecord):
table = "measurement"
pk_field = "mid"
fields = ('mid', 'mname', 'vid', 'eid', 'nval', 'val1', 'val2', 'val3',
'unit', 'accuracy', 'mdate', 'note')
def __init__(self, db, mid=None):
super().__init__(db, mid)
self.title = _("Measurement({})")
+14
View File
@@ -0,0 +1,14 @@
# SPDX-License-Identifier: WTFPL
from .dbrecord import DbRecord
class Project(DbRecord):
table = "project"
pk_field = "prokid"
fields = ('projid', 'projname', 'vid', 'responsible', 'startdate',
'duration', 'costs_plan', 'costs_final', 'remarks', 'projstate')
def __init__(self, db, projid=None):
super().__init__(db, projid)
self.title = _("Project({})")
+15
View File
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: WTFPL
from .dbrecord import DbRecord
class Provision(DbRecord):
table = "provisions"
pk_field = "provid"
fields = ('provid', 'provname', 'provcat', 'sid', 'boxid', 'number',
'target', 'weight', 'weight_net', 'unit', 'calories', 'storedate',
'shelflife', 'price', 'remarks')
def __init__(self, db, provid=None):
super().__init__(db, provid)
self.title = _("Provision({})")
+14
View File
@@ -0,0 +1,14 @@
# SPDX-License-Identifier: WTFPL
from .dbrecord import DbRecord
class Storage(DbRecord):
table = "storage"
pk_field = "sid"
fields = ('sid', 'vid', 'sname', 'stype', 'capacity', 'capaunit',
'x', 'y', 'z', 'wx', 'wy', 'wz', 'angle', 'color', 'remarks')
def __init__(self, db, sid=None):
super().__init__(db, sid)
self.title = _("Storage({})")
+14
View File
@@ -0,0 +1,14 @@
# SPDX-License-Identifier: WTFPL
from .dbrecord import DbRecord
class Task(DbRecord):
table = "task"
pk_field = "taskid"
fields = ('taskid', 'vid', 'projid', 'taskname', 'priority', 'taskstate',
'state_ts', 'plandate', 'duedate', 'started', 'finished', 'responsible')
def __init__(self, db, taskid=None):
super().__init__(db, taskid)
self.title = _("Task({})")
+136
View File
@@ -0,0 +1,136 @@
# SPDX-License-Identifier: WTFPL
import MySQLdb.cursors
import bcrypt
import random
import string
class User(object):
@classmethod
def genpass(cls, length=12):
return ''.join(random.choices(string.ascii_letters + string.digits, k=length))
def __init__(self, db, user_id=None):
self.conn = db.conn
self.cur = db.conn.cursor(MySQLdb.cursors.DictCursor)
if user_id is not None:
self.user_id = int(user_id)
self.load()
else:
self.login = None
self.displayname = None
self.language = 'en'
self.role = 'undef'
self.flags = []
def load(self, user_id=0):
sql = (
"SELECT login, displayname, language, role, flags "
"FROM user "
"WHERE userid=%s")
self.cur.execute(sql, (self.user_id, ))
row = self.cur.fetchone()
if row:
self.data = row
self.login = row['login']
self.displayname = row['displayname']
self.language = row['language']
self.role = row['role']
self.flags = row['flags'].split(',') if row['flags'] else []
else:
self.data = None
def save(self):
fields = ('login', 'displayname', 'language', 'role')
sqlfield = []
values = []
for field in fields:
newdata = getattr(self, field)
if self.data[field] != newdata:
sqlfield.append(f"{field}=%s")
values.append(newdata)
self.data[field] = newdata
# flags set handled separately
newflags = ','.join(self.flags) if self.flags else None
if self.data['flags'] != newflags:
sqlfield.append(f"flags=%s")
values.append(newflags)
self.data['flags'] = newflags
if values:
# only save if changed values exist
sql = "UPDATE user SET " + ",".join(sqlfield) + " WHERE userid=%s"
values.append(self.user_id)
self.cur.execute(sql, values)
self.conn.commit()
""" unused at the moment
def mark_login(self):
sql = "UPDATE user SET last_login=NOW() WHERE username=%s"
self.cur.execute(sql, (self.username, ))
self.conn.commit()
def mark_logout(self):
sql = "UPDATE user SET last_logout=NOW() WHERE username=%s"
self.cur.execute(sql, (self.username, ))
self.conn.commit()
"""
def create(self):
"""
Creates a new user with the first free id > 0
The role is the lowest possible one
"""
sql = (
"SELECT MIN(u1.userid+1) AS newid "
"FROM user AS u1 LEFT JOIN user AS u2 ON u2.userid=u1.userid+1 "
"WHERE u1.userid>=0 AND u2.userid IS NULL")
self.cur.execute(sql)
newid = self.cur.fetchone()['newid']
self.user_id = newid
# TODO check if login is unused else create other name
# add function for that feature
self.login = f"user{newid}"
self.displayname = f"User #{newid}"
self.language = 'en'
self.role = 'sailor'
self.flags = None
sql = (
"INSERT INTO user (userid, login, pass, displayname, role, flags, language) "
"VALUES (%s,%s,%s,%s,%s,%s,%s)"
)
cryptpass = bcrypt.hashpw(self.genpass().encode(), bcrypt.gensalt())
self.cur.execute(sql, (newid, self.login, cryptpass, self.displayname, self.role, self.flags, self.language))
# create mandatory settings
sql = "INSERT INTO settings (userid, sno, valint) VALUES (%s, 1, 0)"
self.cur.execute(sql, (newid,))
self.conn.commit()
return newid
def delete(self):
sql = "DELETE FROM settings WHERE userid=%s"
self.cur.execute(sql, (self.user_id,))
sql = "DELETE FROM user WHERE userid=%s"
self.cur.execute(sql, (self.user_id,))
self.conn.commit()
def setpass(self, newpass):
sql = "UPDATE user SET pass=%s WHERE userid=%s"
cryptpass = bcrypt.hashpw(newpass.encode(), bcrypt.gensalt())
print(cryptpass, self.user_id)
self.cur.execute(sql, (cryptpass, self.user_id))
self.conn.commit()
def __repr__(self):
# Unique description
out = "User({})".format(self.user_id)
return out
def __str__(self):
# Prettyprint
out = "User(%d) - %s\n" % (self.user_id, self.displayname)
out += " F: %s\n" % self.flags
out += " R: %s\n" % self.role
out += " L: %s\n" % self.language
return out
+15
View File
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: WTFPL
from .dbrecord import DbRecord
class Vessel(DbRecord):
table = "vessel"
pk_field = "vid"
fields = ('vid', 'vtype', 'vesselname', 'model', 'shipyard', 'loa', 'lwl',
'beam', 'draught', 'draught_min', 'displacement', 'ballast',
'beltpos_aft', 'beltpos_bow', 'shapefile', 'sid_default', 'remarks')
def __init__(self, db, vid=None):
super().__init__(db, vid)
self.title = _("Vessel({})")