Skip to content
Snippets Groups Projects
Commit 465021dd authored by Anders H. Rebner's avatar Anders H. Rebner
Browse files

#40 SOI-PK er Title og Version, SOI-lagring og test oppdatert

parent 6b8924e0
No related branches found
No related tags found
1 merge request!59#40 Lagre SOI i database
Pipeline #80260 passed
......@@ -7,7 +7,6 @@ import soitool.coder
from soitool.enumerates import CodebookSort
from soitool.serialize_export_import_soi import serialize_soi
from soitool.compressor import compress
from soitool.dialog_wrappers import exec_warning_dialog
# Set name and path to default (future) database
DBNAME = "database"
......@@ -20,9 +19,11 @@ SECONDS_IN_24H = 24 * 60 * 60
# DDL-statements for creating tables
SOI = (
"CREATE TABLE SOI("
"Title VARCHAR PRIMARY KEY NOT NULL CHECK(length(Title) > 0), "
"Title VARCHAR NOT NULL CHECK(length(Title) > 0), "
"Version VARCHAR NOT NULL CHECK(length(Version) > 0), "
"SOI TEXT NOT NULL CHECK(length(SOI) > 0), "
"Date DATETIME)"
"Date DATETIME, "
"PRIMARY KEY(Title, Version))"
)
CODEBOOK = (
"CREATE TABLE Codebook("
......@@ -347,44 +348,35 @@ class Database:
self.conn.execute(stmt, (code, word))
self.conn.commit()
def insert_soi(self, soi):
"""Serialize, compress and insert SOI into database-table SOI.
def insert_or_update_soi(self, soi):
"""Serialize, compress and insert/update SOI in database-table SOI.
If one or more SOI's with the same title exist in db, '(1)', '(2)', ...
is added to the Title-column of the SOI to insert.
SOI's with the same title and version are overwritten.
Parameters
----------
soi : soitool.soi.SOI
The SOI-instance to insert into database.
The SOI-instance to insert or update.
"""
# Serialize and compress SOI
compressed_soi = compress(serialize_soi(soi))
# Count SOI's in database with equal title,
# including titles ending with '(x)'
title = soi.title
length = len(title)
stmt = (
"SELECT COUNT(*) FROM SOI "
"WHERE substr(Title,0,?)=? "
"AND (length(Title)=? OR length(Title)=?)"
)
count = self.conn.execute(
stmt, (length + 1, title, length, length + 3)
).fetchone()[0]
# Add '(x)' to title if one or more SOI's with equal title exist
if count > 0:
title += "(" + str(count + 1) + ")"
# Insert SOI into database
stmt = "INSERT INTO SOI (Title, SOI, Date) VALUES(?,?,?)"
try:
self.conn.execute(stmt, (title, compressed_soi, soi.date))
self.conn.commit()
except sqlite3.IntegrityError:
exec_warning_dialog(
"SOI ble ikke lagret i databasen.",
"Sørg for at SOI'en har en tittel.",
# Check if SOI with the same title and version exists
stmt = "SELECT COUNT(*) FROM SOI " "WHERE Title=? " "AND Version=?"
count = self.conn.execute(stmt, (soi.title, soi.version)).fetchone()[0]
# If SOI exists, overwrite
if count == 1:
stmt = "UPDATE SOI SET SOI=?, Date=? WHERE Title=? AND Version=?"
self.conn.execute(
stmt, (compressed_soi, soi.date, soi.title, soi.version)
)
# SOI does not exist, insert
else:
stmt = (
"INSERT INTO SOI (Title, Version, SOI, Date) VALUES(?,?,?,?)"
)
self.conn.execute(
stmt, (soi.title, soi.version, compressed_soi, soi.date)
)
self.conn.commit()
......@@ -22,8 +22,6 @@ from soitool.codebook_widget import CodebookWidget
from soitool.codebook_model_view import CodebookTableModel
from soitool.database import Database, DBPATH
from soitool.help_actions import ShortcutsHelpDialog, BasicUsageHelpDialog
from soitool.soi_db_widget import SOIDbWidget
from soitool.soi_model_view import SOITableModel
from soitool.serialize_export_import_soi import (
export_soi,
import_soi,
......@@ -111,7 +109,6 @@ class MainWindow(QMainWindow):
open_file_db = QAction("Åpne fra DB", self)
open_file_db.setShortcut("Ctrl+d")
open_file_db.setStatusTip("Åpne en SOI fra databasen")
open_file_db.triggered.connect(self.show_soi_db)
file_menu.addAction(open_file_db)
# Preview SOI
......@@ -310,24 +307,7 @@ class MainWindow(QMainWindow):
# If tab contains an SOI
if isinstance(tab_widget, SOIWorkspaceWidget):
# Update tab showing SOI's in db if it is open,
# and pause database-lock by codebook-tab if it is open.
soi_db_view = None
codebook_db_view = None
for i in range(self.tabs.count()):
if self.tabs.tabText(i) == "SOI'er i DB":
soi_db_view = self.tabs.widget(i).view
soi_db_view.setModel(None)
elif self.tabs.tabText(i) == "Kodebok":
codebook_db_view = self.tabs.widget(i).view
codebook_db_view.setModel(None)
self.database.insert_soi(tab_widget.soi)
if soi_db_view is not None:
soi_db_view.setModel(SOITableModel())
if codebook_db_view is not None:
codebook_db_view.setModel(CodebookTableModel())
self.database.insert_or_update_soi(tab_widget.soi)
else:
exec_info_dialog(
"Valgt tab er ingen SOI-tab",
......@@ -335,23 +315,6 @@ class MainWindow(QMainWindow):
"Riktig tab må velges for å lagre en SOI i DB.",
)
def show_soi_db(self):
"""Open and select tab containing SOIDbWidget.
Select tab if it is already open,
create and select tab if not open.
"""
# Loop through tabs to look for existing SOI-db-tab:
for i in range(self.tabs.count()):
if self.tabs.tabText(i) == "SOI'er i DB":
self.tabs.setCurrentIndex(i)
break
# SOI-db-tab does not exist, create, add and select tab
else:
tab = SOIDbWidget(self.database, self.tabs)
self.tabs.addTab(tab, "SOI'er i DB")
self.tabs.setCurrentWidget(tab)
def open_shortcut_help(self):
"""Open shortcut dialog."""
self.popup_shortcut_help.setWindowTitle("Hurtigtaster")
......
"""Module containing a widget for viewing and opening SOI's from database."""
from PySide2.QtWidgets import QWidget, QHBoxLayout
from soitool.soi_model_view import SOITableView
class SOIDbWidget(QWidget):
"""Widget for viewing and opening SOI's from database."""
def __init__(self, database, tab_widget):
super().__init__()
self.view = SOITableView(database, tab_widget)
self.create_and_set_layout()
def create_and_set_layout(self):
"""Create layout, add widget and set layout."""
hbox = QHBoxLayout()
hbox.addWidget(self.view)
self.setLayout(hbox)
"""GUI-interface towards database-table 'SOI'.
Contains functionality for showing and opening SOI's in database.
"""
from PySide2.QtWidgets import QTableView
from PySide2.QtSql import QSqlDatabase, QSqlTableModel
from PySide2.QtCore import Qt
from soitool.style import CODEBOOK_HEADER_FONT, CODEBOOK_HEADER_BACKGROUND_CSS
from soitool.serialize_export_import_soi import construct_soi_from_serialized
from soitool.soi_workspace_widget import SOIWorkspaceWidget
# Name and type of database
CONNAME = "SOIDB"
DBTYPE = "QSQLITE"
class SOITableView(QTableView):
"""TableView with a model of the 'SOI'-table from database.
This modified QTableView creates a SOITableModel, which reads data from the
SOI-table. When the user double-clicks or presses the enter-key, a tab
containing SOIWorkspaceWidget, with the SOI from the current row, is opened
and selected.
Parameters
----------
database : soitool.database.Database
Is used to create a QSqlDatabase from the database-file.
tab_widget : QTabWidget
Is used to open a new tab.
Raises
------
RuntimeError
If database does not open.
"""
def __init__(self, database, tab_widget):
super().__init__()
db = QSqlDatabase.addDatabase(DBTYPE, CONNAME)
db.setDatabaseName(database.db_path)
self.tab_widget = tab_widget
if not db.open():
raise RuntimeError("Could not open database.")
# Enable sorting
self.setSortingEnabled(True)
# Create and set model
model = SOITableModel()
self.setModel(model)
# Remove horizontal scrollbar, hide vertical header and 'SOI'-column
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.verticalHeader().hide()
self.hideColumn(1)
# Set horizontal header-text and it's style
self.set_horizontal_header_text()
header = self.horizontalHeader()
header.setFont(CODEBOOK_HEADER_FONT)
header.setStyleSheet(CODEBOOK_HEADER_BACKGROUND_CSS)
# Resize
self.resizeColumnsToContents()
width = self.columnWidth(0) + self.columnWidth(2) + 2 # +2 offset
self.setFixedWidth(width)
self.doubleClicked.connect(self.open_soi_tab)
def set_horizontal_header_text(self):
"""Set Norwegian names in horizontal header."""
self.model().setHeaderData(0, Qt.Horizontal, "Tittel")
self.model().setHeaderData(2, Qt.Horizontal, "Dato")
def keyPressEvent(self, event):
"""Open SOI-tab if enter-key is pressed."""
if event.key() == Qt.Key_Return:
self.open_soi_tab()
super().keyPressEvent(event)
def open_soi_tab(self):
"""Construct SOI and open SOIWorkspacewidget in new tab."""
# Get index of the current row and read compressed, serialized SOI
row = self.currentIndex().row()
compressed_soi = self.model().index(row, 1).data()
# Construct SOI and create SOIWorkspaceWidget
soi = construct_soi_from_serialized(compressed_soi, compressed=True)
tab = SOIWorkspaceWidget(soi)
# Add and select tab
self.tab_widget.addTab(tab, soi.title)
self.tab_widget.setCurrentWidget(tab)
def setModel(self, model):
"""Set model, resize and hide 'SOI'-column.
Parameters
----------
model : SOITableModel or None
Model containing data to display.
"""
super().setModel(model)
if model is not None:
self.hideColumn(1)
self.resizeColumnsToContents()
width = self.columnWidth(0) + self.columnWidth(2) + 2 # +2 offset
self.setFixedWidth(width)
class SOITableModel(QSqlTableModel):
"""Uneditable QSqlTableModel of database-table 'SOI'."""
def __init__(self):
super().__init__(None, QSqlDatabase.database(CONNAME))
self.setEditStrategy(QSqlTableModel.OnFieldChange)
self.setTable("SOI")
self.setSort(2, Qt.DescendingOrder) # Sort by date
self.select()
def flags(self, index):
"""Disable editing.
Parameters
----------
index : QModelIndex
Is used to locate data in a model.
"""
flags = super().flags(index)
flags ^= Qt.ItemIsEditable
return flags
......@@ -302,7 +302,7 @@ class DatabaseTest(unittest.TestCase):
test_date = datetime.now().strftime("%Y-%m-%d")
# Create and insert SOI
soi = SOI(title=test_title, date=test_date)
self.database.insert_soi(soi)
self.database.insert_or_update_soi(soi)
# Assert only one SOI is in table
stmt = "SELECT * FROM SOI"
......@@ -311,16 +311,22 @@ class DatabaseTest(unittest.TestCase):
# Assert SOI was written correctly
self.assertEqual(queried[0]["Title"], test_title)
self.assertEqual(queried[0]["Version"], soi.version)
self.assertEqual(queried[0]["Date"], test_date)
self.assertEqual(queried[0]["SOI"], compress(serialize_soi(soi)))
# Insert same SOI again and assert '(2)' is added to title
self.database.insert_soi(soi)
stmt = "SELECT COUNT(*) FROM SOI WHERE Title=?"
count = self.database.conn.execute(
stmt, (test_title + "(2)",)
).fetchone()[0]
self.assertEqual(count, 1)
# Insert the same SOI (same title & version), assert it is overwritten.
self.database.insert_or_update_soi(soi)
# Assert only one SOI is in table
queried = self.database.conn.execute(stmt).fetchall()
self.assertEqual(len(queried), 1)
# Insert another version of the same SOI, assert a new row is added.
soi.version = "2"
self.database.insert_or_update_soi(soi)
# Assert two SOI's are in table
queried = self.database.conn.execute(stmt).fetchall()
self.assertEqual(len(queried), 2)
def delete_db(self):
"""Delete generated database-file."""
......
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