Allow for printing session results.

This commit is contained in:
Christopher T. Johnson
2023-12-13 10:49:05 -05:00
parent 12595771b6
commit a03289db51
7 changed files with 361 additions and 20 deletions

View File

@@ -1,7 +1,14 @@
import json
import os
import secrets
import smtplib
from datetime import datetime, timedelta
from email import policy
from email.message import EmailMessage
from html.parser import HTMLParser from html.parser import HTMLParser
from io import StringIO from io import StringIO
from PyQt6.QtCore import QSize, Qt, pyqtSlot from PyQt6.QtCore import QResource, QSize, Qt, pyqtSlot
from PyQt6.QtGui import QStandardItem, QStandardItemModel from PyQt6.QtGui import QStandardItem, QStandardItemModel
from PyQt6.QtSql import QSqlQuery, QSqlQueryModel from PyQt6.QtSql import QSqlQuery, QSqlQueryModel
from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QStyledItemDelegate from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QStyledItemDelegate
@@ -10,6 +17,66 @@ from main import query_error
from ui.PeopleDialog import Ui_Dialog from ui.PeopleDialog import Ui_Dialog
class blockHandler(HTMLParser):
text = ""
blocks = []
active = 0
tags = [
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"p",
"b",
"i",
"em",
"st",
"span",
]
space = ["b", "i", "em", "st", "span"]
def __init__(self):
super().__init__()
self.reset()
self.strict = False
self.convert_charrefs = True
self.text = ""
self.blocks = []
self.active = 0
return
def handle_starttag(self, tag, attrs):
if not tag in self.tags:
return
self.active += 1
if tag in self.space:
self.text += " "
self.text += f"<{tag}>"
return
def handle_endtag(self, tag):
if not tag in self.tags:
return
self.active -= 1
self.text += f"</{tag}>"
if tag in self.space:
self.text += " "
if self.active <= 0:
self.blocks.append(self.text)
self.text = ""
self.active = 0
return
def handle_data(self, data):
self.text += data
return
def get_block(self, block):
return self.blocks[block]
class MLStripper(HTMLParser): class MLStripper(HTMLParser):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@@ -45,6 +112,9 @@ class PersonDialog(QDialog, Ui_Dialog):
super(PersonDialog, self).__init__(*args, **kwargs) super(PersonDialog, self).__init__(*args, **kwargs)
self.setupUi(self) self.setupUi(self)
self.show() self.show()
QResource.registerResource(
os.path.join(os.path.dirname(__file__), "../ui/resources.rcc"), "/"
)
model = QSqlQueryModel() model = QSqlQueryModel()
query = QSqlQuery() query = QSqlQuery()
query.prepare("SELECT book_id, title " "FROM books " "ORDER BY title") query.prepare("SELECT book_id, title " "FROM books " "ORDER BY title")
@@ -60,7 +130,8 @@ class PersonDialog(QDialog, Ui_Dialog):
self.sectionCombo.setModel(model) self.sectionCombo.setModel(model)
self.sectionCombo.setEnabled(False) self.sectionCombo.setEnabled(False)
self.sectionCombo.setCurrentIndex(-1) self.sectionCombo.setCurrentIndex(-1)
self.printBtn.setEnabled(False)
self.emailBtn.setEnabled(False)
button = self.buttonBox.button(QDialogButtonBox.StandardButton.Ok) button = self.buttonBox.button(QDialogButtonBox.StandardButton.Ok)
button.setEnabled(False) button.setEnabled(False)
@@ -71,6 +142,8 @@ class PersonDialog(QDialog, Ui_Dialog):
self.sectionCombo.currentIndexChanged.connect(self.sectionSelected) self.sectionCombo.currentIndexChanged.connect(self.sectionSelected)
self.nameEdit.editingFinished.connect(self.checkLineEdits) self.nameEdit.editingFinished.connect(self.checkLineEdits)
self.orgEdit.editingFinished.connect(self.checkLineEdits) self.orgEdit.editingFinished.connect(self.checkLineEdits)
self.printBtn.clicked.connect(self.senditAction)
self.emailBtn.clicked.connect(self.senditAction)
if self.person_id > 0: if self.person_id > 0:
self.setPerson(self.person_id) self.setPerson(self.person_id)
return return
@@ -78,7 +151,7 @@ class PersonDialog(QDialog, Ui_Dialog):
def setPerson(self, person_id: int) -> None: def setPerson(self, person_id: int) -> None:
query = QSqlQuery() query = QSqlQuery()
query.prepare( query.prepare(
"SELECT p.name, p.organization, p.book_id, s.sequence " "SELECT p.name, p.organization, p.book_id, p.email, s.sequence "
"FROM people p " "FROM people p "
"LEFT JOIN person_book pb " "LEFT JOIN person_book pb "
"ON (p.book_id = pb.book_id " "ON (p.book_id = pb.book_id "
@@ -95,6 +168,7 @@ class PersonDialog(QDialog, Ui_Dialog):
self.person_id = person_id self.person_id = person_id
self.nameEdit.setText(query.value("name")) self.nameEdit.setText(query.value("name"))
self.orgEdit.setText(query.value("organization")) self.orgEdit.setText(query.value("organization"))
self.emailEdit.setText(query.value("email"))
model = self.bookCombo.model() model = self.bookCombo.model()
matches = model.match( matches = model.match(
model.createIndex(0, 0), model.createIndex(0, 0),
@@ -104,12 +178,58 @@ class PersonDialog(QDialog, Ui_Dialog):
Qt.MatchFlag.MatchExactly, Qt.MatchFlag.MatchExactly,
) )
if len(matches) != 1: if len(matches) != 1:
raise Excpetion( raise Exception(
f"Match failed looking for book_id: {query.value('book_id')}" f"Match failed looking for book_id: {query.value('book_id')}"
) )
row = int(matches[0].row()) row = int(matches[0].row())
self.bookCombo.setCurrentIndex(row) self.bookCombo.setCurrentIndex(row)
self.sectionCombo.setCurrentIndex(query.value("sequence")) self.sectionCombo.setCurrentIndex(query.value("sequence"))
query.prepare(
"SELECT * FROM sessions "
"WHERE person_id = :person_id "
"ORDER BY start DESC"
)
query.bindValue(":person_id", person_id)
if not query.exec():
query_error(query)
model = QSqlQueryModel()
model.setQuery(query)
self.sessionCombo.setModel(model)
self.sessionCombo.setModelColumn(2)
self.printBtn.setEnabled(True)
self.emailBtn.setEnabled(True)
return
@pyqtSlot()
def senditAction(self) -> None:
html = "<!DOCTYPE html>\n<html><head><title>Hello</title>\n"
html += (
'<style type="text/css">\n'
+ QResource(":email.css").data().decode("utf-8")
+ "</style>\n"
)
html += "</head><body>\n"
html += self.makeDefinitions()
html += self.makeText()
html += "</body>\n</html>\n"
if self.sender() == self.printBtn:
print(html)
return
msg = EmailMessage(policy=policy.default)
start = datetime.fromisoformat(self.sessionCombo.currentText())
msg["Subject"] = f"TT English, Session: {start.date().isoformat()}"
msg["From"] = "Christopher T. Johnson <cjohnson@troglodite.com>"
msg["To"] = self.emailEdit.text().strip()
msg.set_content("There is a html message you should read")
msg.add_alternative(html, subtype="html")
server = smtplib.SMTP(secrets.SMTP_HOST, secrets.SMTP_PORT)
server.set_debuglevel(1)
if secrets.SMTP_STARTTLS:
server.starttls()
server.login(secrets.SMTP_USER, secrets.SMTP_PASSWORD)
server.send_message(msg)
server.quit()
return return
@pyqtSlot(int) @pyqtSlot(int)
@@ -222,3 +342,82 @@ class PersonDialog(QDialog, Ui_Dialog):
else: else:
button.setEnabled(False) button.setEnabled(False)
return return
def makeDefinitions(self) -> str:
query = QSqlQuery()
query.prepare(
"SELECT w.word, w.definition FROM sessions s "
"LEFT JOIN session_word sw "
"ON (s.session_id = s.session_id) "
"LEFT JOIN words w "
"ON (sw.word_id = w.word_id) "
"WHERE s.session_id = :session_id "
"ORDER BY w.word"
)
row = self.sessionCombo.currentIndex()
model = self.sessionCombo.model()
index = model.index(row, 0)
session_id = index.data()
query.bindValue(":session_id", session_id)
if not query.exec():
query_error(query)
html = '<div class="words">\n<dl>\n'
while query.next():
html += "<dt>" + query.value("word") + "</dt>\n<dd>\n"
data = json.loads(query.value("definition"))
if "phonetics" in data:
for p in data["phonetics"]:
if "text" in p:
html += '<p class="phonetic">' + p["text"] + "</p>\n"
html += '<dl class="meanings">\n'
for meaning in data["meanings"]:
html += "<dt>" + meaning["partOfSpeech"] + "</dt>\n<dd><ul>\n"
for definition in meaning["definitions"]:
html += "<li>" + definition["definition"] + "</li>\n"
html += "</ul>\n"
html += "</dl>\n</dd>\n"
html += "</dl>\n</div>\n"
return html
def makeText(self) -> str:
query = QSqlQuery()
section_query = QSqlQuery()
html = '<div class="text">'
session_id = (
self.sessionCombo.model()
.index(self.sessionCombo.currentIndex(), 0)
.data()
)
query.prepare(
"SELECT * FROM session_block sb "
"WHERE sb.session_id = :session_id "
"ORDER BY sb.section_id, sb.block"
)
query.bindValue(":session_id", session_id)
if not query.exec():
query_error(query)
section_query.prepare(
"SELECT * FROM sections " "WHERE section_id = :section_id"
)
section_id = 0
while query.next():
if section_id != query.value("section_id"):
section_id = query.value("section_id")
section_query.bindValue(":section_id", section_id)
if not section_query.exec():
query_error(section_query)
if not section_query.next():
raise Exception(f"Missing section {section_id}")
section = blockHandler()
section.feed(section_query.value("content"))
html += section.get_block(query.value("block")) + "\n"
html += "</div>\n"
return html
def makeStats(self) -> str:
html = '<div class="stats">'
html += "</div>\n"
return html

View File

@@ -198,6 +198,7 @@ SQL_CMDS = [
"person_id INTEGER REFERENCES people ON DELETE CASCADE, " "person_id INTEGER REFERENCES people ON DELETE CASCADE, "
"start TEXT DEFAULT '', " "start TEXT DEFAULT '', "
"stop TEXT DEFAULT '', " "stop TEXT DEFAULT '', "
"total TEXT DEFAULT '', "
"notes TEXT DEFAULT '')", "notes TEXT DEFAULT '')",
# #
"CREATE TABLE IF NOT EXISTS session_word " "CREATE TABLE IF NOT EXISTS session_word "

View File

@@ -12,7 +12,12 @@ from PyQt6 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object): class Ui_Dialog(object):
def setupUi(self, Dialog): def setupUi(self, Dialog):
Dialog.setObjectName("Dialog") Dialog.setObjectName("Dialog")
Dialog.resize(542, 219) Dialog.resize(542, 300)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(Dialog.sizePolicy().hasHeightForWidth())
Dialog.setSizePolicy(sizePolicy)
self.formLayout = QtWidgets.QFormLayout(Dialog) self.formLayout = QtWidgets.QFormLayout(Dialog)
self.formLayout.setObjectName("formLayout") self.formLayout.setObjectName("formLayout")
self.label = QtWidgets.QLabel(parent=Dialog) self.label = QtWidgets.QLabel(parent=Dialog)
@@ -21,6 +26,12 @@ class Ui_Dialog(object):
self.nameEdit = QtWidgets.QLineEdit(parent=Dialog) self.nameEdit = QtWidgets.QLineEdit(parent=Dialog)
self.nameEdit.setObjectName("nameEdit") self.nameEdit.setObjectName("nameEdit")
self.formLayout.setWidget(0, QtWidgets.QFormLayout.ItemRole.FieldRole, self.nameEdit) self.formLayout.setWidget(0, QtWidgets.QFormLayout.ItemRole.FieldRole, self.nameEdit)
self.label_5 = QtWidgets.QLabel(parent=Dialog)
self.label_5.setObjectName("label_5")
self.formLayout.setWidget(1, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_5)
self.emailEdit = QtWidgets.QLineEdit(parent=Dialog)
self.emailEdit.setObjectName("emailEdit")
self.formLayout.setWidget(1, QtWidgets.QFormLayout.ItemRole.FieldRole, self.emailEdit)
self.label_2 = QtWidgets.QLabel(parent=Dialog) self.label_2 = QtWidgets.QLabel(parent=Dialog)
self.label_2.setObjectName("label_2") self.label_2.setObjectName("label_2")
self.formLayout.setWidget(2, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_2) self.formLayout.setWidget(2, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_2)
@@ -39,17 +50,45 @@ class Ui_Dialog(object):
self.sectionCombo = QtWidgets.QComboBox(parent=Dialog) self.sectionCombo = QtWidgets.QComboBox(parent=Dialog)
self.sectionCombo.setObjectName("sectionCombo") self.sectionCombo.setObjectName("sectionCombo")
self.formLayout.setWidget(4, QtWidgets.QFormLayout.ItemRole.FieldRole, self.sectionCombo) self.formLayout.setWidget(4, QtWidgets.QFormLayout.ItemRole.FieldRole, self.sectionCombo)
self.widget = QtWidgets.QWidget(parent=Dialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.widget.sizePolicy().hasHeightForWidth())
self.widget.setSizePolicy(sizePolicy)
self.widget.setObjectName("widget")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.widget)
self.horizontalLayout.setObjectName("horizontalLayout")
self.printBtn = QtWidgets.QPushButton(parent=self.widget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.printBtn.sizePolicy().hasHeightForWidth())
self.printBtn.setSizePolicy(sizePolicy)
self.printBtn.setObjectName("printBtn")
self.horizontalLayout.addWidget(self.printBtn)
self.emailBtn = QtWidgets.QPushButton(parent=self.widget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.emailBtn.sizePolicy().hasHeightForWidth())
self.emailBtn.setSizePolicy(sizePolicy)
self.emailBtn.setObjectName("emailBtn")
self.horizontalLayout.addWidget(self.emailBtn)
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
self.horizontalLayout.addItem(spacerItem)
self.formLayout.setWidget(7, QtWidgets.QFormLayout.ItemRole.FieldRole, self.widget)
self.buttonBox = QtWidgets.QDialogButtonBox(parent=Dialog) self.buttonBox = QtWidgets.QDialogButtonBox(parent=Dialog)
self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal) self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.StandardButton.Cancel|QtWidgets.QDialogButtonBox.StandardButton.Ok) self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.StandardButton.Cancel|QtWidgets.QDialogButtonBox.StandardButton.Ok)
self.buttonBox.setObjectName("buttonBox") self.buttonBox.setObjectName("buttonBox")
self.formLayout.setWidget(5, QtWidgets.QFormLayout.ItemRole.SpanningRole, self.buttonBox) self.formLayout.setWidget(8, QtWidgets.QFormLayout.ItemRole.SpanningRole, self.buttonBox)
self.label_5 = QtWidgets.QLabel(parent=Dialog) self.label_6 = QtWidgets.QLabel(parent=Dialog)
self.label_5.setObjectName("label_5") self.label_6.setObjectName("label_6")
self.formLayout.setWidget(1, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_5) self.formLayout.setWidget(6, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_6)
self.emailEdit = QtWidgets.QLineEdit(parent=Dialog) self.sessionCombo = QtWidgets.QComboBox(parent=Dialog)
self.emailEdit.setObjectName("emailEdit") self.sessionCombo.setObjectName("sessionCombo")
self.formLayout.setWidget(1, QtWidgets.QFormLayout.ItemRole.FieldRole, self.emailEdit) self.formLayout.setWidget(6, QtWidgets.QFormLayout.ItemRole.FieldRole, self.sessionCombo)
self.retranslateUi(Dialog) self.retranslateUi(Dialog)
self.buttonBox.accepted.connect(Dialog.accept) # type: ignore self.buttonBox.accepted.connect(Dialog.accept) # type: ignore
@@ -64,7 +103,10 @@ class Ui_Dialog(object):
_translate = QtCore.QCoreApplication.translate _translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "People")) Dialog.setWindowTitle(_translate("Dialog", "People"))
self.label.setText(_translate("Dialog", "Name")) self.label.setText(_translate("Dialog", "Name"))
self.label_5.setText(_translate("Dialog", "Email"))
self.label_2.setText(_translate("Dialog", "Organization")) self.label_2.setText(_translate("Dialog", "Organization"))
self.label_3.setText(_translate("Dialog", "Book")) self.label_3.setText(_translate("Dialog", "Book"))
self.label_4.setText(_translate("Dialog", "Section")) self.label_4.setText(_translate("Dialog", "Section"))
self.label_5.setText(_translate("Dialog", "Email")) self.printBtn.setText(_translate("Dialog", "Print"))
self.emailBtn.setText(_translate("Dialog", "EMail"))
self.label_6.setText(_translate("Dialog", "Session"))

View File

@@ -7,9 +7,15 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>542</width> <width>542</width>
<height>219</height> <height>300</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle"> <property name="windowTitle">
<string>People</string> <string>People</string>
</property> </property>
@@ -24,6 +30,16 @@
<item row="0" column="1"> <item row="0" column="1">
<widget class="QLineEdit" name="nameEdit"/> <widget class="QLineEdit" name="nameEdit"/>
</item> </item>
<item row="1" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Email</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="emailEdit"/>
</item>
<item row="2" column="0"> <item row="2" column="0">
<widget class="QLabel" name="label_2"> <widget class="QLabel" name="label_2">
<property name="text"> <property name="text">
@@ -54,7 +70,58 @@
<item row="4" column="1"> <item row="4" column="1">
<widget class="QComboBox" name="sectionCombo"/> <widget class="QComboBox" name="sectionCombo"/>
</item> </item>
<item row="5" column="0" colspan="2"> <item row="7" column="1">
<widget class="QWidget" name="widget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="printBtn">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Print</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="emailBtn">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>EMail</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item row="8" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox"> <widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
@@ -64,15 +131,15 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0"> <item row="6" column="0">
<widget class="QLabel" name="label_5"> <widget class="QLabel" name="label_6">
<property name="text"> <property name="text">
<string>Email</string> <string>Session</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="6" column="1">
<widget class="QLineEdit" name="emailEdit"/> <widget class="QComboBox" name="sessionCombo"/>
</item> </item>
</layout> </layout>
</widget> </widget>

31
ui/email.css Normal file
View File

@@ -0,0 +1,31 @@
@import url('https://fonts.cdnfonts.com/css/gentium-plus');
@import url('https://fonts.cdnfonts.com/css/opendyslexic');
body {
font-family: "OpenDyslexic3", sans-serif;
font-size: 20px;
}
div.words dt {
font-family: "opendyslexic", sans-serif;
font-size: 48px;
font-weight: Bold;
}
dl.meanings dt {
font-family: "opendyslexic", sans-serif;
font-size: 24px;
font-weight: Bold;
}
dl.meanings li {
font-family: "opendyslexic", sans-serif;
max-width: 66ch;
}
p.phonetic {
font-family: "Gentium Plus", sans-serif;
font-size: 40px;
margin-top: 0px;
margin-bottom: 0px;
}
div.text {
font-size: 20px;
font-family: "opendyslexic", sans-serif;
max-width: 66ch;
}

View File

@@ -2,6 +2,7 @@
<qresource prefix="/"> <qresource prefix="/">
<file>print.css</file> <file>print.css</file>
<file>display.css</file> <file>display.css</file>
<file>email.css</file>
<file>opendyslexic/OpenDyslexic-Regular.otf</file> <file>opendyslexic/OpenDyslexic-Regular.otf</file>
</qresource> </qresource>
</RCC> </RCC>

Binary file not shown.