Skip to content
Snippets Groups Projects
Commit 2397053a authored by Thomas Holene Løkkeborg's avatar Thomas Holene Løkkeborg
Browse files

Merge branch 'modul-forhåndsavtalte-koder' into 'master'

#95 Forhåndsavtalte koder

See merge request !74
parents 3fa2c06e e270edcc
No related branches found
No related tags found
1 merge request!74#95 Forhåndsavtalte koder
Pipeline #83543 passed
...@@ -162,14 +162,50 @@ class Database: ...@@ -162,14 +162,50 @@ class Database:
Returns Returns
------- -------
List of strings List
Categories Containing categories (string).
""" """
stmt = "SELECT DISTINCT Category FROM CategoryWords" stmt = "SELECT DISTINCT Category FROM CategoryWords"
queried = self.conn.execute(stmt) queried = self.conn.execute(stmt)
categories = [] categories = [row["Category"] for row in queried]
for row in queried:
categories.append(row["Category"]) return categories
def get_random_category_word(self):
"""Read a random word from database-table CategoryWords.
Returns
-------
String
Word (string).
"""
stmt = "SELECT Word FROM CategoryWords ORDER BY RANDOM() LIMIT 1"
word = self.conn.execute(stmt).fetchone()[0]
return word
def get_categories_from_codebook(self, small=False):
"""Read categories from full or small codebook.
Parameters
----------
small : bool, optional
Categories are from small codebook if True, full codebook if
False, by default False.
Returns
-------
List
Containing categories (string).
"""
stmt = "SELECT Category FROM Codebook"
if small:
stmt += " WHERE Type='Liten' GROUP BY Category"
else:
stmt += " GROUP BY Category"
queried = self.conn.execute(stmt).fetchall()
categories = [row["Category"] for row in queried]
return categories return categories
...@@ -226,6 +262,31 @@ class Database: ...@@ -226,6 +262,31 @@ class Database:
return codebook return codebook
def get_codebook_expressions_in_category(self, category, small=False):
"""Read expressions, from full or small codebook, in a given category.
Parameters
----------
category : string
Expressions in this category are returned.
small : bool, optional
Expressions are from small codebook if True, full codebook if
False, by default False.
Returns
-------
List
Containing expressions (string).
"""
stmt = "SELECT Word FROM Codebook WHERE Category=?"
if small:
stmt += " AND Type='Liten'"
queried = self.conn.execute(stmt, (category,)).fetchall()
expressions = [row["Word"] for row in queried]
return expressions
def update_codebook(self): def update_codebook(self):
"""Update codes in DB.""" """Update codes in DB."""
# Get all the words (PK) # Get all the words (PK)
......
...@@ -227,7 +227,7 @@ class MainWindow(QMainWindow): ...@@ -227,7 +227,7 @@ class MainWindow(QMainWindow):
def open_soi_workspace_tab(self): def open_soi_workspace_tab(self):
"""Open and select tab containing a SOIWorkspaceWidget.""" """Open and select tab containing a SOIWorkspaceWidget."""
tab = SOIWorkspaceWidget() tab = SOIWorkspaceWidget(self.database)
self.tabs.addTab(tab, tab.soi.title) self.tabs.addTab(tab, tab.soi.title)
self.tabs.setCurrentWidget(tab) self.tabs.setCurrentWidget(tab)
...@@ -315,10 +315,10 @@ class MainWindow(QMainWindow): ...@@ -315,10 +315,10 @@ class MainWindow(QMainWindow):
)[0] )[0]
if len(file_path) > 0: if len(file_path) > 0:
soi = import_soi(file_path) soi = import_soi(file_path, self.database)
# Create and select tab # Create and select tab
tab = SOIWorkspaceWidget(soi) tab = SOIWorkspaceWidget(self.database, soi)
self.tabs.addTab(tab, soi.title) self.tabs.addTab(tab, soi.title)
self.tabs.setCurrentWidget(tab) self.tabs.setCurrentWidget(tab)
......
soitool/media/predefinedcodesmodule.PNG

10.4 KiB

...@@ -94,11 +94,10 @@ class CodeTableBase(ModuleBase, QTableWidget, metaclass=Meta): ...@@ -94,11 +94,10 @@ class CodeTableBase(ModuleBase, QTableWidget, metaclass=Meta):
# Set cell-items # Set cell-items
for i in range(self.rowCount()): for i in range(self.rowCount()):
for j in range(self.columnCount()): for j in range(self.columnCount()):
item = QTableWidgetItem( # + 1 skip headline
cells[i + 1][j] item = QTableWidgetItem(cells[i + 1][j])
) # +1 skip headline item.setTextAlignment(Qt.AlignCenter)
if j == 2: if j == 2:
item.setTextAlignment(Qt.AlignCenter)
item.setFont(self.code_font) item.setFont(self.code_font)
self.setItem(i, j, item) self.setItem(i, j, item)
......
This diff is collapsed.
...@@ -16,6 +16,7 @@ from soitool.modules.module_authentication_board import ( ...@@ -16,6 +16,7 @@ from soitool.modules.module_authentication_board import (
) )
from soitool.modules.module_subtractorcodes import SubtractorcodesModule from soitool.modules.module_subtractorcodes import SubtractorcodesModule
from soitool.modules.module_freetext import FreeTextModule from soitool.modules.module_freetext import FreeTextModule
from soitool.modules.module_predefined_codes import PredefinedCodesModule
from soitool.accept_reject_dialog import AcceptRejectDialog from soitool.accept_reject_dialog import AcceptRejectDialog
...@@ -27,6 +28,7 @@ MODULE_CHOICES = [ ...@@ -27,6 +28,7 @@ MODULE_CHOICES = [
AuthenticationBoardModule, AuthenticationBoardModule,
SubtractorcodesModule, SubtractorcodesModule,
FreeTextModule, FreeTextModule,
PredefinedCodesModule,
] ]
......
...@@ -10,6 +10,7 @@ from soitool.modules.module_authentication_board import ( ...@@ -10,6 +10,7 @@ from soitool.modules.module_authentication_board import (
) )
from soitool.modules.module_subtractorcodes import SubtractorcodesModule from soitool.modules.module_subtractorcodes import SubtractorcodesModule
from soitool.modules.module_freetext import FreeTextModule from soitool.modules.module_freetext import FreeTextModule
from soitool.modules.module_predefined_codes import PredefinedCodesModule
# Valid schema for serialized SOI # Valid schema for serialized SOI
SERIALIZED_SOI_SCHEMA = Schema( SERIALIZED_SOI_SCHEMA = Schema(
...@@ -173,7 +174,7 @@ def export_soi(soi, compressed=True): ...@@ -173,7 +174,7 @@ def export_soi(soi, compressed=True):
file.close() file.close()
def import_soi(file_path): def import_soi(file_path, database):
"""Import compressed or uncompressed serialized SOI. """Import compressed or uncompressed serialized SOI.
Reads content of file and decompresses it for .txt-files. Reads content of file and decompresses it for .txt-files.
...@@ -183,6 +184,8 @@ def import_soi(file_path): ...@@ -183,6 +184,8 @@ def import_soi(file_path):
---------- ----------
file_path : string file_path : string
Full path to a file containing serialized SOI. Full path to a file containing serialized SOI.
database : soitool.database.Database
Database-instance passed to specific modules.
Returns Returns
------- -------
...@@ -200,18 +203,22 @@ def import_soi(file_path): ...@@ -200,18 +203,22 @@ def import_soi(file_path):
serialized = file.read() serialized = file.read()
if file_path.endswith(".txt"): if file_path.endswith(".txt"):
return construct_soi_from_serialized(serialized, compressed=True) return construct_soi_from_serialized(
serialized, database, compressed=True
)
return construct_soi_from_serialized(serialized) return construct_soi_from_serialized(serialized, database)
def construct_soi_from_serialized(serialized, compressed=False): def construct_soi_from_serialized(serialized, database, compressed=False):
"""Construct an SOI-object from a serialized SOI. """Construct an SOI-object from a serialized SOI.
Parameters Parameters
---------- ----------
serialized : string serialized : string
Serialized SOI. Serialized SOI.
database : soitool.database.Database
Database-instance passed to specific modules.
compressed : bool, optional compressed : bool, optional
True if serialized SOI is compressed, by default False. True if serialized SOI is compressed, by default False.
...@@ -236,8 +243,12 @@ def construct_soi_from_serialized(serialized, compressed=False): ...@@ -236,8 +243,12 @@ def construct_soi_from_serialized(serialized, compressed=False):
raise ValueError("Serialized SOI does not have correct format.") raise ValueError("Serialized SOI does not have correct format.")
# Construct modules and attachments # Construct modules and attachments
modules = construct_modules_from_serialized(serialized["modules"]) modules = construct_modules_from_serialized(
attachments = construct_modules_from_serialized(serialized["attachments"]) serialized["modules"], database
)
attachments = construct_modules_from_serialized(
serialized["attachments"], database
)
# Create SOI # Create SOI
soi = SOI( soi = SOI(
...@@ -261,13 +272,15 @@ def construct_soi_from_serialized(serialized, compressed=False): ...@@ -261,13 +272,15 @@ def construct_soi_from_serialized(serialized, compressed=False):
return soi return soi
def construct_modules_from_serialized(serialized_modules): def construct_modules_from_serialized(serialized_modules, database):
"""Instantiate modules from serialized format. """Instantiate modules from serialized format.
Parameters Parameters
---------- ----------
serialized_modules : list serialized_modules : list
Containing dicts with serialized modules or attachment-modules. Containing dicts with serialized modules or attachment-modules.
database : soitool.database.Database
Database-instance passed to specific modules.
Returns Returns
------- -------
...@@ -283,16 +296,14 @@ def construct_modules_from_serialized(serialized_modules): ...@@ -283,16 +296,14 @@ def construct_modules_from_serialized(serialized_modules):
for module in serialized_modules: for module in serialized_modules:
module_type = module["type"] module_type = module["type"]
size = module["size"]
data = module["data"]
if module_type == "TableModule": if module_type == "TableModule":
size = module["size"]
data = module["data"]
modules.append( modules.append(
{"widget": TableModule(size, data), "meta": module["meta"]} {"widget": TableModule(size, data), "meta": module["meta"]}
) )
elif module_type == "AuthenticationBoardModule": elif module_type == "AuthenticationBoardModule":
size = module["size"]
data = module["data"]
modules.append( modules.append(
{ {
"widget": AuthenticationBoardModule(size, data), "widget": AuthenticationBoardModule(size, data),
...@@ -300,8 +311,6 @@ def construct_modules_from_serialized(serialized_modules): ...@@ -300,8 +311,6 @@ def construct_modules_from_serialized(serialized_modules):
} }
) )
elif module_type == "SubtractorcodesModule": elif module_type == "SubtractorcodesModule":
size = module["size"]
data = module["data"]
modules.append( modules.append(
{ {
"widget": SubtractorcodesModule(size, data), "widget": SubtractorcodesModule(size, data),
...@@ -309,11 +318,16 @@ def construct_modules_from_serialized(serialized_modules): ...@@ -309,11 +318,16 @@ def construct_modules_from_serialized(serialized_modules):
} }
) )
elif module_type == "FreeTextModule": elif module_type == "FreeTextModule":
size = module["size"]
data = module["data"]
modules.append( modules.append(
{"widget": FreeTextModule(size, data), "meta": module["meta"]} {"widget": FreeTextModule(size, data), "meta": module["meta"]}
) )
elif module_type == "PredefinedCodesModule":
modules.append(
{
"widget": PredefinedCodesModule(database, data),
"meta": module["meta"],
}
)
else: else:
raise TypeError( raise TypeError(
"Module-type '{}' is not recognized.".format(module_type) "Module-type '{}' is not recognized.".format(module_type)
......
...@@ -26,7 +26,8 @@ class SOITableView(QTableView): ...@@ -26,7 +26,8 @@ class SOITableView(QTableView):
Parameters Parameters
---------- ----------
database : soitool.database.Database database : soitool.database.Database
Is used to create a QSqlDatabase from the database-file. Is used to create a QSqlDatabase from the database-file,
and to instantiate SOIWorkspaceWidget.
tab_widget : QTabWidget tab_widget : QTabWidget
Is used to open a new tab. Is used to open a new tab.
...@@ -38,8 +39,10 @@ class SOITableView(QTableView): ...@@ -38,8 +39,10 @@ class SOITableView(QTableView):
def __init__(self, database, tab_widget): def __init__(self, database, tab_widget):
super().__init__() super().__init__()
self.database = database
db = QSqlDatabase.addDatabase(DBTYPE, CONNAME) db = QSqlDatabase.addDatabase(DBTYPE, CONNAME)
db.setDatabaseName(database.db_path) db.setDatabaseName(self.database.db_path)
self.tab_widget = tab_widget self.tab_widget = tab_widget
if not db.open(): if not db.open():
...@@ -91,8 +94,10 @@ class SOITableView(QTableView): ...@@ -91,8 +94,10 @@ class SOITableView(QTableView):
compressed_soi = self.model().index(row, 2).data() compressed_soi = self.model().index(row, 2).data()
# Construct SOI and create SOIWorkspaceWidget # Construct SOI and create SOIWorkspaceWidget
soi = construct_soi_from_serialized(compressed_soi, compressed=True) soi = construct_soi_from_serialized(
tab = SOIWorkspaceWidget(soi) compressed_soi, self.database, compressed=True
)
tab = SOIWorkspaceWidget(self.database, soi)
# Add and select tab # Add and select tab
self.tab_widget.addTab(tab, soi.title) self.tab_widget.addTab(tab, soi.title)
......
...@@ -18,6 +18,10 @@ from soitool.inline_editable_soi_view import InlineEditableSOIView ...@@ -18,6 +18,10 @@ from soitool.inline_editable_soi_view import InlineEditableSOIView
from soitool.setup_settings import Setup from soitool.setup_settings import Setup
from soitool.new_module_dialog import NewModuleDialog from soitool.new_module_dialog import NewModuleDialog
from soitool.dialog_wrappers import exec_warning_dialog from soitool.dialog_wrappers import exec_warning_dialog
from soitool.modules.module_predefined_codes import PredefinedCodesModule
# List of SOI-modules that require a database as parameter
DATABASE_MODULES = [PredefinedCodesModule]
class SOIWorkspaceWidget(QWidget): class SOIWorkspaceWidget(QWidget):
...@@ -25,14 +29,17 @@ class SOIWorkspaceWidget(QWidget): ...@@ -25,14 +29,17 @@ class SOIWorkspaceWidget(QWidget):
Creates a new SOI by default, but can receive an existing SOI through Creates a new SOI by default, but can receive an existing SOI through
it's init parameter 'soi'. it's init parameter 'soi'.
The parameter 'database' is passed to instantiate specific modules.
The widget is used inside tabs in our application, and contains a sidebar The widget is used inside tabs in our application, and contains a sidebar
with a module list along with a view of the SOI. with a module list along with a view of the SOI.
""" """
def __init__(self, soi=None): def __init__(self, database, soi=None):
super().__init__() super().__init__()
self.database = database
if soi is None: if soi is None:
self.soi = SOI() self.soi = SOI()
else: else:
...@@ -118,8 +125,14 @@ class SOIWorkspaceWidget(QWidget): ...@@ -118,8 +125,14 @@ class SOIWorkspaceWidget(QWidget):
) )
try: try:
if module_widget_implementation in DATABASE_MODULES:
module = module_widget_implementation(
database=self.database
)
else:
module = module_widget_implementation()
self.soi.add_module( self.soi.add_module(
module_name, module_widget_implementation(), is_attachment, module_name, module, is_attachment,
) )
except ModuleNameTaken: except ModuleNameTaken:
exec_warning_dialog( exec_warning_dialog(
......
[ [
{
"word": "0",
"category": "Tall",
"type": "Liten"
},
{
"word": "1",
"category": "Tall",
"type": "Liten"
},
{
"word": "2",
"category": "Tall",
"type": "Liten"
},
{
"word": "3",
"category": "Tall",
"type": "Liten"
},
{
"word": "4",
"category": "Tall",
"type": "Liten"
},
{ {
"word": "40 mm", "word": "40 mm",
"category": "V\u00e5penteknisk", "category": "V\u00e5penteknisk",
"type": "Stor" "type": "Stor"
}, },
{
"word": "5",
"category": "Tall",
"type": "Liten"
},
{
"word": "6",
"category": "Tall",
"type": "Liten"
},
{
"word": "7",
"category": "Tall",
"type": "Liten"
},
{
"word": "8",
"category": "Tall",
"type": "Liten"
},
{
"word": "9",
"category": "Tall",
"type": "Liten"
},
{
"word": "A",
"category": "Bokstaver",
"type": "Liten"
},
{ {
"word": "AG3", "word": "AG3",
"category": "V\u00e5penteknisk", "category": "V\u00e5penteknisk",
...@@ -34,6 +89,11 @@ ...@@ -34,6 +89,11 @@
"category": "Testkategori", "category": "Testkategori",
"type": "Stor" "type": "Stor"
}, },
{
"word": "B",
"category": "Bokstaver",
"type": "Liten"
},
{ {
"word": "BV-206", "word": "BV-206",
"category": "Kj\u00f8ret\u00f8y", "category": "Kj\u00f8ret\u00f8y",
...@@ -47,12 +107,17 @@ ...@@ -47,12 +107,17 @@
{ {
"word": "Bombe", "word": "Bombe",
"category": "V\u00e5penteknisk", "category": "V\u00e5penteknisk",
"type": "Stor" "type": "Liten"
}, },
{ {
"word": "Bro", "word": "Bro",
"category": "Landemerker", "category": "Landemerker",
"type": "Stor" "type": "Liten"
},
{
"word": "C",
"category": "Bokstaver",
"type": "Liten"
}, },
{ {
"word": "CV 90", "word": "CV 90",
...@@ -84,6 +149,11 @@ ...@@ -84,6 +149,11 @@
"category": "Testkategori", "category": "Testkategori",
"type": "Stor" "type": "Stor"
}, },
{
"word": "D",
"category": "Bokstaver",
"type": "Liten"
},
{ {
"word": "Dal", "word": "Dal",
"category": "Landemerker", "category": "Landemerker",
...@@ -104,6 +174,11 @@ ...@@ -104,6 +174,11 @@
"category": "Testkategori", "category": "Testkategori",
"type": "Stor" "type": "Stor"
}, },
{
"word": "E",
"category": "Bokstaver",
"type": "Liten"
},
{ {
"word": "ERYX", "word": "ERYX",
"category": "V\u00e5penteknisk", "category": "V\u00e5penteknisk",
...@@ -117,7 +192,7 @@ ...@@ -117,7 +192,7 @@
{ {
"word": "Elv", "word": "Elv",
"category": "Landemerker", "category": "Landemerker",
"type": "Stor" "type": "Liten"
}, },
{ {
"word": "Er i rute", "word": "Er i rute",
...@@ -129,11 +204,26 @@ ...@@ -129,11 +204,26 @@
"category": "Testkategori", "category": "Testkategori",
"type": "Stor" "type": "Stor"
}, },
{
"word": "F",
"category": "Bokstaver",
"type": "Liten"
},
{ {
"word": "Ferdigstilling", "word": "Ferdigstilling",
"category": "Straff", "category": "Straff",
"type": "Stor" "type": "Stor"
}, },
{
"word": "Fram",
"category": "Retninger",
"type": "Liten"
},
{
"word": "G",
"category": "Bokstaver",
"type": "Liten"
},
{ {
"word": "G-notes", "word": "G-notes",
"category": "Testkategori", "category": "Testkategori",
...@@ -145,29 +235,24 @@ ...@@ -145,29 +235,24 @@
"type": "Stor" "type": "Stor"
}, },
{ {
"word": "HK-416", "word": "H",
"category": "V\u00e5penteknisk", "category": "Bokstaver",
"type": "Stor" "type": "Liten"
},
{
"word": "Hadza",
"category": "Testkategori",
"type": "Stor"
}, },
{ {
"word": "Heron", "word": "HK-416",
"category": "Testkategori", "category": "V\u00e5penteknisk",
"type": "Stor" "type": "Liten"
}, },
{ {
"word": "Hindu", "word": "Helikopter",
"category": "Testkategori", "category": "St\u00f8tte",
"type": "Stor" "type": "Liten"
}, },
{ {
"word": "H\u00e5ndgranat", "word": "H\u00e5ndgranat",
"category": "V\u00e5penteknisk", "category": "V\u00e5penteknisk",
"type": "Stor" "type": "Liten"
}, },
{ {
"word": "H\u00f8yde", "word": "H\u00f8yde",
...@@ -175,15 +260,30 @@ ...@@ -175,15 +260,30 @@
"type": "Stor" "type": "Stor"
}, },
{ {
"word": "Jacqui", "word": "I",
"category": "Testkategori", "category": "Bokstaver",
"type": "Stor" "type": "Liten"
},
{
"word": "J",
"category": "Bokstaver",
"type": "Liten"
}, },
{ {
"word": "Jamnagar", "word": "Jamnagar",
"category": "Testkategori", "category": "Testkategori",
"type": "Stor" "type": "Stor"
}, },
{
"word": "K",
"category": "Bokstaver",
"type": "Liten"
},
{
"word": "K9",
"category": "St\u00f8tte",
"type": "Liten"
},
{ {
"word": "Khronos", "word": "Khronos",
"category": "Testkategori", "category": "Testkategori",
...@@ -205,18 +305,13 @@ ...@@ -205,18 +305,13 @@
"type": "Stor" "type": "Stor"
}, },
{ {
"word": "Leopard 2", "word": "L",
"category": "Kj\u00f8ret\u00f8y", "category": "Bokstaver",
"type": "Stor" "type": "Liten"
},
{
"word": "Louis Quatorze",
"category": "Testkategori",
"type": "Stor"
}, },
{ {
"word": "Lucifer", "word": "Leopard 2",
"category": "Testkategori", "category": "Kj\u00f8ret\u00f8y",
"type": "Stor" "type": "Stor"
}, },
{ {
...@@ -224,6 +319,11 @@ ...@@ -224,6 +319,11 @@
"category": "V\u00e5penteknisk", "category": "V\u00e5penteknisk",
"type": "Stor" "type": "Stor"
}, },
{
"word": "M",
"category": "Bokstaver",
"type": "Liten"
},
{ {
"word": "M04", "word": "M04",
"category": "Bekledning", "category": "Bekledning",
...@@ -255,19 +355,14 @@ ...@@ -255,19 +355,14 @@
"type": "Stor" "type": "Stor"
}, },
{ {
"word": "MP-5", "word": "MP",
"category": "V\u00e5penteknisk", "category": "St\u00f8tte",
"type": "Liten" "type": "Liten"
}, },
{ {
"word": "Maeander", "word": "MP-5",
"category": "Testkategori", "category": "V\u00e5penteknisk",
"type": "Stor" "type": "Liten"
},
{
"word": "Manila",
"category": "Testkategori",
"type": "Stor"
}, },
{ {
"word": "Meldingsblankett (blokker)", "word": "Meldingsblankett (blokker)",
...@@ -275,14 +370,14 @@ ...@@ -275,14 +370,14 @@
"type": "Stor" "type": "Stor"
}, },
{ {
"word": "Mendoza", "word": "Mine",
"category": "Testkategori", "category": "V\u00e5penteknisk",
"type": "Stor" "type": "Stor"
}, },
{ {
"word": "Midwest", "word": "N",
"category": "Testkategori", "category": "Bokstaver",
"type": "Stor" "type": "Liten"
}, },
{ {
"word": "Netting under over", "word": "Netting under over",
...@@ -290,19 +385,19 @@ ...@@ -290,19 +385,19 @@
"type": "Stor" "type": "Stor"
}, },
{ {
"word": "Nikkei index", "word": "Nord",
"category": "Testkategori", "category": "Retninger",
"type": "Stor" "type": "Liten"
}, },
{ {
"word": "Orthodox Jew", "word": "O",
"category": "Testkategori", "category": "Bokstaver",
"type": "Stor" "type": "Liten"
}, },
{ {
"word": "P-80", "word": "P",
"category": "V\u00e5penteknisk", "category": "Bokstaver",
"type": "Stor" "type": "Liten"
}, },
{ {
"word": "PACE-batteri/BA-3090 (stk)", "word": "PACE-batteri/BA-3090 (stk)",
...@@ -329,6 +424,16 @@ ...@@ -329,6 +424,16 @@
"category": "Testkategori", "category": "Testkategori",
"type": "Stor" "type": "Stor"
}, },
{
"word": "Q",
"category": "Bokstaver",
"type": "Liten"
},
{
"word": "R",
"category": "Bokstaver",
"type": "Liten"
},
{ {
"word": "Regnt\u00f8y", "word": "Regnt\u00f8y",
"category": "Bekledning", "category": "Bekledning",
...@@ -350,32 +455,22 @@ ...@@ -350,32 +455,22 @@
"type": "Stor" "type": "Stor"
}, },
{ {
"word": "Saaniches", "word": "S",
"category": "Testkategori", "category": "Bokstaver",
"type": "Stor" "type": "Liten"
},
{
"word": "Sarah",
"category": "Testkategori",
"type": "Stor"
},
{
"word": "Scylla",
"category": "Testkategori",
"type": "Stor"
}, },
{ {
"word": "Senate", "word": "Saaniches",
"category": "Testkategori", "category": "Testkategori",
"type": "Stor" "type": "Stor"
}, },
{ {
"word": "Sephardim", "word": "Sanitet",
"category": "Testkategori", "category": "St\u00f8tte",
"type": "Stor" "type": "Liten"
}, },
{ {
"word": "Serpentes", "word": "Sarah",
"category": "Testkategori", "category": "Testkategori",
"type": "Stor" "type": "Stor"
}, },
...@@ -390,44 +485,39 @@ ...@@ -390,44 +485,39 @@
"type": "Stor" "type": "Stor"
}, },
{ {
"word": "Silicon Wadi", "word": "Stans, -e, -et",
"category": "Testkategori", "category": "Uttrykk/tiltak/oppdrag",
"type": "Stor"
},
{
"word": "Siralun",
"category": "Testkategori",
"type": "Stor" "type": "Stor"
}, },
{ {
"word": "Somerset Island", "word": "Sti",
"category": "Testkategori", "category": "Landemerker",
"type": "Stor" "type": "Liten"
}, },
{ {
"word": "Squalicum's", "word": "Surinamese",
"category": "Testkategori", "category": "Testkategori",
"type": "Stor" "type": "Stor"
}, },
{ {
"word": "Stans, -e, -et", "word": "S\u00f8r",
"category": "Uttrykk/tiltak/oppdrag", "category": "Retninger",
"type": "Stor" "type": "Liten"
}, },
{ {
"word": "Sti", "word": "T",
"category": "Landemerker", "category": "Bokstaver",
"type": "Stor" "type": "Liten"
}, },
{ {
"word": "Surinamese", "word": "Tilbake",
"category": "Testkategori", "category": "Retninger",
"type": "Stor" "type": "Liten"
}, },
{ {
"word": "Tsvetaeva", "word": "U",
"category": "Testkategori", "category": "Bokstaver",
"type": "Stor" "type": "Liten"
}, },
{ {
"word": "U-235", "word": "U-235",
...@@ -444,6 +534,11 @@ ...@@ -444,6 +534,11 @@
"category": "Personlig utrustning", "category": "Personlig utrustning",
"type": "Stor" "type": "Stor"
}, },
{
"word": "V",
"category": "Bokstaver",
"type": "Liten"
},
{ {
"word": "Vann (l)", "word": "Vann (l)",
"category": "Etterforsyninger", "category": "Etterforsyninger",
...@@ -460,94 +555,29 @@ ...@@ -460,94 +555,29 @@
"type": "Stor" "type": "Stor"
}, },
{ {
"word": "Vest plate", "word": "Vest",
"category": "Personnlig utrustning", "category": "Retninger",
"type": "Stor" "type": "Liten"
},
{
"word": "Vicar of Christ",
"category": "Testkategori",
"type": "Stor"
},
{
"word": "Visayan",
"category": "Testkategori",
"type": "Stor"
},
{
"word": "Winterfest",
"category": "Testkategori",
"type": "Stor"
},
{
"word": "acanthocephalan",
"category": "Testkategori",
"type": "Stor"
},
{
"word": "acceptress",
"category": "Testkategori",
"type": "Stor"
},
{
"word": "accountants",
"category": "Testkategori",
"type": "Stor"
},
{
"word": "advertisers",
"category": "Testkategori",
"type": "Stor"
},
{
"word": "aestivates",
"category": "Testkategori",
"type": "Stor"
},
{
"word": "aetiologies",
"category": "Testkategori",
"type": "Stor"
},
{
"word": "after-damp",
"category": "Testkategori",
"type": "Stor"
},
{
"word": "after-dinner",
"category": "Testkategori",
"type": "Stor"
},
{
"word": "alalia",
"category": "Testkategori",
"type": "Stor"
},
{
"word": "alanyl",
"category": "Testkategori",
"type": "Stor"
}, },
{ {
"word": "alcoholometry", "word": "W",
"category": "Testkategori", "category": "Bokstaver",
"type": "Stor" "type": "Liten"
}, },
{ {
"word": "alfresco", "word": "X",
"category": "Testkategori", "category": "Bokstaver",
"type": "Stor" "type": "Liten"
}, },
{ {
"word": "algolagnic", "word": "Y",
"category": "Testkategori", "category": "Bokstaver",
"type": "Stor" "type": "Liten"
}, },
{ {
"word": "allured", "word": "Z",
"category": "Testkategori", "category": "Bokstaver",
"type": "Stor" "type": "Liten"
}, },
{ {
"word": "angleworm", "word": "angleworm",
...@@ -574,26 +604,6 @@ ...@@ -574,26 +604,6 @@
"category": "Testkategori", "category": "Testkategori",
"type": "Stor" "type": "Stor"
}, },
{
"word": "antitragicus",
"category": "Testkategori",
"type": "Stor"
},
{
"word": "apama",
"category": "Testkategori",
"type": "Stor"
},
{
"word": "aphoristic",
"category": "Testkategori",
"type": "Stor"
},
{
"word": "archpriest",
"category": "Testkategori",
"type": "Stor"
},
{ {
"word": "areola", "word": "areola",
"category": "Testkategori", "category": "Testkategori",
...@@ -609,21 +619,6 @@ ...@@ -609,21 +619,6 @@
"category": "Testkategori", "category": "Testkategori",
"type": "Stor" "type": "Stor"
}, },
{
"word": "assubjugated",
"category": "Testkategori",
"type": "Stor"
},
{
"word": "athanasy",
"category": "Testkategori",
"type": "Stor"
},
{
"word": "atony",
"category": "Testkategori",
"type": "Stor"
},
{ {
"word": "attapulgite", "word": "attapulgite",
"category": "Testkategori", "category": "Testkategori",
...@@ -3374,9 +3369,14 @@ ...@@ -3374,9 +3369,14 @@
"category": "Testkategori", "category": "Testkategori",
"type": "Stor" "type": "Stor"
}, },
{
"word": "\u00d8st",
"category": "Retninger",
"type": "Liten"
},
{ {
"word": "\u00e6nigmata", "word": "\u00e6nigmata",
"category": "Testkategori", "category": "Testkategori",
"type": "Stor" "type": "Stor"
} }
] ]
\ No newline at end of file
...@@ -179,6 +179,48 @@ class DatabaseTest(unittest.TestCase): ...@@ -179,6 +179,48 @@ class DatabaseTest(unittest.TestCase):
categories_db = self.database.get_categories() categories_db = self.database.get_categories()
self.assertEqual(categories_file, categories_db) self.assertEqual(categories_file, categories_db)
def test_get_random_category_word(self):
"""Assert function get_random_category_word works as expected."""
# Read all category-words
stmt = "SELECT Word from CategoryWords"
queried = self.database.conn.execute(stmt).fetchall()
category_words = [row["Word"] for row in queried]
# Generate 5 random category-words and assert that they are
# from table CategoryWords
generated_category_words = []
for _ in range(5):
category_word = self.database.get_random_category_word()
self.assertTrue(category_word in category_words)
generated_category_words.append(category_word)
# Assert that all words are not equal
self.assertTrue(len(set(generated_category_words)) > 1)
def test_get_categories_from_codebook(self):
"""Assert function get_categories_from_codebook works as expected."""
# Get categories in full and small codebook
stmt = "SELECT Category FROM Codebook GROUP BY Category"
queried = self.database.conn.execute(stmt).fetchall()
expected_categories_full = [row["Category"] for row in queried]
stmt = (
"SELECT Category FROM Codebook "
"WHERE Type='Liten' GROUP BY Category"
)
queried = self.database.conn.execute(stmt).fetchall()
expected_categories_small = [row["Category"] for row in queried]
# Get categories in full and small codebook through function
actual_categories_full = self.database.get_categories_from_codebook()
actual_categories_small = self.database.get_categories_from_codebook(
small=True
)
# Assert actual categories matches expected categories
self.assertEqual(actual_categories_full, expected_categories_full)
self.assertEqual(actual_categories_small, expected_categories_small)
def test_get_codebook(self): def test_get_codebook(self):
"""Assert function get_codebook returns full codebook.""" """Assert function get_codebook returns full codebook."""
# Load full codebook # Load full codebook
...@@ -226,6 +268,37 @@ class DatabaseTest(unittest.TestCase): ...@@ -226,6 +268,37 @@ class DatabaseTest(unittest.TestCase):
self.assertRegex(actual[i]["code"], "[A-Z]{" + str(code_len) + "}") self.assertRegex(actual[i]["code"], "[A-Z]{" + str(code_len) + "}")
self.assertEqual(entry["type"], actual[i]["type"]) self.assertEqual(entry["type"], actual[i]["type"])
def test_get_codebook_expressions_in_category(self):
"""Test function get_codebook_expressions_in_category."""
# Get a category in full and small codebook
category_full = self.database.get_categories_from_codebook()[0]
category_small = self.database.get_categories_from_codebook(
small=True
)[0]
# Get expressions from category
stmt = "SELECT Word FROM Codebook WHERE Category=?"
queried = self.database.conn.execute(stmt, (category_full,)).fetchall()
expected_expressions_full = [row["Word"] for row in queried]
stmt += " AND Type='Liten'"
queried = self.database.conn.execute(
stmt, (category_small,)
).fetchall()
expected_expressions_small = [row["Word"] for row in queried]
# Get expressions from function
actual_expr_full = self.database.get_codebook_expressions_in_category(
category_full
)
actual_expr_small = self.database.get_codebook_expressions_in_category(
category_small, small=True
)
# Assert actual expressions matches expected expressions
self.assertEqual(actual_expr_full, expected_expressions_full)
self.assertEqual(actual_expr_small, expected_expressions_small)
def test_update_codebook(self): def test_update_codebook(self):
"""Test that the codes get updated.""" """Test that the codes get updated."""
# Get entries before and after update # Get entries before and after update
...@@ -281,7 +354,7 @@ class DatabaseTest(unittest.TestCase): ...@@ -281,7 +354,7 @@ class DatabaseTest(unittest.TestCase):
) )
else: else:
print("ERROR: Database is not 675 entries long, cant run test") print("ERROR: Database is not 676 entries long, cant run test")
self.assertTrue(False) # pylint: disable=W1503 self.assertTrue(False) # pylint: disable=W1503
def test_seconds_to_next_update(self): def test_seconds_to_next_update(self):
......
"""Tests for PredefinedCodesModule."""
import unittest
import string
from test.test_database import TESTDBPATH
from PySide2.QtCore import Qt, QTimer
from PySide2 import QtGui
from PySide2.QtTest import QTest
from PySide2.QtWidgets import QApplication
from soitool.database import Database
from soitool.modules.module_predefined_codes import (
PredefinedCodesModule,
HEADLINE,
DEFAULT_COLUMN_HEIGHT,
)
ALPHABET = string.ascii_uppercase
if isinstance(QtGui.qApp, type(None)):
app = QApplication([])
else:
app = QtGui.qApp
class TestDefaultPredefinedCodesModule(unittest.TestCase):
"""TestCase for PredefinedCodesModule."""
def setUp(self):
"""Create database and PredefinedCodesModule."""
self.database = Database(db_path=TESTDBPATH)
# Get categories in small codebook
self.categories = self.database.get_categories_from_codebook(
small=True
)
def press_enter_on_active_dialog():
active_widget = app.activeModalWidget()
QTest.keyClick(active_widget, Qt.Key_Enter)
QTimer.singleShot(0, press_enter_on_active_dialog)
self.module = PredefinedCodesModule(self.database)
def test_default_dialog(self):
"""Test default values in dialog."""
def test_and_close_dialog():
dialog = app.activeModalWidget()
# Assert prefilled headline is correct
self.assertEqual(dialog.edit_headline.text(), HEADLINE)
# Assert prefilled warning-word is not empty
warning_word = dialog.edit_warning_word.text()
self.assertTrue(len(warning_word) > 0)
# Assert prefilled maximum column-height is correct
maximum_column_height_value = dialog.edit_column_height.value()
self.assertTrue(
maximum_column_height_value >= DEFAULT_COLUMN_HEIGHT
)
# Assert categories are correct
for i, category in enumerate(self.categories):
dialog_category = dialog.list_category_order.item(i).text()
self.assertEqual(category, dialog_category)
# Close dialog
dialog.accept()
# Use shortcut to open dialog
QTimer.singleShot(0, test_and_close_dialog)
QTest.keyClicks(self.module, "R", Qt.ControlModifier)
def test_default_module(self):
"""Test default module."""
# Assert headline is correct
self.assertEqual(self.module.headline.text(), HEADLINE)
# Assert warning-word is not empty
self.assertTrue(len(self.module.warning_word.text()) > 0)
# Assert module has one table per category
self.assertEqual(len(self.module.tables), len(self.categories))
# Assert each table has the correct headline and contents
for i, table in enumerate(self.module.tables):
expected_headline = " " + ALPHABET[i] + " " + self.categories[i]
actual_headline = table.item(0, 0).text()
self.assertEqual(actual_headline, expected_headline)
expressions = self.database.get_codebook_expressions_in_category(
self.categories[i], small=True
)
# Assert codes are correct and read expressions in table
actual_expressions = []
for j in range(1, table.rowCount()):
expected_code = ALPHABET[j - 1]
actual_code = table.item(j, 0).text()
self.assertEqual(actual_code, expected_code)
actual_expressions.append(table.item(j, 1).text())
# Assert expressions are correct
for actual_expression in actual_expressions:
self.assertTrue(actual_expression in expressions)
class TestDefaultPredefinedCodesModuleFromData(unittest.TestCase):
"""TestCase for initializing PredefinedCodesModule from data."""
def test_create_from_data(self):
"""Test creating PredefinedCodesModule from data."""
test_data = {
"headline": "HeadlineText",
"warning_word": "WarningWordText",
"maximum_column_height": 500,
"categories": ["CategoryOne", "CategoryTwo"],
"tables": [
{
"table_headline": "TableHeadlineOne",
"expressions": ["expressionOne", "expressionTwo"],
},
{
"table_headline": "TableHeadlineTwo",
"expressions": ["expressionOne", "expressionTwo"],
},
],
}
# Create PredefinedCodesModule from data
module = PredefinedCodesModule(Database(db_path=TESTDBPATH), test_data)
# Assert headline is correct
self.assertEqual(module.headline.text(), "HeadlineText")
# Assert warning-word is correct
self.assertEqual(module.warning_word.text(), "WarningWordText")
# Assert module has two tables
self.assertEqual(len(module.tables), 2)
# Assert each table has the correct headline and expressions
table_one = module.tables[0]
table_two = module.tables[1]
# Assert correct headlines
self.assertEqual(table_one.item(0, 0).text(), "TableHeadlineOne")
self.assertEqual(table_two.item(0, 0).text(), "TableHeadlineTwo")
# Assert correct codes and expressions
self.assertEqual(table_one.item(1, 1).text(), "expressionOne")
self.assertEqual(table_one.item(2, 1).text(), "expressionTwo")
self.assertEqual(table_two.item(1, 1).text(), "expressionOne")
self.assertEqual(table_two.item(2, 1).text(), "expressionTwo")
for i in range(1, 3):
self.assertEqual(table_one.item(i, 0).text(), ALPHABET[i - 1])
self.assertEqual(table_two.item(i, 0).text(), ALPHABET[i - 1])
...@@ -19,13 +19,30 @@ from soitool.modules.module_authentication_board import ( ...@@ -19,13 +19,30 @@ from soitool.modules.module_authentication_board import (
AuthenticationBoardModule, AuthenticationBoardModule,
) )
from soitool.modules.module_subtractorcodes import SubtractorcodesModule from soitool.modules.module_subtractorcodes import SubtractorcodesModule
from soitool.modules.module_predefined_codes import PredefinedCodesModule
from soitool.new_module_dialog import MODULE_CHOICES from soitool.new_module_dialog import MODULE_CHOICES
from soitool.soi_workspace_widget import DATABASE_MODULES
from soitool.database import Database
# The error being ignored here is pylint telling us that 'test' is a standard
# module, so the import should be placed further up. In our case we have an
# actual custom module called 'test', so pylint is confused.
# pylint: disable=C0411
from test.test_database import TESTDBPATH
if isinstance(QtGui.qApp, type(None)): if isinstance(QtGui.qApp, type(None)):
app = QApplication([]) app = QApplication([])
else: else:
app = QtGui.qApp app = QtGui.qApp
# Modules with a popup as part of their __init__
POPUP_MODULES = [
AuthenticationBoardModule,
SubtractorcodesModule,
PredefinedCodesModule,
]
def screen_information(): def screen_information():
"""Get string with information about the screen. """Get string with information about the screen.
...@@ -87,20 +104,28 @@ class TestModulesAcrossResolutions(unittest.TestCase): ...@@ -87,20 +104,28 @@ class TestModulesAcrossResolutions(unittest.TestCase):
deleted, or changed. It will fail until it is updated. deleted, or changed. It will fail until it is updated.
""" """
expected_result = [ expected_result = [
{"x": 0, "y": 0, "page": 1, "name": "AuthenticationBoardModule"}, {"x": 0, "y": 0, "page": 1, "name": "PredefinedCodesModule"},
{"x": 407.5, "y": 0, "page": 1, "name": "SubtractorcodesModule"}, {"x": 654, "y": 0, "page": 1, "name": "AuthenticationBoardModule"},
{"x": 602.0, "y": 0, "page": 1, "name": "FreeTextModule"}, {"x": 1061.5, "y": 0, "page": 1, "name": "SubtractorcodesModule"},
{"x": 702.0, "y": 0, "page": 1, "name": "TableModule"}, {"x": 1287.0, "y": 0, "page": 1, "name": "FreeTextModule"},
{"x": 1387.0, "y": 0, "page": 1, "name": "TableModule"},
] ]
# For use with modules that require a database
database = Database(db_path=TESTDBPATH)
def press_enter(): def press_enter():
active_widget = app.activeModalWidget() active_widget = app.activeModalWidget()
# triple click to select existing text for overwrite
QTest.mouseDClick(active_widget.edit_headline, Qt.LeftButton) # AuthenticationBoardModule needs special treatment because the
QTest.mouseClick(active_widget.edit_headline, Qt.LeftButton) # title can contain random info
# need to overwrite text because title otherwise contains if isinstance(active_widget, AuthenticationBoardModule):
# unpredictable text # triple click to select existing text for overwrite
QTest.keyClicks(active_widget.edit_headline, "TestTitle") QTest.mouseDClick(active_widget.edit_headline, Qt.LeftButton)
QTest.mouseClick(active_widget.edit_headline, Qt.LeftButton)
# need to overwrite text because title otherwise contains
# unpredictable text
QTest.keyClicks(active_widget.edit_headline, "TestTitle")
QTest.keyClick(active_widget, Qt.Key_Enter) QTest.keyClick(active_widget, Qt.Key_Enter)
soi = SOI() soi = SOI()
...@@ -108,9 +133,13 @@ class TestModulesAcrossResolutions(unittest.TestCase): ...@@ -108,9 +133,13 @@ class TestModulesAcrossResolutions(unittest.TestCase):
for module in MODULE_CHOICES: for module in MODULE_CHOICES:
# If we're adding one of the modules with a popup as part of it's # If we're adding one of the modules with a popup as part of it's
# constructor we need to singleShot pressing enter to close it # constructor we need to singleShot pressing enter to close it
if module in (AuthenticationBoardModule, SubtractorcodesModule): if module in POPUP_MODULES:
QTimer.singleShot(0, press_enter) QTimer.singleShot(0, press_enter)
soi.add_module(module.__name__, module())
if module in DATABASE_MODULES:
soi.add_module(module.__name__, module(database=database))
else:
soi.add_module(module.__name__, module())
soi.reorganize() soi.reorganize()
......
...@@ -4,9 +4,11 @@ import unittest ...@@ -4,9 +4,11 @@ import unittest
import json import json
from pathlib import Path from pathlib import Path
from datetime import datetime from datetime import datetime
from test.test_database import TESTDBPATH
from PySide2.QtWidgets import QApplication from PySide2.QtWidgets import QApplication
from soitool.soi import SOI from soitool.soi import SOI
from soitool.modules.module_table import TableModule from soitool.modules.module_table import TableModule
from soitool.database import Database
from soitool.serialize_export_import_soi import ( from soitool.serialize_export_import_soi import (
serialize_soi, serialize_soi,
export_soi, export_soi,
...@@ -116,6 +118,7 @@ class SerializeTest(unittest.TestCase): ...@@ -116,6 +118,7 @@ class SerializeTest(unittest.TestCase):
self.file_path_compressed = os.path.join( self.file_path_compressed = os.path.join(
SOITOOL_ROOT_PATH, file_name_compressed SOITOOL_ROOT_PATH, file_name_compressed
) )
self.database = Database(db_path=TESTDBPATH)
def test_serialize_soi(self): def test_serialize_soi(self):
"""Serialize SOI and check its validity against schema.""" """Serialize SOI and check its validity against schema."""
...@@ -149,7 +152,7 @@ class SerializeTest(unittest.TestCase): ...@@ -149,7 +152,7 @@ class SerializeTest(unittest.TestCase):
# Assert import throws error # Assert import throws error
with self.assertRaises(ValueError) as error: with self.assertRaises(ValueError) as error:
import_soi(invalid_soi_file_path) import_soi(invalid_soi_file_path, self.database)
self.assertEqual( self.assertEqual(
str(error.exception), str(error.exception),
"Serialized SOI does not have correct format.", "Serialized SOI does not have correct format.",
...@@ -160,8 +163,10 @@ class SerializeTest(unittest.TestCase): ...@@ -160,8 +163,10 @@ class SerializeTest(unittest.TestCase):
def test_import_soi(self): def test_import_soi(self):
"""Import serialized SOI's, check content and delete SOI-files.""" """Import serialized SOI's, check content and delete SOI-files."""
# Import uncompressed and compressed SOI's # Import uncompressed and compressed SOI's
soi_uncompressed = import_soi(self.file_path_uncompressed) soi_uncompressed = import_soi(
soi_compressed = import_soi(self.file_path_compressed) self.file_path_uncompressed, self.database
)
soi_compressed = import_soi(self.file_path_compressed, self.database)
# Assert SOI content is correct # Assert SOI content is correct
self.check_soi_content(soi_uncompressed) self.check_soi_content(soi_uncompressed)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment