Compare commits

...

3 Commits

Author SHA1 Message Date
Christopher T. Johnson
7a02bbb262 Add StatusBar information
Updated workers.py to hold "current" values.
Status bar is three widgets, status label, progressbar and load button.

Fixes: #8, #9
2025-02-25 15:24:34 -05:00
Christopher T. Johnson
dd9f08aa5e More linting 2025-02-25 10:17:14 -05:00
Christopher T. Johnson
49de6b1f35 Lint 2025-02-25 10:14:44 -05:00
7 changed files with 168 additions and 69 deletions

View File

@@ -1,12 +1,10 @@
from typing import Any, cast from typing import Any, cast
from PySide6.QtCore import ( from PySide6.QtCore import (
QByteArray,
QDate, QDate,
QModelIndex, QModelIndex,
QPersistentModelIndex, QPersistentModelIndex,
QPoint, QPoint,
QSettings,
Qt, Qt,
Signal, Signal,
Slot, Slot,
@@ -15,19 +13,25 @@ from PySide6.QtGui import (
QCloseEvent, QCloseEvent,
QColor, QColor,
) )
from PySide6.QtSql import QSqlTableModel from PySide6.QtSql import QSqlQuery, QSqlTableModel
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
QAbstractItemView, QAbstractItemView,
QHBoxLayout,
QHeaderView, QHeaderView,
QLabel,
QMainWindow, QMainWindow,
QProgressBar,
QPushButton,
QSizePolicy,
QStyledItemDelegate, QStyledItemDelegate,
QStyleOptionViewItem, QStyleOptionViewItem,
QWidget,
) )
from docketModel import docketModel from docketModel import docketModel
from lib.utils import QStyleOptionViewItemInit, readGeometry, writeGeometry from lib.utils import QStyleOptionViewItemInit, query_error, readGeometry, writeGeometry
from ui.MainWindow import Ui_MainWindow from ui.MainWindow import Ui_MainWindow
from workers import updateThread from workers import loadCases, updateThread
class dateDelegate(QStyledItemDelegate): class dateDelegate(QStyledItemDelegate):
@@ -115,7 +119,8 @@ class casesModel(QSqlTableModel):
class MainWindow(QMainWindow, Ui_MainWindow): class MainWindow(QMainWindow, Ui_MainWindow):
show_entries = Signal(int) show_entries = Signal(int)
update_status = Signal()
loadThread = None loadThread = None
def __init__(self) -> None: def __init__(self) -> None:
@@ -169,12 +174,77 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.docketView.horizontalHeader().setSectionResizeMode( self.docketView.horizontalHeader().setSectionResizeMode(
1, QHeaderView.ResizeMode.Stretch 1, QHeaderView.ResizeMode.Stretch
) )
widget = QWidget()
layout = QHBoxLayout(widget)
self.status = QLabel("Status")
layout.addWidget(self.status)
layout.addStretch()
self.progress = QProgressBar()
self.progress.setRange(0,100)
self.progress.setValue(0)
self.progress.setMinimumWidth(150)
self.progress.setMaximumWidth(150)
self.progress.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred)
layout.addWidget(self.progress)
self.loadButton = QPushButton()
self.loadButton.setObjectName("loadButton")
self.loadButton.setText("Load Cases")
layout.addWidget(self.loadButton)
self.statusbar.addWidget(widget,10)
self.loadButton.clicked.connect(self.loadCases)
self.update_status.connect(self.statusBarUpdate)
self.update_status.emit()
return return
def closeEvent(self, event: QCloseEvent) -> None: def closeEvent(self, event: QCloseEvent) -> None:
writeGeometry(self) writeGeometry(self)
super().closeEvent(event) super().closeEvent(event)
return return
@Slot()
def loadCases(self) -> None:
if self.loadThread is None:
self.loadThread = loadCases()
self.loadThread.caseLoaded.connect(self.progress.setValue)
self.loadThread.finished.connect(self.loadCasesDone)
self.loadButton.setEnabled(False)
self.loadThread.start()
return
@Slot()
def loadCasesDone(self) -> None:
self.loadButton.setEnabled(True)
self.update_status.emit()
return
@Slot()
def statusBarUpdate(self) -> None:
if self.loadThread is None:
year = ''
max = 'unknown'
else:
year = self.loadThread.year
max = self.loadThread.number
query = QSqlQuery()
query.prepare("SELECT COUNT(*) AS number_active FROM cases WHERE active=1")
if not query.exec():
query_error(query)
if query.next():
active = query.value('number_active')
query.prepare("select SUBSTRING(docket_id,1,2) AS year, "
"SUBSTRING(docket_id,3,1) AS type, "
"MAX(CAST(SUBSTRING(docket_id,4) AS INTEGER)) AS number "
"FROM cases "
"GROUP BY year, type "
"ORDER BY type, year, number"
"LIMIT 2")
#if not query.exec():
# query_error(query)
msg=f"Oldest: {year}, Active: {active}, Max. Case: {max}"
self.status.setText(msg)
return
@Slot(QModelIndex) # type: ignore @Slot(QModelIndex) # type: ignore
def rowClicked(self, index: QModelIndex) -> None: def rowClicked(self, index: QModelIndex) -> None:
if not index.isValid(): if not index.isValid():

View File

@@ -1,6 +1,5 @@
from datetime import date from datetime import date
from PySide6.QtCore import QSettings
from pony.orm import ( # type: ignore[import-untyped] from pony.orm import ( # type: ignore[import-untyped]
Database, Database,
LongStr, LongStr,
@@ -10,9 +9,11 @@ from pony.orm import ( # type: ignore[import-untyped]
Set, Set,
set_sql_debug, set_sql_debug,
) )
from PySide6.QtCore import QSettings
db = Database() db = Database()
class Cases(db.Entity): # type: ignore[name-defined] class Cases(db.Entity): # type: ignore[name-defined]
case_id = PrimaryKey(int, auto=True) case_id = PrimaryKey(int, auto=True)
docket_id = Required(str) docket_id = Required(str)
@@ -32,32 +33,29 @@ class Entries(db.Entity): # type: ignore[name-defined]
text = Required(LongStr) text = Required(LongStr)
documents = Set("Documents") documents = Set("Documents")
class Documents(db.Entity): # type: ignore[name-defined] class Documents(db.Entity): # type: ignore[name-defined]
document_id = PrimaryKey(int, auto=True) document_id = PrimaryKey(int, auto=True)
entry_id = Required(Entries) entry_id = Required(Entries)
name = Required(str) name = Required(str)
url = Required(LongStr) url = Required(LongStr)
class History(db.Entity): # type: ignore[name-defined] class History(db.Entity): # type: ignore[name-defined]
history_id = PrimaryKey(int, auto=True) history_id = PrimaryKey(int, auto=True)
year = Required(str, max_len=3) year = Required(str, max_len=3)
edocket = Required(bool) edocket = Required(bool)
number = Required(int) number = Required(int)
def updateDatabase(settings: QSettings) -> None: def updateDatabase(settings: QSettings) -> None:
set_sql_debug(True) set_sql_debug(True)
engine = settings.value('engine') engine = settings.value("engine")
if engine not in ['QMYSQL', 'MARIADB']: if engine not in ["QMYSQL", "MARIADB"]:
raise Exception(f"Unknown database engine: {engine}") raise Exception(f"Unknown database engine: {engine}")
db.bind( db.bind(
provider="mysql", provider="mysql",
user=settings.value('user'), user=settings.value("user"),
host=settings.value('hostname'), host=settings.value("hostname"),
database=settings.value('databasename'), database=settings.value("databasename"),
password=settings.value('password'), password=settings.value("password"),
) )
db.generate_mapping(create_tables=True) db.generate_mapping(create_tables=True)
db.disconnect() db.disconnect()

View File

@@ -1,6 +1,11 @@
from typing import NoReturn, cast from typing import NoReturn
from PySide6.QtCore import QByteArray, QCoreApplication, QObject, QRect, QSettings, Signal from PySide6.QtCore import (
QByteArray,
QCoreApplication,
QRect,
QSettings,
)
from PySide6.QtGui import QColor, QFont from PySide6.QtGui import QColor, QFont
from PySide6.QtSql import QSqlQuery from PySide6.QtSql import QSqlQuery
from PySide6.QtWidgets import QStyleOptionViewItem, QWidget from PySide6.QtWidgets import QStyleOptionViewItem, QWidget
@@ -29,23 +34,26 @@ class QStyleOptionViewItemInit(QStyleOptionViewItem):
font: QFont font: QFont
text: str text: str
def openSettings(group:str|None = None) -> QSettings:
def openSettings(group: str | None = None) -> QSettings:
settings = QSettings("Troglodite Services", "SCOTUS Watch") settings = QSettings("Troglodite Services", "SCOTUS Watch")
if group is not None: if group is not None:
settings.beginGroup(group) settings.beginGroup(group)
return settings return settings
def readGeometry(widget:QWidget) -> None:
def readGeometry(widget: QWidget) -> None:
settings = openSettings(widget.objectName()) settings = openSettings(widget.objectName())
geometry = settings.value('geometry', QByteArray()) geometry = settings.value("geometry", QByteArray())
assert isinstance(geometry, QByteArray) assert isinstance(geometry, QByteArray)
if not geometry.isEmpty(): if not geometry.isEmpty():
widget.restoreGeometry(geometry) widget.restoreGeometry(geometry)
settings.endGroup() settings.endGroup()
return return
def writeGeometry(widget: QWidget) -> None: def writeGeometry(widget: QWidget) -> None:
settings = openSettings(widget.objectName()) settings = openSettings(widget.objectName())
settings.setValue('geometry', widget.saveGeometry()) settings.setValue("geometry", widget.saveGeometry())
settings.endGroup() settings.endGroup()
return return

View File

@@ -83,7 +83,7 @@ class PDFViewer(QDialog, Ui_pdfViewer):
def __init__(self, parent: QWidget | None) -> None: def __init__(self, parent: QWidget | None) -> None:
super(PDFViewer, self).__init__(parent) super(PDFViewer, self).__init__(parent)
self.setupUi(self) self.setupUi(self)
self.setObjectName('PDFViewer') self.setObjectName("PDFViewer")
readGeometry(self) readGeometry(self)
print(self.objectName()) print(self.objectName())
self.zoomSelector = ZoomSelector(self) self.zoomSelector = ZoomSelector(self)

View File

@@ -25,20 +25,20 @@ from MainWindow import MainWindow
def main() -> int: def main() -> int:
# #
app = QApplication(sys.argv) app = QApplication(sys.argv)
settings = openSettings('database') settings = openSettings("database")
file = QFile(settings.fileName()) file = QFile(settings.fileName())
if not file.exists(): if not file.exists():
result = setupDialog().exec() result = setupDialog().exec()
if result == QDialog.DialogCode.Rejected: if result == QDialog.DialogCode.Rejected:
print(f"We require database credentials") print(f"We require database credentials")
return(2) return 2
updateDatabase(settings) updateDatabase(settings)
db = QSqlDatabase.addDatabase("QMYSQL") db = QSqlDatabase.addDatabase("QMYSQL")
db.setHostName(settings.value("hostname")) db.setHostName(settings.value("hostname"))
portStr = cast(str, settings.value('port', '3306')) portStr = cast(str, settings.value("port", "3306"))
db.setPort(int(portStr)) db.setPort(int(portStr))
db.setDatabaseName(settings.value("databasename","scotus")) # type: ignore db.setDatabaseName(settings.value("databasename", "scotus")) # type: ignore
db.setUserName(settings.value("user","scotus")) # type: ignore db.setUserName(settings.value("user", "scotus")) # type: ignore
db.setPassword(settings.value("password")) db.setPassword(settings.value("password"))
settings.endGroup() settings.endGroup()
db.open() db.open()

View File

@@ -1,20 +1,21 @@
from PySide6.QtCore import Slot, Qt from PySide6.QtCore import Qt, Slot
from PySide6.QtGui import QPalette from PySide6.QtGui import QPalette
from PySide6.QtWidgets import QDialog, QStatusBar, QVBoxLayout, QWidget from PySide6.QtWidgets import QDialog, QStatusBar, QVBoxLayout, QWidget
from lib.utils import openSettings from lib.utils import openSettings
from ui.dbSetup import Ui_setupDialog from ui.dbSetup import Ui_setupDialog
class setupDialog(QDialog,Ui_setupDialog):
def __init__(self, parent: QWidget | None = None): class setupDialog(QDialog, Ui_setupDialog):
super(setupDialog,self).__init__(parent) def __init__(self, parent: QWidget | None = None) -> None:
super(setupDialog, self).__init__(parent)
self.setupUi(self) self.setupUi(self)
self.sqliteEdit.setEnabled(False) self.sqliteEdit.setEnabled(False)
layout = self.layout() layout = self.layout()
assert isinstance(layout, QVBoxLayout) assert isinstance(layout, QVBoxLayout)
self.statusBar = QStatusBar(self) self.statusBar = QStatusBar(self)
self.statusBar.setObjectName('statusBar') self.statusBar.setObjectName("statusBar")
where = layout.indexOf(self.buttonBox) where = layout.indexOf(self.buttonBox)
layout.insertWidget(where, self.statusBar) layout.insertWidget(where, self.statusBar)
self.pwEdit.editingFinished.connect(self.pwDone) self.pwEdit.editingFinished.connect(self.pwDone)
@@ -22,7 +23,7 @@ class setupDialog(QDialog,Ui_setupDialog):
return return
@Slot() @Slot()
def pwDone(self): def pwDone(self) -> None:
pw = self.pwEdit.text() pw = self.pwEdit.text()
confirm = self.pwConfirmEdit.text() confirm = self.pwConfirmEdit.text()
@@ -37,29 +38,29 @@ class setupDialog(QDialog,Ui_setupDialog):
self.pwGood = True self.pwGood = True
return return
@Slot(int) # type: ignore @Slot(int) # type: ignore
def done(self, r:int) -> None: def done(self, r: int) -> None:
if r == QDialog.DialogCode.Rejected: if r == QDialog.DialogCode.Rejected:
super(setupDialog,self).done(r) super(setupDialog, self).done(r)
return return
self.pwDone() self.pwDone()
if not self.pwGood: if not self.pwGood:
return return
super(setupDialog,self).done(r) super(setupDialog, self).done(r)
return return
@Slot() @Slot()
def accept(self) -> None: def accept(self) -> None:
settings=openSettings('database') settings = openSettings("database")
settings.setValue('hostname', self.hostEdit.text()) settings.setValue("hostname", self.hostEdit.text())
settings.setValue('databasename', self.dbEdit.text()) settings.setValue("databasename", self.dbEdit.text())
settings.setValue('user',self.userEdit.text()) settings.setValue("user", self.userEdit.text())
settings.setValue('password', self.pwEdit.text()) settings.setValue("password", self.pwEdit.text())
settings.setValue('port', self.portEdit.text()) settings.setValue("port", self.portEdit.text())
if self.comboBox.currentIndex() == 0: if self.comboBox.currentIndex() == 0:
settings.setValue('engine', 'QMYSQL') settings.setValue("engine", "QMYSQL")
elif self.comboBox.currentIndex() == 1: elif self.comboBox.currentIndex() == 1:
settings.setValue('engine', 'QSQLITE') settings.setValue("engine", "QSQLITE")
# Other types: QDB2, QIBASE, QOCI, QODBC, QPSQL, QMIMER # Other types: QDB2, QIBASE, QOCI, QODBC, QPSQL, QMIMER
else: else:
print("Bad Database Type") print("Bad Database Type")

View File

@@ -4,7 +4,7 @@ import re
import dateparser import dateparser
import requests import requests
from bs4 import BeautifulSoup, Tag from bs4 import BeautifulSoup, Tag
from PySide6.QtCore import QDateTime, QThread from PySide6.QtCore import QDateTime, QThread, Signal
from PySide6.QtSql import QSqlDatabase, QSqlQuery from PySide6.QtSql import QSqlDatabase, QSqlQuery
from lib.utils import query_error from lib.utils import query_error
@@ -223,9 +223,17 @@ def update_db(case_id: str, db: QSqlDatabase) -> int:
# #
# If there is a linked case, we need to get the ID for that case. # If there is a linked case, we need to get the ID for that case.
if linked is not None: if linked is not None:
#
# If this case is on the Emergency Docket and it is linked to
# a case on the regular docket, then this case is no longer active
#
deactivate = False
linked = linked.replace("Linked with ", "") linked = linked.replace("Linked with ", "")
for did in linked.split(","): for did in linked.split(","):
did = did.strip() did = did.strip()
if re.match(r"\d+-\d+$", did):
deactivate = True
query.prepare("SELECT * FROM cases WHERE docket_id = :did") query.prepare("SELECT * FROM cases WHERE docket_id = :did")
query.bindValue(":did", linked) query.bindValue(":did", linked)
if not query.exec(): if not query.exec():
@@ -255,6 +263,14 @@ def update_db(case_id: str, db: QSqlDatabase) -> int:
query.bindValue(":rhs", linked_id) query.bindValue(":rhs", linked_id)
if not query.exec(): if not query.exec():
query_error(query) query_error(query)
if re.match(r"\d+-\d+$", docket_id):
deactivate = False
if deactivate:
query.prepare("UPDATE cases SET active=0 WHERE case_id = :cid")
query.bindValue(":cid", case_id)
if not query.exec():
query_error(query)
# #
# XXX - Process lower courts # XXX - Process lower courts
# #
@@ -295,15 +311,19 @@ class updateThread(QThread):
class loadCases(QThread): class loadCases(QThread):
caseLoaded = Signal(int)
year = QDateTime.currentDateTime().toString("yy")
number = 0
def run(self) -> None: def run(self) -> None:
db = QSqlDatabase.cloneDatabase("qt_sql_default_connection", "load") db = QSqlDatabase.cloneDatabase("qt_sql_default_connection", "load")
if not db.open(): if not db.open():
raise Exception("db.open()") raise Exception("db.open()")
year = QDateTime.currentDateTime().toString("yy")
query = QSqlQuery(db) query = QSqlQuery(db)
query.prepare("SELECT * FROM history WHERE year = :year") query.prepare("SELECT * FROM history WHERE year = :year")
print(f"year = {year}") print(f"year = {self.year}")
query.bindValue(":year", year) query.bindValue(":year", self.year)
if not query.exec(): if not query.exec():
query_error(query) query_error(query)
@@ -312,48 +332,49 @@ class loadCases(QThread):
"INSERT INTO history (year, edocket, number) " "INSERT INTO history (year, edocket, number) "
"VALUES (:year, 0, 1)" "VALUES (:year, 0, 1)"
) )
query.bindValue(":year", year) query.bindValue(":year", self.year)
if not query.exec(): if not query.exec():
query_error(query) query_error(query)
edocket = 0 edocket = 0
number = 1 self.number = 1
history_id = query.lastInsertId() history_id = query.lastInsertId()
else: else:
history_id = query.value("history_id") history_id = query.value("history_id")
edocket = query.value("edocket") edocket = query.value("edocket")
number = query.value("number") self.number = query.value("number")
count = 0 count = 0
while year > "00" and count < 100: while self.year > "00" and count < 100:
self.caseLoaded.emit(count)
query.prepare("SELECT * FROM cases WHERE docket_id = :did") query.prepare("SELECT * FROM cases WHERE docket_id = :did")
if edocket == 1: if edocket == 1:
docket_id = f"{year}A{number}" docket_id = f"{self.year}A{self.number}"
else: else:
docket_id = f"{year}-{number}" docket_id = f"{self.year}-{self.number}"
query.bindValue(":did", docket_id) query.bindValue(":did", docket_id)
if not query.exec(): if not query.exec():
query_error(query) query_error(query)
if query.next(): if query.next():
if query.value("active") == 0: if query.value("active") == 0:
print("Already exists and is inactive") print("Already exists and is inactive")
number += 1 self.number += 1
continue continue
result = update_db(docket_id, db) result = update_db(docket_id, db)
print(f"result: {result}") print(f"result: {result}")
if result < 0: if result < 0:
year = f"{int(year) - 1:02d}" self.year = f"{int(self.year) - 1:02d}"
if number > 1: if self.number > 1:
query.prepare( query.prepare(
"UPDATE history set number = :number WHERE history_id=:hid" "UPDATE history set number = :number WHERE history_id=:hid"
) )
query.bindValue(":number", number - 1) query.bindValue(":number", self.number - 1)
query.bindValue(":hid", history_id) query.bindValue(":hid", history_id)
if not query.exec(): if not query.exec():
query_error(query) query_error(query)
query.prepare("SELECT * FROM history WHERE year = :year") query.prepare("SELECT * FROM history WHERE year = :year")
print(f"year = {year}") print(f"year = {self.year}")
query.bindValue(":year", year) query.bindValue(":year", self.year)
if not query.exec(): if not query.exec():
query_error(query) query_error(query)
@@ -362,29 +383,30 @@ class loadCases(QThread):
"INSERT INTO history (year, edocket, number) " "INSERT INTO history (year, edocket, number) "
"VALUES (:year, 0, 1)" "VALUES (:year, 0, 1)"
) )
query.bindValue(":year", year) query.bindValue(":year", self.year)
if not query.exec(): if not query.exec():
query_error(query) query_error(query)
edocket = 0 edocket = 0
number = 1 self.number = 1
history_id = query.lastInsertId() history_id = query.lastInsertId()
else: else:
history_id = query.value("history_id") history_id = query.value("history_id")
edocket = query.value("edocket") edocket = query.value("edocket")
number = query.value("number") self.number = query.value("number")
continue continue
number += 1 self.number += 1
count += 1 count += 1
if number > 1: if self.number > 1:
query.prepare( query.prepare(
"UPDATE history SET number= :number WHERE year = :year" "UPDATE history SET number= :number WHERE year = :year"
) )
query.bindValue(":number", number) query.bindValue(":number", self.number)
query.bindValue(":year", year) query.bindValue(":year", self.year)
if not query.exec(): if not query.exec():
query_error(query) query_error(query)
db.close() db.close()
del db del db
QSqlDatabase.removeDatabase("load") QSqlDatabase.removeDatabase("load")
self.caseLoaded.emit(0)
return return