excel2moodle 0.3.4__py3-none-any.whl → 0.3.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- excel2moodle/__init__.py +16 -88
- excel2moodle/__main__.py +9 -1
- excel2moodle/core/__init__.py +1 -3
- excel2moodle/core/category.py +32 -32
- excel2moodle/core/dataStructure.py +35 -51
- excel2moodle/core/etHelpers.py +12 -17
- excel2moodle/core/exceptions.py +1 -1
- excel2moodle/core/globals.py +2 -1
- excel2moodle/core/numericMultiQ.py +11 -12
- excel2moodle/core/parser.py +121 -102
- excel2moodle/core/question.py +32 -30
- excel2moodle/core/questionValidator.py +28 -19
- excel2moodle/core/questionWriter.py +232 -139
- excel2moodle/core/stringHelpers.py +16 -23
- excel2moodle/extra/equationVerification.py +13 -27
- excel2moodle/logger.py +101 -0
- excel2moodle/ui/appUi.py +97 -102
- excel2moodle/ui/dialogs.py +40 -4
- excel2moodle/ui/settings.py +105 -54
- excel2moodle/ui/treewidget.py +13 -10
- excel2moodle/ui/windowMain.py +18 -57
- {excel2moodle-0.3.4.dist-info → excel2moodle-0.3.6.dist-info}/METADATA +1 -1
- excel2moodle-0.3.6.dist-info/RECORD +33 -0
- {excel2moodle-0.3.4.dist-info → excel2moodle-0.3.6.dist-info}/WHEEL +1 -1
- excel2moodle-0.3.4.dist-info/RECORD +0 -32
- {excel2moodle-0.3.4.dist-info → excel2moodle-0.3.6.dist-info}/entry_points.txt +0 -0
- {excel2moodle-0.3.4.dist-info → excel2moodle-0.3.6.dist-info}/licenses/LICENSE +0 -0
- {excel2moodle-0.3.4.dist-info → excel2moodle-0.3.6.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,4 @@
|
|
1
|
-
"""Script for verifying the equations written into the ``result`` field of NFM-type Question
|
1
|
+
"""Script for verifying the equations written into the ``result`` field of NFM-type Question.
|
2
2
|
|
3
3
|
This script does two things.
|
4
4
|
|
@@ -40,7 +40,7 @@ from excel2moodle.core import numericMultiQ as nmq
|
|
40
40
|
|
41
41
|
|
42
42
|
def checkResult(checkerValue: float, calculation: float, tolerance=0.01) -> bool:
|
43
|
-
"""Checks if the two Arguments are within the tolerance the same value
|
43
|
+
"""Checks if the two Arguments are within the tolerance the same value.
|
44
44
|
|
45
45
|
:param checkerValue: the value the calculation is compared against
|
46
46
|
:type checkerValue: fleat
|
@@ -54,19 +54,15 @@ def checkResult(checkerValue: float, calculation: float, tolerance=0.01) -> bool
|
|
54
54
|
False if checkerValue != calculation
|
55
55
|
:rtype: bool
|
56
56
|
"""
|
57
|
-
|
58
57
|
upper = abs(checkerValue + checkerValue * tolerance)
|
59
58
|
lower = abs(checkerValue - checkerValue * tolerance)
|
60
|
-
|
61
|
-
return True
|
62
|
-
else:
|
63
|
-
return False
|
59
|
+
return bool(abs(calculation) > lower and abs(calculation) < upper)
|
64
60
|
|
65
61
|
|
66
62
|
def equationChecker(
|
67
63
|
categoryName: str, qNumber: int, spreadsheetFile
|
68
64
|
) -> tuple[list[str], list[float], float]:
|
69
|
-
"""This Function calculates all Results an invokes the checkResult function
|
65
|
+
"""This Function calculates all Results an invokes the checkResult function.
|
70
66
|
|
71
67
|
Parameters
|
72
68
|
----------
|
@@ -86,8 +82,8 @@ def equationChecker(
|
|
86
82
|
The list with the calculated results
|
87
83
|
checkerValue : float
|
88
84
|
The value taken from the ``firstResult`` field
|
89
|
-
"""
|
90
85
|
|
86
|
+
"""
|
91
87
|
spreadsheetFile.resolve()
|
92
88
|
df = pd.read_excel(spreadsheetFile, sheet_name=categoryName, index_col=0)
|
93
89
|
eq = df.loc["result"][qNumber]
|
@@ -95,7 +91,6 @@ def equationChecker(
|
|
95
91
|
try:
|
96
92
|
res = float(df.loc["firstResult"][qNumber])
|
97
93
|
except Exception:
|
98
|
-
print(f"Es ist kein 'firstResult' gegeben, kann nichts überprüfen")
|
99
94
|
res = 0
|
100
95
|
bps, calcs = nmq.parseNumericMultiQuestion(df, bps, eq, qNumber)
|
101
96
|
return bps, calcs, res
|
@@ -106,11 +101,11 @@ def main(
|
|
106
101
|
catN=None,
|
107
102
|
qNumber=None,
|
108
103
|
) -> None:
|
109
|
-
"""Takes the Spreadsheet, and asks for a category and a question number"""
|
110
|
-
if catN
|
104
|
+
"""Takes the Spreadsheet, and asks for a category and a question number."""
|
105
|
+
if catN is None:
|
111
106
|
catN = input("Geben Sie die Kategorie an: KAT_")
|
112
107
|
categoryName = f"KAT_{catN}"
|
113
|
-
if qNumber
|
108
|
+
if qNumber is None:
|
114
109
|
qNumber = int(input("Geben Sie die Fragennummer an: "))
|
115
110
|
bullets, results, firstResult = equationChecker(
|
116
111
|
categoryName, qNumber, spreadsheetFile=spreadsheetFile
|
@@ -120,23 +115,14 @@ def main(
|
|
120
115
|
for i, calculation in enumerate(results):
|
121
116
|
if i == 0 and firstResult != 0:
|
122
117
|
check = checkResult(firstResult, calculation)
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
)
|
127
|
-
|
128
|
-
if check == True:
|
129
|
-
print(
|
130
|
-
f"Das erste berechnete Ergebnis stimmt mit dem Wert in 'firstResult' überein\n"
|
131
|
-
)
|
118
|
+
|
119
|
+
if check:
|
120
|
+
pass
|
132
121
|
else:
|
133
|
-
|
134
|
-
f"WARNUNG: Das erste berechnete Ergebnis weicht von dem Wert {
|
135
|
-
firstResult=} ab.\n"
|
136
|
-
)
|
122
|
+
pass
|
137
123
|
|
138
124
|
|
139
125
|
if __name__ == "__main__":
|
140
|
-
spreadsheet = input(
|
126
|
+
spreadsheet = input("Geben Sie den Pfad zu dem spreadsheet an:")
|
141
127
|
while True:
|
142
128
|
main(Path(spreadsheet))
|
excel2moodle/logger.py
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
"""Logging Setup for Excel2moodle.
|
2
|
+
|
3
|
+
Mainly this sets up the general configuration.
|
4
|
+
This includes emitting the signals for the main Window, to fast forward all logs.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import logging
|
8
|
+
|
9
|
+
from PySide6.QtCore import QObject, Signal
|
10
|
+
|
11
|
+
from excel2moodle.ui.settings import Settings, SettingsKey
|
12
|
+
|
13
|
+
settings = Settings()
|
14
|
+
|
15
|
+
loggerConfig = {
|
16
|
+
"version": 1,
|
17
|
+
"disable_existing_loggers": False,
|
18
|
+
"formatters": {
|
19
|
+
"standard": {
|
20
|
+
"format": "[{levelname:^8s}] {module:.^14s}: {message}",
|
21
|
+
"style": "{",
|
22
|
+
},
|
23
|
+
"file": {
|
24
|
+
"format": "%(asctime)s [%(levelname)-5s] %(name)12s: %(message)s",
|
25
|
+
},
|
26
|
+
},
|
27
|
+
"handlers": {
|
28
|
+
"default": {
|
29
|
+
"level": "DEBUG",
|
30
|
+
"formatter": "standard",
|
31
|
+
"class": "logging.StreamHandler",
|
32
|
+
"stream": "ext://sys.stdout", # Default is stderr
|
33
|
+
},
|
34
|
+
"file": {
|
35
|
+
"level": "DEBUG",
|
36
|
+
"formatter": "file",
|
37
|
+
"class": "logging.FileHandler",
|
38
|
+
# "class": "logging.handlers.TimedRotatingFileHandler",
|
39
|
+
"filename": settings.get(SettingsKey.LOGFILE),
|
40
|
+
# "when": "M",
|
41
|
+
# "interval": 1,
|
42
|
+
# "backupCount": "3",
|
43
|
+
"delay": "true",
|
44
|
+
},
|
45
|
+
},
|
46
|
+
"loggers": {
|
47
|
+
"": { # root logger
|
48
|
+
"handlers": ["default", "file"],
|
49
|
+
"level": "DEBUG",
|
50
|
+
"propagate": True,
|
51
|
+
},
|
52
|
+
"excel2moodle.questionParser": {
|
53
|
+
"handlers": ["default"],
|
54
|
+
"level": "DEBUG",
|
55
|
+
"propagate": True,
|
56
|
+
},
|
57
|
+
"__main__": { # if __name__ == '__main__'
|
58
|
+
"handlers": ["default"],
|
59
|
+
"level": "DEBUG",
|
60
|
+
"propagate": True,
|
61
|
+
},
|
62
|
+
},
|
63
|
+
}
|
64
|
+
|
65
|
+
|
66
|
+
class QSignaler(QObject):
|
67
|
+
signal = Signal(str)
|
68
|
+
|
69
|
+
|
70
|
+
class LogWindowHandler(logging.Handler):
|
71
|
+
def __init__(self, *args, **kwargs) -> None:
|
72
|
+
super().__init__(*args, **kwargs)
|
73
|
+
self.emitter = QSignaler()
|
74
|
+
# Define a formatter with log level and module
|
75
|
+
log_format = "[%(levelname)s] %(module)s: %(message)s"
|
76
|
+
self.formatter = logging.Formatter(log_format)
|
77
|
+
self.setFormatter(self.formatter)
|
78
|
+
loglevel = settings.get(SettingsKey.LOGLEVEL)
|
79
|
+
self.setLevel(loglevel)
|
80
|
+
self.logLevelColors = {
|
81
|
+
"DEBUG": "gray",
|
82
|
+
"INFO": "green",
|
83
|
+
"WARNING": "orange",
|
84
|
+
"ERROR": "red",
|
85
|
+
"CRITICAL": "pink",
|
86
|
+
}
|
87
|
+
|
88
|
+
def emit(self, record: logging.LogRecord) -> None:
|
89
|
+
"""Emit the signal, with a new logging message."""
|
90
|
+
log_message = self.format(record)
|
91
|
+
color = self.logLevelColors.get(record.levelname, "black")
|
92
|
+
prettyMessage = f'<span style="color:{color};">{log_message}</span>'
|
93
|
+
self.emitter.signal.emit(prettyMessage)
|
94
|
+
|
95
|
+
|
96
|
+
class LogAdapterQuestionID(logging.LoggerAdapter):
|
97
|
+
"""Prepend the Question ID to the logging messages."""
|
98
|
+
|
99
|
+
def process(self, msg, kwargs):
|
100
|
+
"""Append the Question ID to the log Message."""
|
101
|
+
return "[{}]: {}".format(self.extra["qID"], msg), kwargs
|
excel2moodle/ui/appUi.py
CHANGED
@@ -1,20 +1,20 @@
|
|
1
|
-
"""
|
1
|
+
"""AppUi holds the extended class mainWindow() and any other main Windows.
|
2
2
|
|
3
|
-
It needs to be seperated from ``windowMain.py`` because that file will be changed by
|
4
|
-
which generates the python code from the ``.ui`` file
|
3
|
+
It needs to be seperated from ``windowMain.py`` because that file will be changed by the
|
4
|
+
``pyside6-uic`` command, which generates the python code from the ``.ui`` file
|
5
5
|
"""
|
6
6
|
|
7
|
-
import logging
|
7
|
+
import logging
|
8
8
|
from pathlib import Path
|
9
9
|
|
10
10
|
from PySide6 import QtCore, QtWidgets
|
11
|
-
from PySide6.QtCore import Qt
|
11
|
+
from PySide6.QtCore import QRunnable, Qt, QThreadPool
|
12
12
|
|
13
|
-
from excel2moodle import
|
13
|
+
from excel2moodle import qSignalLogger
|
14
14
|
from excel2moodle.core.dataStructure import QuestionDB
|
15
15
|
from excel2moodle.extra import equationVerification as eqVerif
|
16
|
-
from excel2moodle.ui import dialogs
|
17
|
-
from excel2moodle.ui.settings import Settings
|
16
|
+
from excel2moodle.ui import dialogs
|
17
|
+
from excel2moodle.ui.settings import Settings, SettingsKey
|
18
18
|
from excel2moodle.ui.treewidget import CategoryItem, QuestionItem
|
19
19
|
from excel2moodle.ui.windowMain import Ui_MoodleTestGenerator
|
20
20
|
|
@@ -33,104 +33,92 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
33
33
|
self.testDB = testDB
|
34
34
|
self.ui = Ui_MoodleTestGenerator()
|
35
35
|
self.ui.setupUi(self)
|
36
|
-
|
36
|
+
self.connectEvents()
|
37
|
+
logger.info("Settings are stored under: %s", self.settings.fileName())
|
37
38
|
self.ui.treeWidget.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
|
38
39
|
self.ui.treeWidget.header().setSectionResizeMode(
|
39
|
-
QtWidgets.QHeaderView.ResizeToContents
|
40
|
+
QtWidgets.QHeaderView.ResizeToContents,
|
40
41
|
)
|
41
42
|
self.ui.checkBoxIncludeCategories.setChecked(
|
42
|
-
self.settings.
|
43
|
+
self.settings.get(SettingsKey.INCLUDEINCATS),
|
44
|
+
)
|
45
|
+
self.ui.spinBoxDefaultQVariant.setValue(
|
46
|
+
self.settings.get(SettingsKey.QUESTIONVARIANT)
|
43
47
|
)
|
44
|
-
|
45
|
-
self.ui.retranslateUi(self)
|
46
|
-
logger.info(f"Settings are stored under: {self.settings.fileName()}")
|
47
48
|
self.ui.pointCounter.setReadOnly(True)
|
48
49
|
self.ui.questionCounter.setReadOnly(True)
|
49
50
|
self.setStatus(
|
50
|
-
"Wählen Sie
|
51
|
+
"Wählen Sie eine Excel Tabelle mit den Fragen aus",
|
51
52
|
)
|
52
53
|
try:
|
53
54
|
self.resize(self.settings.value("windowSize"))
|
54
55
|
self.move(self.settings.value("windowPosition"))
|
55
56
|
except Exception:
|
56
57
|
pass
|
57
|
-
self.
|
58
|
+
self.threadPool = QThreadPool()
|
58
59
|
|
59
60
|
def connectEvents(self) -> None:
|
60
61
|
self.ui.treeWidget.itemClicked.connect(self.onSelectionChanged)
|
61
62
|
self.ui.checkBoxQuestionListSelectAll.checkStateChanged.connect(
|
62
|
-
self.toggleQuestionSelectionState
|
63
|
+
self.toggleQuestionSelectionState,
|
63
64
|
)
|
64
65
|
qSignalLogger.emitter.signal.connect(self.updateLog)
|
65
|
-
self.ui.actionEquationChecker.triggered.connect(self.
|
66
|
+
self.ui.actionEquationChecker.triggered.connect(self.openEqCheckerDlg)
|
66
67
|
self.ui.checkBoxIncludeCategories.checkStateChanged.connect(
|
67
|
-
self.setIncludeCategoriesSetting
|
68
|
+
self.setIncludeCategoriesSetting,
|
68
69
|
)
|
69
|
-
# self.ui.buttonRefresh.clicked.connect(self.refreshList)
|
70
70
|
self.ui.actionParseAll.triggered.connect(self.onParseAll)
|
71
|
-
self.testDB.dataChanged.signal.connect(self.
|
71
|
+
# self.testDB.dataChanged.signal.connect(self.refreshList)
|
72
72
|
self.ui.buttonSpreadSheet.clicked.connect(self.onButSpreadsheet)
|
73
73
|
self.ui.buttonTestGen.clicked.connect(self.onButGenTest)
|
74
|
-
self.ui.actionPreviewQ.triggered.connect(self.
|
75
|
-
self.ui.actionAbout.triggered.connect(self.
|
74
|
+
self.ui.actionPreviewQ.triggered.connect(self.openPreviewQuestionDlg)
|
75
|
+
self.ui.actionAbout.triggered.connect(self.openAboutDlg)
|
76
76
|
self.settings.shPathChanged.connect(self.onSheetPathChanged)
|
77
|
+
self.ui.spinBoxDefaultQVariant.valueChanged.connect(self.setQVariantDefault)
|
78
|
+
|
79
|
+
@QtCore.Slot()
|
80
|
+
def setQVariantDefault(self, value: int) -> None:
|
81
|
+
self.settings.set(SettingsKey.QUESTIONVARIANT, value)
|
82
|
+
|
83
|
+
@QtCore.Slot()
|
84
|
+
def onParseAll(self) -> None:
|
85
|
+
"""Event triggered by the *Tools/Parse all Questions* Event.
|
86
|
+
|
87
|
+
It parses all the Questions found in the spreadsheet
|
88
|
+
and then refreshes the list of questions.
|
89
|
+
If successful it prints out a list of all exported Questions
|
90
|
+
"""
|
91
|
+
self.ui.treeWidget.clear()
|
92
|
+
process = ParseSpreadsheetThread(self.testDB, self)
|
93
|
+
self.threadPool.start(process)
|
77
94
|
|
78
95
|
@QtCore.Slot(Path)
|
79
96
|
def onSheetPathChanged(self, sheet: Path) -> None:
|
80
97
|
logger.debug("Slot, new Spreadsheet triggered")
|
81
98
|
self.spreadSheetPath = sheet
|
82
99
|
self.mainPath = sheet.parent
|
100
|
+
svgFolder = self.mainPath / self.settings.get(SettingsKey.PICTURESUBFOLDER)
|
101
|
+
svgFolder.resolve()
|
102
|
+
self.settings.set(SettingsKey.PICTUREFOLDER, svgFolder)
|
83
103
|
self.ui.buttonSpreadSheet.setText(str(sheet.name))
|
104
|
+
self.onParseAll()
|
84
105
|
|
85
106
|
def updateLog(self, log) -> None:
|
86
107
|
self.ui.loggerWindow.append(log)
|
87
108
|
|
88
|
-
def setIncludeCategoriesSetting(self):
|
109
|
+
def setIncludeCategoriesSetting(self) -> None:
|
89
110
|
if self.ui.checkBoxIncludeCategories.isChecked():
|
90
|
-
self.settings.
|
91
|
-
logger.debug("set includeCats to True")
|
111
|
+
self.settings.set(SettingsKey.INCLUDEINCATS, True)
|
92
112
|
else:
|
93
|
-
self.settings.
|
94
|
-
logger.debug("set includeCats to False")
|
113
|
+
self.settings.set(SettingsKey.INCLUDEINCATS, False)
|
95
114
|
|
96
|
-
|
97
|
-
def onAbout(self):
|
98
|
-
aboutMessage: str = f"""
|
99
|
-
<h1> About {e2mMetadata['name']}</h1><br>
|
100
|
-
<p style="text-align:center">
|
101
|
-
|
102
|
-
<b><a href="{e2mMetadata['homepage']}">{e2mMetadata['name']}</a> - {e2mMetadata['description']}</b>
|
103
|
-
</p>
|
104
|
-
<p style="text-align:center">
|
105
|
-
The documentation can be found under <b>
|
106
|
-
<a href="{e2mMetadata['documentation']}">{e2mMetadata['documentation']}</a></b>
|
107
|
-
</br>
|
108
|
-
</p>
|
109
|
-
<p style="text-align:center">
|
110
|
-
This project is maintained by {e2mMetadata['author']}.
|
111
|
-
<br>
|
112
|
-
Development takes place at <a href="{e2mMetadata['homepage']}"> GitLab: {e2mMetadata['homepage']}</a>
|
113
|
-
contributions are very welcome
|
114
|
-
</br>
|
115
|
-
If you encounter any issues please report them under the repositories issues page.
|
116
|
-
</br>
|
117
|
-
</p>
|
118
|
-
<p style="text-align:center">
|
119
|
-
<i>This project is published under {e2mMetadata['license']}, you are welcome, to share, modify and reuse the code.</i>
|
120
|
-
</p>
|
121
|
-
"""
|
122
|
-
QtWidgets.QMessageBox.information(
|
123
|
-
self, f"About {e2mMetadata['name']}", aboutMessage
|
124
|
-
)
|
125
|
-
|
126
|
-
def closeEvent(self, event):
|
115
|
+
def closeEvent(self, event) -> None:
|
127
116
|
self.settings.setValue("windowSize", self.size())
|
128
117
|
self.settings.setValue("windowPosition", self.pos())
|
129
118
|
|
130
119
|
@QtCore.Slot()
|
131
|
-
def onSelectionChanged(self,
|
132
|
-
"""Whenever the selection changes the total of selected points needs to be recalculated"""
|
133
|
-
|
120
|
+
def onSelectionChanged(self, **args) -> None:
|
121
|
+
"""Whenever the selection changes the total of selected points needs to be recalculated."""
|
134
122
|
count: int = 0
|
135
123
|
questions: int = 0
|
136
124
|
selection = self.ui.treeWidget.selectedItems()
|
@@ -138,17 +126,13 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
138
126
|
questions += 1
|
139
127
|
count += q.getQuestion().points
|
140
128
|
|
141
|
-
logger.info(
|
129
|
+
logger.info("%s questions are selected with %s points", questions, count)
|
142
130
|
self.ui.pointCounter.setValue(count)
|
143
131
|
self.ui.questionCounter.setValue(questions)
|
144
|
-
return None
|
145
132
|
|
146
133
|
@QtCore.Slot()
|
147
|
-
def toggleQuestionSelectionState(self, state):
|
148
|
-
|
149
|
-
setter = True
|
150
|
-
else:
|
151
|
-
setter = False
|
134
|
+
def toggleQuestionSelectionState(self, state) -> None:
|
135
|
+
setter = state == Qt.Checked
|
152
136
|
root = self.ui.treeWidget.invisibleRootItem()
|
153
137
|
childN = root.childCount()
|
154
138
|
for i in range(childN):
|
@@ -158,21 +142,20 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
158
142
|
|
159
143
|
@QtCore.Slot()
|
160
144
|
def onButGenTest(self) -> None:
|
145
|
+
"""Open a file Dialog so the export file may be choosen."""
|
161
146
|
path = QtWidgets.QFileDialog.getSaveFileName(
|
162
147
|
self,
|
163
148
|
"Select Output File",
|
164
|
-
dir=f"{
|
165
|
-
self.mainPath/"Testfile.xml"}",
|
149
|
+
dir=f"{self.mainPath / 'Testfile.xml'}",
|
166
150
|
filter="xml Files (*.xml)",
|
167
151
|
)
|
168
152
|
self.exportFile = Path(path[0])
|
169
|
-
logger.info(
|
153
|
+
logger.info("New Export File is set %s", self.exportFile)
|
170
154
|
selection: list[QuestionItem] = self.ui.treeWidget.selectedItems()
|
171
155
|
self.testDB.appendQuestions(selection, self.exportFile)
|
172
|
-
return None
|
173
156
|
|
174
157
|
@QtCore.Slot()
|
175
|
-
def onButSpreadsheet(self):
|
158
|
+
def onButSpreadsheet(self) -> None:
|
176
159
|
file = QtWidgets.QFileDialog.getOpenFileName(
|
177
160
|
self,
|
178
161
|
self.tr("Open Spreadsheet"),
|
@@ -182,21 +165,13 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
182
165
|
)
|
183
166
|
self.excelPath = Path(file[0]).resolve()
|
184
167
|
self.settings.setSpreadsheet(self.excelPath)
|
185
|
-
logger.debug(f"Saved Spreadsheet Path: {self.excelPath}\n")
|
186
168
|
self.setStatus("[OK] Excel Tabelle wurde eingelesen")
|
187
|
-
return None
|
188
169
|
|
189
|
-
|
190
|
-
|
191
|
-
"""Event triggered by the *Tools/Parse all Questions* Event
|
170
|
+
def refreshList(self) -> None:
|
171
|
+
"""Refresh the question overview in the main window.
|
192
172
|
|
193
|
-
|
194
|
-
If successful it prints out a list of all exported Questions
|
173
|
+
Enable the export Button afterwards.
|
195
174
|
"""
|
196
|
-
self.ui.buttonTestGen.setEnabled(True)
|
197
|
-
self.testDB.parseAll()
|
198
|
-
self.setStatus("[OK] Alle Fragen wurden erfolgreich in XML-Dateien umgewandelt")
|
199
|
-
# below is former refres method
|
200
175
|
logger.info("starting List refresh")
|
201
176
|
cats = self.testDB.categories
|
202
177
|
self.ui.treeWidget.clear()
|
@@ -206,49 +181,69 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
206
181
|
for q in cat.questions.values():
|
207
182
|
QuestionItem(catItem, q)
|
208
183
|
self.setStatus("[OK] Fragen Liste wurde aktualisiert")
|
209
|
-
|
184
|
+
self.ui.buttonTestGen.setEnabled(True)
|
210
185
|
|
211
186
|
@QtCore.Slot()
|
212
|
-
def
|
187
|
+
def openPreviewQuestionDlg(self) -> None:
|
213
188
|
item = self.ui.treeWidget.currentItem()
|
214
189
|
if isinstance(item, QuestionItem):
|
215
190
|
dialog = dialogs.QuestinoPreviewDialog(self, item.getQuestion())
|
216
191
|
dialog.show()
|
217
192
|
else:
|
218
|
-
logger.info(
|
193
|
+
logger.info("current Item is not a Question, can't preview")
|
219
194
|
|
220
|
-
def setStatus(self, status):
|
195
|
+
def setStatus(self, status) -> None:
|
221
196
|
self.ui.statusbar.clearMessage()
|
222
197
|
self.ui.statusbar.showMessage(self.tr(status))
|
223
198
|
|
224
199
|
@QtCore.Slot()
|
225
|
-
def
|
226
|
-
logger.debug(
|
200
|
+
def openEqCheckerDlg(self) -> None:
|
201
|
+
logger.debug("opening wEquationChecker \n")
|
227
202
|
self.uiEqChecker = EqCheckerWindow()
|
228
203
|
self.uiEqChecker.excelFile = self.excelPath
|
229
204
|
self.uiEqChecker.show()
|
230
205
|
|
206
|
+
@QtCore.Slot()
|
207
|
+
def openAboutDlg(self) -> None:
|
208
|
+
about = dialogs.AboutDialog(self)
|
209
|
+
about.exec()
|
210
|
+
|
211
|
+
|
212
|
+
class ParseSpreadsheetThread(QRunnable):
|
213
|
+
def __init__(self, questionDB: QuestionDB, mainApp: MainWindow) -> None:
|
214
|
+
super().__init__()
|
215
|
+
self.testDB = questionDB
|
216
|
+
self.mainApp = mainApp
|
217
|
+
|
218
|
+
@QtCore.Slot()
|
219
|
+
def run(self) -> None:
|
220
|
+
self.testDB.readSpreadsheetData(self.mainApp.spreadSheetPath)
|
221
|
+
self.mainApp.setStatus("[OK] Tabellen wurde eingelesen")
|
222
|
+
self.testDB.parseAll()
|
223
|
+
self.mainApp.setStatus("[OK] Alle Fragen wurden erfolgreich geparsed")
|
224
|
+
self.mainApp.refreshList()
|
225
|
+
|
231
226
|
|
232
227
|
class EqCheckerWindow(QtWidgets.QWidget):
|
233
|
-
def __init__(self):
|
228
|
+
def __init__(self) -> None:
|
234
229
|
super().__init__()
|
235
230
|
self.excelFile = Path()
|
236
231
|
self.ui = Ui_EquationChecker()
|
237
232
|
self.ui.setupUi(self)
|
238
233
|
self.ui.buttonRunCheck.clicked.connect(
|
239
234
|
lambda: self.onButRunCheck(
|
240
|
-
self.ui.catNumber.value(),
|
241
|
-
|
235
|
+
self.ui.catNumber.value(),
|
236
|
+
self.ui.qNumber.value(),
|
237
|
+
),
|
242
238
|
)
|
243
239
|
|
244
240
|
def onButRunCheck(self, catN: int, qN: int) -> None:
|
245
|
-
"""
|
246
|
-
Is Triggered by the ``Run Check now`` Button and runs the Equation Check
|
247
|
-
"""
|
248
|
-
|
241
|
+
"""Is Triggered by the ``Run Check now`` Button and runs the Equation Check."""
|
249
242
|
self.ui.textResultsOutput.clear()
|
250
243
|
bullets, results, firstResult = eqVerif.equationChecker(
|
251
|
-
f"KAT_{catN}",
|
244
|
+
f"KAT_{catN}",
|
245
|
+
qN,
|
246
|
+
self.excelFile,
|
252
247
|
)
|
253
248
|
check = False
|
254
249
|
self.ui.lineFirstResult.setText(f"{firstResult}")
|
@@ -257,13 +252,13 @@ class EqCheckerWindow(QtWidgets.QWidget):
|
|
257
252
|
check = eqVerif.checkResult(firstResult, calculation)
|
258
253
|
self.ui.lineCalculatedRes.setText(f"{calculation}")
|
259
254
|
self.ui.textResultsOutput.append(
|
260
|
-
f"Ergebnis {i+1}: \t{calculation}\n\tMit den Werten: \n{bullets[i]}\n"
|
255
|
+
f"Ergebnis {i + 1}: \t{calculation}\n\tMit den Werten: \n{bullets[i]}\n",
|
261
256
|
)
|
262
257
|
|
263
|
-
if check
|
258
|
+
if check:
|
264
259
|
self.ui.lineCheckResult.setText("[OK]")
|
265
260
|
logger.info(
|
266
|
-
|
261
|
+
"Das erste berechnete Ergebnis stimmt mit dem Wert in 'firstResult' überein\n",
|
267
262
|
)
|
268
263
|
else:
|
269
264
|
self.ui.lineCheckResult.setText("[ERROR]")
|
excel2moodle/ui/dialogs.py
CHANGED
@@ -1,9 +1,10 @@
|
|
1
|
-
"""This Module hosts the various Dialog Classes, that can be shown from main Window"""
|
1
|
+
"""This Module hosts the various Dialog Classes, that can be shown from main Window."""
|
2
2
|
|
3
3
|
import lxml.etree as ET
|
4
4
|
from PySide6 import QtGui, QtWidgets
|
5
5
|
from PySide6.QtSvgWidgets import QGraphicsSvgItem
|
6
6
|
|
7
|
+
from excel2moodle import e2mMetadata
|
7
8
|
from excel2moodle.core.globals import XMLTags
|
8
9
|
from excel2moodle.core.question import Question
|
9
10
|
from excel2moodle.ui.questionPreviewDialog import Ui_QuestionPrevDialog
|
@@ -11,7 +12,7 @@ from excel2moodle.ui.variantDialog import Ui_Dialog
|
|
11
12
|
|
12
13
|
|
13
14
|
class QuestionVariantDialog(QtWidgets.QDialog):
|
14
|
-
def __init__(self, parent, question: Question):
|
15
|
+
def __init__(self, parent, question: Question) -> None:
|
15
16
|
super().__init__(parent)
|
16
17
|
self.setWindowTitle("Question Variant Dialog")
|
17
18
|
self.maxVal = question.variants
|
@@ -37,6 +38,7 @@ class QuestinoPreviewDialog(QtWidgets.QDialog):
|
|
37
38
|
self.question = question
|
38
39
|
self.ui = Ui_QuestionPrevDialog()
|
39
40
|
self.ui.setupUi(self)
|
41
|
+
self.setModal(True)
|
40
42
|
self.setWindowTitle(f"Question - {question.id} - Preview")
|
41
43
|
self.setupQuestion()
|
42
44
|
|
@@ -81,13 +83,13 @@ class QuestinoPreviewDialog(QtWidgets.QDialog):
|
|
81
83
|
for i, ans in enumerate(self.question.answerVariants):
|
82
84
|
a = ans.find("text").text
|
83
85
|
text = QtWidgets.QLineEdit(a, self)
|
84
|
-
self.ui.answersFormLayout.addRow(f"Answer {i+1}", text)
|
86
|
+
self.ui.answersFormLayout.addRow(f"Answer {i + 1}", text)
|
85
87
|
|
86
88
|
elif self.question.qtype == "NF":
|
87
89
|
ans = self.question.element.find(XMLTags.ANSWER)
|
88
90
|
a = ans.find("text").text
|
89
91
|
text = QtWidgets.QLineEdit(a, self)
|
90
|
-
self.ui.answersFormLayout.addRow(
|
92
|
+
self.ui.answersFormLayout.addRow("Result", text)
|
91
93
|
|
92
94
|
elif self.question.qtype == "MC":
|
93
95
|
for i, ans in enumerate(self.question.element.findall(XMLTags.ANSWER)):
|
@@ -95,3 +97,37 @@ class QuestinoPreviewDialog(QtWidgets.QDialog):
|
|
95
97
|
frac = ans.get("fraction")
|
96
98
|
text = QtWidgets.QLineEdit(a, self)
|
97
99
|
self.ui.answersFormLayout.addRow(f"Fraction: {frac}", text)
|
100
|
+
|
101
|
+
|
102
|
+
class AboutDialog(QtWidgets.QMessageBox):
|
103
|
+
def __init__(self, parent: QtWidgets.QWidget) -> None:
|
104
|
+
super().__init__(parent)
|
105
|
+
self.setWindowTitle(f"About {e2mMetadata['name']}")
|
106
|
+
self.setIcon(QtWidgets.QMessageBox.Information)
|
107
|
+
self.setStandardButtons(QtWidgets.QMessageBox.StandardButton.Close)
|
108
|
+
|
109
|
+
self.aboutMessage: str = f"""
|
110
|
+
<h1> About {e2mMetadata["name"]}</h1><br>
|
111
|
+
<p style="text-align:center">
|
112
|
+
|
113
|
+
<b><a href="{e2mMetadata["homepage"]}">{e2mMetadata["name"]}</a> - {e2mMetadata["description"]}</b>
|
114
|
+
</p>
|
115
|
+
<p style="text-align:center">
|
116
|
+
The documentation can be found under <b>
|
117
|
+
<a href="{e2mMetadata["documentation"]}">{e2mMetadata["documentation"]}</a></b>
|
118
|
+
</br>
|
119
|
+
</p>
|
120
|
+
<p style="text-align:center">
|
121
|
+
This project is maintained by {e2mMetadata["author"]}.
|
122
|
+
<br>
|
123
|
+
Development takes place at <a href="{e2mMetadata["homepage"]}"> GitLab: {e2mMetadata["homepage"]}</a>
|
124
|
+
contributions are very welcome
|
125
|
+
</br>
|
126
|
+
If you encounter any issues please report them under the repositories issues page.
|
127
|
+
</br>
|
128
|
+
</p>
|
129
|
+
<p style="text-align:center">
|
130
|
+
<i>This project is published under {e2mMetadata["license"]}, you are welcome, to share, modify and reuse the code.</i>
|
131
|
+
</p>
|
132
|
+
"""
|
133
|
+
self.setText(self.aboutMessage)
|