excel2moodle 0.3.5__py3-none-any.whl → 0.3.7__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 +3 -9
- excel2moodle/__main__.py +8 -1
- excel2moodle/core/category.py +6 -44
- excel2moodle/core/dataStructure.py +242 -79
- excel2moodle/core/globals.py +0 -20
- excel2moodle/core/parser.py +15 -171
- excel2moodle/core/question.py +30 -13
- excel2moodle/core/stringHelpers.py +8 -32
- excel2moodle/core/{questionValidator.py → validator.py} +26 -31
- excel2moodle/logger.py +0 -1
- excel2moodle/question_types/__init__.py +33 -0
- excel2moodle/question_types/mc.py +93 -0
- excel2moodle/question_types/nf.py +30 -0
- excel2moodle/question_types/nfm.py +92 -0
- excel2moodle/ui/appUi.py +66 -51
- excel2moodle/ui/dialogs.py +8 -7
- excel2moodle/ui/settings.py +100 -36
- {excel2moodle-0.3.5.dist-info → excel2moodle-0.3.7.dist-info}/METADATA +2 -2
- excel2moodle-0.3.7.dist-info/RECORD +37 -0
- {excel2moodle-0.3.5.dist-info → excel2moodle-0.3.7.dist-info}/WHEEL +1 -1
- excel2moodle-0.3.5.dist-info/RECORD +0 -33
- {excel2moodle-0.3.5.dist-info → excel2moodle-0.3.7.dist-info}/entry_points.txt +0 -0
- {excel2moodle-0.3.5.dist-info → excel2moodle-0.3.7.dist-info}/licenses/LICENSE +0 -0
- {excel2moodle-0.3.5.dist-info → excel2moodle-0.3.7.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
"""Numerical question implementation."""
|
2
|
+
|
3
|
+
import lxml.etree as ET
|
4
|
+
|
5
|
+
from excel2moodle.core.globals import (
|
6
|
+
DFIndex,
|
7
|
+
XMLTags,
|
8
|
+
)
|
9
|
+
from excel2moodle.core.parser import QuestionParser
|
10
|
+
from excel2moodle.core.question import Question
|
11
|
+
|
12
|
+
|
13
|
+
class NFQuestion(Question):
|
14
|
+
def __init__(self, *args, **kwargs) -> None:
|
15
|
+
super().__init__(*args, **kwargs)
|
16
|
+
|
17
|
+
|
18
|
+
class NFQuestionParser(QuestionParser):
|
19
|
+
"""Subclass for parsing numeric questions."""
|
20
|
+
|
21
|
+
def __init__(self) -> None:
|
22
|
+
super().__init__()
|
23
|
+
self.genFeedbacks = [XMLTags.GENFEEDB]
|
24
|
+
|
25
|
+
def setAnswers(self) -> list[ET.Element]:
|
26
|
+
result = self.rawInput[DFIndex.RESULT]
|
27
|
+
ansEle: list[ET.Element] = []
|
28
|
+
tol = self.rawInput[DFIndex.TOLERANCE]
|
29
|
+
ansEle.append(self.getNumericAnsElement(result=result, tolerance=tol))
|
30
|
+
return ansEle
|
@@ -0,0 +1,92 @@
|
|
1
|
+
"""Numerical question multi implementation."""
|
2
|
+
|
3
|
+
import re
|
4
|
+
from typing import TYPE_CHECKING
|
5
|
+
|
6
|
+
import lxml.etree as ET
|
7
|
+
from asteval import Interpreter
|
8
|
+
|
9
|
+
from excel2moodle.core import stringHelpers
|
10
|
+
from excel2moodle.core.globals import (
|
11
|
+
DFIndex,
|
12
|
+
XMLTags,
|
13
|
+
)
|
14
|
+
from excel2moodle.core.parser import QuestionParser
|
15
|
+
from excel2moodle.core.question import Question
|
16
|
+
|
17
|
+
if TYPE_CHECKING:
|
18
|
+
import lxml.etree as ET
|
19
|
+
|
20
|
+
|
21
|
+
class NFMQuestion(Question):
|
22
|
+
def __init__(self, *args, **kwargs) -> None:
|
23
|
+
super().__init__(*args, **kwargs)
|
24
|
+
|
25
|
+
|
26
|
+
class NFMQuestionParser(QuestionParser):
|
27
|
+
def __init__(self) -> None:
|
28
|
+
super().__init__()
|
29
|
+
self.genFeedbacks = [XMLTags.GENFEEDB]
|
30
|
+
self.astEval = Interpreter()
|
31
|
+
|
32
|
+
def setAnswers(self) -> None:
|
33
|
+
equation = self.rawInput[DFIndex.RESULT]
|
34
|
+
bps = str(self.rawInput[DFIndex.BPOINTS])
|
35
|
+
ansElementsList: list[ET.Element] = []
|
36
|
+
varNames: list[str] = self._getVarsList(bps)
|
37
|
+
self.question.variables, number = self._getVariablesDict(varNames)
|
38
|
+
for n in range(number):
|
39
|
+
self._setupAstIntprt(self.question.variables, n)
|
40
|
+
result = self.astEval(equation)
|
41
|
+
if isinstance(result, float):
|
42
|
+
tol = self.rawInput[DFIndex.TOLERANCE]
|
43
|
+
ansElementsList.append(
|
44
|
+
self.getNumericAnsElement(result=round(result, 3), tolerance=tol),
|
45
|
+
)
|
46
|
+
self.question.answerVariants = ansElementsList
|
47
|
+
self.setVariants(len(ansElementsList))
|
48
|
+
|
49
|
+
def setVariants(self, number: int) -> None:
|
50
|
+
self.question.variants = number
|
51
|
+
mvar = self.question.category.maxVariants
|
52
|
+
if mvar is None:
|
53
|
+
self.question.category.maxVariants = number
|
54
|
+
else:
|
55
|
+
self.question.category.maxVariants = min(number, mvar)
|
56
|
+
|
57
|
+
def _setupAstIntprt(self, var: dict[str, list[float | int]], index: int) -> None:
|
58
|
+
"""Setup the asteval Interpreter with the variables."""
|
59
|
+
for name, value in var.items():
|
60
|
+
self.astEval.symtable[name] = value[index]
|
61
|
+
|
62
|
+
def _getVariablesDict(self, keyList: list) -> tuple[dict[str, list[float]], int]:
|
63
|
+
"""Liest alle Variablen-Listen deren Name in ``keyList`` ist aus dem DataFrame im Column[index]."""
|
64
|
+
dic: dict = {}
|
65
|
+
num: int = 0
|
66
|
+
for k in keyList:
|
67
|
+
val = self.rawInput[k]
|
68
|
+
if isinstance(val, str):
|
69
|
+
li = stringHelpers.getListFromStr(val)
|
70
|
+
num = len(li)
|
71
|
+
variables: list[float] = [float(i.replace(",", ".")) for i in li]
|
72
|
+
dic[str(k)] = variables
|
73
|
+
else:
|
74
|
+
dic[str(k)] = [str(val)]
|
75
|
+
num = 1
|
76
|
+
self.logger.debug("The following variables were provided: %s", dic)
|
77
|
+
return dic, num
|
78
|
+
|
79
|
+
@staticmethod
|
80
|
+
def _getVarsList(bps: str | list[str]) -> list:
|
81
|
+
"""Durchsucht den bulletPoints String nach den Variablen ``{var}``.
|
82
|
+
|
83
|
+
It only finds variables after the ``=`` sign, to not catch LaTex.
|
84
|
+
"""
|
85
|
+
varNames = []
|
86
|
+
regexFinder = re.compile(r"=\s*\{(\w+)\}")
|
87
|
+
if isinstance(bps, list):
|
88
|
+
for _p in bps:
|
89
|
+
varNames.extend(regexFinder.findall(str(_p)))
|
90
|
+
else:
|
91
|
+
varNames = regexFinder.findall(str(bps))
|
92
|
+
return varNames
|
excel2moodle/ui/appUi.py
CHANGED
@@ -8,13 +8,13 @@ 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
|
14
|
-
|
15
|
-
# from excel2moodle.logger import LogWindowHandler
|
13
|
+
from excel2moodle import mainLogger
|
14
|
+
from excel2moodle.core.category import Category
|
16
15
|
from excel2moodle.core.dataStructure import QuestionDB
|
17
16
|
from excel2moodle.extra import equationVerification as eqVerif
|
17
|
+
from excel2moodle.logger import LogWindowHandler
|
18
18
|
from excel2moodle.ui import dialogs
|
19
19
|
from excel2moodle.ui.settings import Settings, SettingsKey
|
20
20
|
from excel2moodle.ui.treewidget import CategoryItem, QuestionItem
|
@@ -24,6 +24,9 @@ from .windowEquationChecker import Ui_EquationChecker
|
|
24
24
|
|
25
25
|
logger = logging.getLogger(__name__)
|
26
26
|
|
27
|
+
loggerSignal = LogWindowHandler()
|
28
|
+
mainLogger.addHandler(loggerSignal)
|
29
|
+
|
27
30
|
|
28
31
|
class MainWindow(QtWidgets.QMainWindow):
|
29
32
|
def __init__(self, settings: Settings, testDB: QuestionDB) -> None:
|
@@ -35,7 +38,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
35
38
|
self.testDB = testDB
|
36
39
|
self.ui = Ui_MoodleTestGenerator()
|
37
40
|
self.ui.setupUi(self)
|
38
|
-
|
41
|
+
self.connectEvents()
|
42
|
+
logger.info("Settings are stored under: %s", self.settings.fileName())
|
39
43
|
self.ui.treeWidget.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
|
40
44
|
self.ui.treeWidget.header().setSectionResizeMode(
|
41
45
|
QtWidgets.QHeaderView.ResizeToContents,
|
@@ -43,33 +47,33 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
43
47
|
self.ui.checkBoxIncludeCategories.setChecked(
|
44
48
|
self.settings.get(SettingsKey.INCLUDEINCATS),
|
45
49
|
)
|
46
|
-
|
47
|
-
|
48
|
-
|
50
|
+
self.ui.spinBoxDefaultQVariant.setValue(
|
51
|
+
self.settings.get(SettingsKey.QUESTIONVARIANT)
|
52
|
+
)
|
49
53
|
self.ui.pointCounter.setReadOnly(True)
|
50
54
|
self.ui.questionCounter.setReadOnly(True)
|
51
55
|
self.setStatus(
|
52
|
-
"Wählen Sie
|
56
|
+
"Wählen Sie eine Excel Tabelle mit den Fragen aus",
|
53
57
|
)
|
54
58
|
try:
|
55
59
|
self.resize(self.settings.value("windowSize"))
|
56
60
|
self.move(self.settings.value("windowPosition"))
|
57
61
|
except Exception:
|
58
62
|
pass
|
59
|
-
self.
|
63
|
+
self.threadPool = QThreadPool()
|
60
64
|
|
61
65
|
def connectEvents(self) -> None:
|
62
|
-
self.ui.treeWidget.
|
66
|
+
self.ui.treeWidget.itemSelectionChanged.connect(self.onSelectionChanged)
|
63
67
|
self.ui.checkBoxQuestionListSelectAll.checkStateChanged.connect(
|
64
68
|
self.toggleQuestionSelectionState,
|
65
69
|
)
|
66
|
-
|
70
|
+
loggerSignal.emitter.signal.connect(self.updateLog)
|
67
71
|
self.ui.actionEquationChecker.triggered.connect(self.openEqCheckerDlg)
|
68
72
|
self.ui.checkBoxIncludeCategories.checkStateChanged.connect(
|
69
73
|
self.setIncludeCategoriesSetting,
|
70
74
|
)
|
71
|
-
self.ui.actionParseAll.triggered.connect(self.
|
72
|
-
self.testDB.
|
75
|
+
self.ui.actionParseAll.triggered.connect(self.parseSpreadsheetAll)
|
76
|
+
self.testDB.signals.categoryQuestionsReady.connect(self.treeRefreshCategory)
|
73
77
|
self.ui.buttonSpreadSheet.clicked.connect(self.onButSpreadsheet)
|
74
78
|
self.ui.buttonTestGen.clicked.connect(self.onButGenTest)
|
75
79
|
self.ui.actionPreviewQ.triggered.connect(self.openPreviewQuestionDlg)
|
@@ -81,6 +85,18 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
81
85
|
def setQVariantDefault(self, value: int) -> None:
|
82
86
|
self.settings.set(SettingsKey.QUESTIONVARIANT, value)
|
83
87
|
|
88
|
+
@QtCore.Slot()
|
89
|
+
def parseSpreadsheetAll(self) -> None:
|
90
|
+
"""Event triggered by the *Tools/Parse all Questions* Event.
|
91
|
+
|
92
|
+
It parses all the Questions found in the spreadsheet
|
93
|
+
and then refreshes the list of questions.
|
94
|
+
If successful it prints out a list of all exported Questions
|
95
|
+
"""
|
96
|
+
self.ui.treeWidget.clear()
|
97
|
+
process = ParseAllThread(self.testDB, self)
|
98
|
+
self.threadPool.start(process)
|
99
|
+
|
84
100
|
@QtCore.Slot(Path)
|
85
101
|
def onSheetPathChanged(self, sheet: Path) -> None:
|
86
102
|
logger.debug("Slot, new Spreadsheet triggered")
|
@@ -90,9 +106,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
90
106
|
svgFolder.resolve()
|
91
107
|
self.settings.set(SettingsKey.PICTUREFOLDER, svgFolder)
|
92
108
|
self.ui.buttonSpreadSheet.setText(str(sheet.name))
|
93
|
-
self.
|
94
|
-
self.testDB.parseAll()
|
95
|
-
self.refreshList()
|
109
|
+
self.parseSpreadsheetAll()
|
96
110
|
|
97
111
|
def updateLog(self, log) -> None:
|
98
112
|
self.ui.loggerWindow.append(log)
|
@@ -116,8 +130,6 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
116
130
|
for q in selection:
|
117
131
|
questions += 1
|
118
132
|
count += q.getQuestion().points
|
119
|
-
|
120
|
-
logger.info("%s questions are selected with %s points", questions, count)
|
121
133
|
self.ui.pointCounter.setValue(count)
|
122
134
|
self.ui.questionCounter.setValue(questions)
|
123
135
|
|
@@ -154,39 +166,22 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
154
166
|
filter=self.tr("Spreadsheet(*.xlsx *.ods)"),
|
155
167
|
selectedFilter=("*.ods"),
|
156
168
|
)
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
"""Event triggered by the *Tools/Parse all Questions* Event.
|
165
|
-
|
166
|
-
It parses all the Questions found in the spreadsheet
|
167
|
-
and then refreshes the list of questions.
|
168
|
-
If successful it prints out a list of all exported Questions
|
169
|
-
"""
|
170
|
-
self.testDB.readSpreadsheetData(self.spreadSheetPath)
|
171
|
-
self.testDB.parseAll()
|
172
|
-
self.setStatus("[OK] Alle Fragen wurden erfolgreich in XML-Dateien umgewandelt")
|
173
|
-
self.refreshList()
|
174
|
-
|
175
|
-
def refreshList(self) -> None:
|
176
|
-
"""Refresh the question overview in the main window.
|
169
|
+
path = Path(file[0]).resolve(strict=True)
|
170
|
+
if path.is_file():
|
171
|
+
self.excelPath = path
|
172
|
+
self.settings.setSpreadsheet(self.excelPath)
|
173
|
+
self.setStatus("[OK] Excel Tabelle wurde eingelesen")
|
174
|
+
else:
|
175
|
+
self.setStatus("[ERROR] keine Tabelle angegeben")
|
177
176
|
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
for
|
184
|
-
catItem
|
185
|
-
|
186
|
-
for q in cat.questions.values():
|
187
|
-
QuestionItem(catItem, q)
|
188
|
-
self.setStatus("[OK] Fragen Liste wurde aktualisiert")
|
189
|
-
self.ui.buttonTestGen.setEnabled(True)
|
177
|
+
@QtCore.Slot(Category)
|
178
|
+
def treeRefreshCategory(self, cat: Category) -> None:
|
179
|
+
"""Append Category with its Questions to the treewidget."""
|
180
|
+
catItem = CategoryItem(self.ui.treeWidget, cat)
|
181
|
+
catItem.setFlags(catItem.flags() & ~Qt.ItemIsSelectable)
|
182
|
+
for q in cat.questions.values():
|
183
|
+
QuestionItem(catItem, q)
|
184
|
+
self.ui.treeWidget.sortItems(0, Qt.SortOrder.AscendingOrder)
|
190
185
|
|
191
186
|
@QtCore.Slot()
|
192
187
|
def openPreviewQuestionDlg(self) -> None:
|
@@ -214,6 +209,26 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
214
209
|
about.exec()
|
215
210
|
|
216
211
|
|
212
|
+
class ParseAllThread(QRunnable):
|
213
|
+
"""Parse the whole Spreadsheet.
|
214
|
+
Start by reading the spreadsheet asynchron.
|
215
|
+
When finished parse all Categories subsequently.
|
216
|
+
"""
|
217
|
+
|
218
|
+
def __init__(self, questionDB: QuestionDB, mainApp: MainWindow) -> None:
|
219
|
+
super().__init__()
|
220
|
+
self.testDB = questionDB
|
221
|
+
self.mainApp = mainApp
|
222
|
+
|
223
|
+
@QtCore.Slot()
|
224
|
+
def run(self) -> None:
|
225
|
+
self.testDB.readCategoriesMetadata(self.mainApp.spreadSheetPath)
|
226
|
+
self.testDB.asyncInitAllCategories(self.mainApp.spreadSheetPath)
|
227
|
+
self.mainApp.setStatus("[OK] Tabellen wurde eingelesen")
|
228
|
+
self.testDB.parseAllQuestions()
|
229
|
+
self.mainApp.ui.buttonTestGen.setEnabled(True)
|
230
|
+
|
231
|
+
|
217
232
|
class EqCheckerWindow(QtWidgets.QWidget):
|
218
233
|
def __init__(self) -> None:
|
219
234
|
super().__init__()
|
excel2moodle/ui/dialogs.py
CHANGED
@@ -81,21 +81,22 @@ class QuestinoPreviewDialog(QtWidgets.QDialog):
|
|
81
81
|
def setAnswers(self) -> None:
|
82
82
|
if self.question.qtype == "NFM":
|
83
83
|
for i, ans in enumerate(self.question.answerVariants):
|
84
|
-
|
85
|
-
text = QtWidgets.QLineEdit(
|
84
|
+
t = ans.find("text").text
|
85
|
+
text = QtWidgets.QLineEdit(t, self)
|
86
86
|
self.ui.answersFormLayout.addRow(f"Answer {i + 1}", text)
|
87
87
|
|
88
88
|
elif self.question.qtype == "NF":
|
89
89
|
ans = self.question.element.find(XMLTags.ANSWER)
|
90
|
-
|
91
|
-
text = QtWidgets.QLineEdit(
|
90
|
+
t = ans.find("text").text
|
91
|
+
text = QtWidgets.QLineEdit(t, self)
|
92
92
|
self.ui.answersFormLayout.addRow("Result", text)
|
93
93
|
|
94
94
|
elif self.question.qtype == "MC":
|
95
95
|
for i, ans in enumerate(self.question.element.findall(XMLTags.ANSWER)):
|
96
|
-
|
96
|
+
pEle = ans.find("text").text
|
97
|
+
t = ET.fromstring(pEle).text
|
97
98
|
frac = ans.get("fraction")
|
98
|
-
text = QtWidgets.QLineEdit(
|
99
|
+
text = QtWidgets.QLineEdit(t, self)
|
99
100
|
self.ui.answersFormLayout.addRow(f"Fraction: {frac}", text)
|
100
101
|
|
101
102
|
|
@@ -107,7 +108,7 @@ class AboutDialog(QtWidgets.QMessageBox):
|
|
107
108
|
self.setStandardButtons(QtWidgets.QMessageBox.StandardButton.Close)
|
108
109
|
|
109
110
|
self.aboutMessage: str = f"""
|
110
|
-
<h1> About {e2mMetadata["name"]}</h1><br>
|
111
|
+
<h1> About {e2mMetadata["name"]} v {e2mMetadata["version"]}</h1><br>
|
111
112
|
<p style="text-align:center">
|
112
113
|
|
113
114
|
<b><a href="{e2mMetadata["homepage"]}">{e2mMetadata["name"]}</a> - {e2mMetadata["description"]}</b>
|
excel2moodle/ui/settings.py
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
import logging
|
4
4
|
from enum import StrEnum
|
5
5
|
from pathlib import Path
|
6
|
-
from typing import Literal, overload
|
6
|
+
from typing import ClassVar, Literal, overload
|
7
7
|
|
8
8
|
from PySide6.QtCore import QSettings, QTimer, Signal
|
9
9
|
|
@@ -11,50 +11,79 @@ logger = logging.getLogger(__name__)
|
|
11
11
|
|
12
12
|
|
13
13
|
class SettingsKey(StrEnum):
|
14
|
+
"""Settings Keys are needed to always acess the correct Value.
|
15
|
+
|
16
|
+
As the QSettings settings are accesed via strings, which could easily gotten wrong.
|
17
|
+
Further, this Enum defines, which type a setting has to be.
|
18
|
+
"""
|
19
|
+
|
14
20
|
def __new__(
|
15
21
|
cls,
|
16
22
|
key: str,
|
23
|
+
place: str,
|
17
24
|
typ: type,
|
18
25
|
default: str | float | Path | bool | None,
|
19
26
|
):
|
20
27
|
"""Define new settings class."""
|
21
28
|
obj = str.__new__(cls, key)
|
22
29
|
obj._value_ = key
|
30
|
+
obj._place_ = place
|
23
31
|
obj._default_ = default
|
24
32
|
obj._typ_ = typ
|
25
33
|
return obj
|
26
34
|
|
35
|
+
def __init__(
|
36
|
+
self, _, place: str, typ: type, default: str | float | Path | None
|
37
|
+
) -> None:
|
38
|
+
self._typ_ = typ
|
39
|
+
self._place_ = place
|
40
|
+
self._default_ = default
|
41
|
+
self._full_ = f"{self._place_}/{self._value_}"
|
42
|
+
|
27
43
|
@property
|
28
44
|
def default(self) -> str | int | float | Path | bool | None:
|
29
45
|
"""Get default value for the key."""
|
30
46
|
return self._default_
|
31
47
|
|
48
|
+
@property
|
49
|
+
def place(self) -> str:
|
50
|
+
return self._place_
|
51
|
+
|
52
|
+
@property
|
53
|
+
def full(self) -> str:
|
54
|
+
return self._full_
|
55
|
+
|
32
56
|
def typ(self) -> type:
|
33
57
|
"""Get default value for the key."""
|
34
58
|
return self._typ_
|
35
59
|
|
36
|
-
QUESTIONVARIANT = "
|
37
|
-
INCLUDEINCATS = "
|
38
|
-
PARSERNF_TOLERANCE = "parser/nf
|
39
|
-
PICTURESUBFOLDER = "core
|
40
|
-
PICTUREFOLDER = "
|
41
|
-
SPREADSHEETFOLDER = "
|
42
|
-
LOGLEVEL = "
|
43
|
-
LOGFILE = "
|
60
|
+
QUESTIONVARIANT = "defaultQuestionVariant", "testgen", int, 0
|
61
|
+
INCLUDEINCATS = "includeCats", "tesgen", bool, False
|
62
|
+
PARSERNF_TOLERANCE = "tolerance", "parser/nf", int, 1
|
63
|
+
PICTURESUBFOLDER = "imgSubFolder", "core", str, "Abbildungen"
|
64
|
+
PICTUREFOLDER = "pictureFolder", "core", Path, None
|
65
|
+
SPREADSHEETFOLDER = "spreadsheetFolder", "core", Path, None
|
66
|
+
LOGLEVEL = "loglevel", "core", str, "INFO"
|
67
|
+
LOGFILE = "logfile", "core", str, "excel2moodleLogFile.log"
|
68
|
+
CATEGORIESSHEET = "categoriesDataSheet", "core", str, "Kategorien"
|
69
|
+
VERSION = "version", "project", int, 1
|
70
|
+
POINTS = "points", "project", float, 1.0
|
44
71
|
|
45
72
|
|
46
73
|
class Settings(QSettings):
|
47
74
|
"""Settings for Excel2moodle."""
|
48
75
|
|
49
76
|
shPathChanged = Signal(Path)
|
77
|
+
localSettings: ClassVar[dict[str, str | float | Path]] = {}
|
50
78
|
|
51
79
|
def __init__(self) -> None:
|
52
80
|
"""Instantiate the settings."""
|
53
81
|
super().__init__("jbosse3", "excel2moodle")
|
54
|
-
|
82
|
+
logger.info("Settings are stored under: %s", self.fileName())
|
83
|
+
if self.contains(SettingsKey.SPREADSHEETFOLDER.full):
|
55
84
|
self.sheet = self.get(SettingsKey.SPREADSHEETFOLDER)
|
56
85
|
if self.sheet.is_file():
|
57
|
-
QTimer.singleShot(
|
86
|
+
QTimer.singleShot(300, self._emitSpreadsheetChanged)
|
58
87
|
|
59
88
|
def _emitSpreadsheetChanged(self) -> None:
|
60
89
|
self.shPathChanged.emit(self.sheet)
|
@@ -62,54 +91,89 @@ class Settings(QSettings):
|
|
62
91
|
@overload
|
63
92
|
def get(
|
64
93
|
self,
|
65
|
-
|
94
|
+
key: Literal[
|
95
|
+
SettingsKey.QUESTIONVARIANT,
|
96
|
+
SettingsKey.PARSERNF_TOLERANCE,
|
97
|
+
SettingsKey.VERSION,
|
98
|
+
SettingsKey.POINTS,
|
99
|
+
],
|
66
100
|
) -> int: ...
|
67
101
|
@overload
|
68
|
-
def get(self,
|
102
|
+
def get(self, key: Literal[SettingsKey.INCLUDEINCATS]) -> bool: ...
|
69
103
|
@overload
|
70
104
|
def get(
|
71
105
|
self,
|
72
|
-
|
73
|
-
SettingsKey.PICTURESUBFOLDER,
|
106
|
+
key: Literal[
|
107
|
+
SettingsKey.PICTURESUBFOLDER,
|
108
|
+
SettingsKey.LOGLEVEL,
|
109
|
+
SettingsKey.LOGFILE,
|
110
|
+
SettingsKey.CATEGORIESSHEET,
|
74
111
|
],
|
75
112
|
) -> str: ...
|
76
113
|
@overload
|
77
114
|
def get(
|
78
115
|
self,
|
79
|
-
|
116
|
+
key: Literal[SettingsKey.PICTUREFOLDER, SettingsKey.SPREADSHEETFOLDER],
|
80
117
|
) -> Path: ...
|
81
118
|
|
82
|
-
def get(self,
|
119
|
+
def get(self, key: SettingsKey):
|
83
120
|
"""Get the typesafe settings value."""
|
84
|
-
logger.debug("
|
85
|
-
if
|
86
|
-
|
87
|
-
|
121
|
+
logger.debug("LocalSettings: %s", self.localSettings)
|
122
|
+
if key in self.localSettings:
|
123
|
+
val = key.typ()(self.localSettings[key])
|
124
|
+
logger.debug("Returning project setting: %s = %s", key, val)
|
125
|
+
return val
|
126
|
+
if key.typ() is Path:
|
127
|
+
path: Path = self.value(key.full, defaultValue=key.default)
|
88
128
|
try:
|
89
129
|
path.resolve(strict=True)
|
90
130
|
except ValueError:
|
91
131
|
logger.warning(
|
92
|
-
f"The settingsvalue {
|
132
|
+
f"The settingsvalue {key} couldn't be fetched with correct typ",
|
93
133
|
)
|
94
|
-
return
|
134
|
+
return key.default
|
135
|
+
logger.debug("Returning path setting: %s = %s", key, path)
|
95
136
|
return path
|
96
|
-
raw = self.value(
|
97
|
-
logger.debug("read a settings Value: %s of type: %s",
|
137
|
+
raw = self.value(key.full, defaultValue=key.default, type=key.typ())
|
138
|
+
logger.debug("read a settings Value: %s of type: %s", key, key.typ())
|
98
139
|
try:
|
99
|
-
|
140
|
+
logger.debug("Returning global setting: %s = %s", key, raw)
|
141
|
+
return key.typ()(raw)
|
100
142
|
except (ValueError, TypeError):
|
101
143
|
logger.warning(
|
102
|
-
f"The settingsvalue {
|
144
|
+
f"The settingsvalue {key} couldn't be fetched with correct typ",
|
103
145
|
)
|
104
|
-
return
|
105
|
-
|
106
|
-
def set(
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
146
|
+
return key.default
|
147
|
+
|
148
|
+
def set(
|
149
|
+
self,
|
150
|
+
key: SettingsKey | str,
|
151
|
+
value: float | bool | Path | str,
|
152
|
+
local: bool = False,
|
153
|
+
) -> None:
|
154
|
+
"""Set the setting to value.
|
155
|
+
|
156
|
+
Parameters
|
157
|
+
----------
|
158
|
+
local
|
159
|
+
True saves local project specific settings.
|
160
|
+
Defaults to False
|
161
|
+
The local settings are meant to be set in the first sheet ``settings``
|
162
|
+
|
163
|
+
"""
|
164
|
+
if local:
|
165
|
+
if key in SettingsKey:
|
166
|
+
self.localSettings[key] = value
|
167
|
+
logger.info("Saved the project setting %s = %s", key, value)
|
168
|
+
else:
|
169
|
+
logger.warning("got invalid local Setting %s = %s", key, value)
|
170
|
+
return
|
171
|
+
if not local and isinstance(key, SettingsKey):
|
172
|
+
if not isinstance(value, key.typ()):
|
173
|
+
logger.error("trying to save setting with wrong type not possible")
|
174
|
+
return
|
175
|
+
self.setValue(key.full, value)
|
176
|
+
logger.info("Saved the global setting %s = %s", key, value)
|
113
177
|
|
114
178
|
def setSpreadsheet(self, sheet: Path) -> None:
|
115
179
|
"""Save spreadsheet path and emit the changed event."""
|
@@ -1,12 +1,12 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: excel2moodle
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.7
|
4
4
|
Summary: A package for converting questions from a spreadsheet, to valid moodle-xml
|
5
5
|
Author: Jakob Bosse
|
6
6
|
License-Expression: GPL-3.0-or-later
|
7
7
|
Project-URL: Repository, https://gitlab.com/jbosse3/excel2moodle.git
|
8
8
|
Project-URL: Documentation, https://jbosse3.gitlab.io/excel2moodle
|
9
|
-
Keywords: moodle,XML,teaching,question,converter
|
9
|
+
Keywords: moodle,XML,teaching,question,converter,open educational Ressource
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
11
11
|
Classifier: Operating System :: OS Independent
|
12
12
|
Requires-Python: >=3.10
|
@@ -0,0 +1,37 @@
|
|
1
|
+
excel2moodle/__init__.py,sha256=SUSyvbQCcfOtTr00HvHpjziNy-flEdWanVFCM9F9yX0,2223
|
2
|
+
excel2moodle/__main__.py,sha256=VGOlPEmC4bhNNIt8uuuG80DZ5unUvPjm1PoVf0yH1FA,756
|
3
|
+
excel2moodle/logger.py,sha256=WomQ_EAiqZ2oVHNReG4KdfGRMrJl3jC3LIr8cbvOlbY,3084
|
4
|
+
excel2moodle/core/__init__.py,sha256=H4Bt6u076RKb6BH5F58nHLQvYPDUoayosM_Onyr9yT0,398
|
5
|
+
excel2moodle/core/category.py,sha256=QIl8nh1ryvlVMtNy8hWRfhZZMgGOhQZ3rwDFqf4woa4,1809
|
6
|
+
excel2moodle/core/dataStructure.py,sha256=krsoifn6fUDBcbp8CHRAvL8Zfbm1nAXwjoESK_2Jeh8,13052
|
7
|
+
excel2moodle/core/etHelpers.py,sha256=i8DAx7YBxrQqzbXFsU-pIvYMPHSRhYci-JvuzY1MzeI,2299
|
8
|
+
excel2moodle/core/exceptions.py,sha256=VgbxrnoR9RRnmDYK2rbB_Bv00r7NLWET6FgddPwo3uw,748
|
9
|
+
excel2moodle/core/globals.py,sha256=cB5kstn57UYYiUJgLxNnqAGNAKVTolUjHPTt7XIA4G8,3294
|
10
|
+
excel2moodle/core/numericMultiQ.py,sha256=vr-gYogu2sf2a_Bhvhnu1ZSZFZXM32MfhJesjTkoOQM,2618
|
11
|
+
excel2moodle/core/parser.py,sha256=eVSHUihgW_Zk-mIffGL1kWJnHeSn9J76MfrgOzg0gNg,8991
|
12
|
+
excel2moodle/core/question.py,sha256=gF7dbbNgchoBWSti_kWlAHW17REYc5jpdtTjmNl2iU8,7157
|
13
|
+
excel2moodle/core/questionWriter.py,sha256=UiWbrtNSiQiYY_x3sF3nz6Ic0b_lL0uWHyeDNDtFvPM,10985
|
14
|
+
excel2moodle/core/stringHelpers.py,sha256=P7xr5LM2r7qfKzEPBVUSMMDsiVB6jpFAUuhOhGkOMQY,2284
|
15
|
+
excel2moodle/core/validator.py,sha256=-ogarWJ1tNrWg457SZtfDqwc8ToDAxgyLM6GW4rvI7g,4829
|
16
|
+
excel2moodle/extra/__init__.py,sha256=PM-id60HD21A3IcGC_fCYFihS8osBGZMIJCcN-ZRsIM,293
|
17
|
+
excel2moodle/extra/equationVerification.py,sha256=GLJl1r90d8AAiNy0H2hooZrg3D6aEwNfifYKAe3aGxM,3921
|
18
|
+
excel2moodle/question_types/__init__.py,sha256=ZYGg_tYop9c5yUfN8St6FddeaDxbyE2hneKKUFVzcGM,955
|
19
|
+
excel2moodle/question_types/mc.py,sha256=N0BREdzg6jcVq1CZMg_G8L4yAU1_1QKeDMaMrfmOBCU,3151
|
20
|
+
excel2moodle/question_types/nf.py,sha256=r1qSqYl31yUrmvzUxs2Dkkn3pBmFGN1aO7HQYIoOqkA,835
|
21
|
+
excel2moodle/question_types/nfm.py,sha256=gv3LvB6M9UI6U2PlpGerGlDVXzz4TZEudc8IsqdIru0,3301
|
22
|
+
excel2moodle/ui/__init__.py,sha256=4EdGtpzwH3rgw4xW9E5x9kdPQYwKbo9rehHRZTNxCrQ,44
|
23
|
+
excel2moodle/ui/appUi.py,sha256=sZ92bS1DBgoK3Chg1Nu_FQ2ry3Iu_ITKgpwqt4zLVAk,10582
|
24
|
+
excel2moodle/ui/dialogs.py,sha256=HkIe3CgSpBb_shbnNEfNJs82OVm1xUA53dsjDE8wwO0,5395
|
25
|
+
excel2moodle/ui/questionPreviewDialog.py,sha256=_rJvz1GM90aNnj3P6SugEezK7JW6m74ZALgkChohWLM,4980
|
26
|
+
excel2moodle/ui/settings.py,sha256=XRnCUgYrF9cz_bv91Zj965SJw3vJ0QlS0dgzzGeee-Y,6331
|
27
|
+
excel2moodle/ui/treewidget.py,sha256=mTRqmZHMZT3QTTvt5Gw9L-yl4KdhhulkF7O1KBwW-_E,2449
|
28
|
+
excel2moodle/ui/variantDialog.py,sha256=snVaF3_YAc7NWjMRg7NzbjL_PzNbOpt4eiqElkE46io,5414
|
29
|
+
excel2moodle/ui/windowDoc.py,sha256=IciZpwrLnGzIQV1aCdKQBg6km3oufHGs8havTFzNJyU,1055
|
30
|
+
excel2moodle/ui/windowEquationChecker.py,sha256=fLyal3sbJwpthWCAxLB5vbSFOX23JoivoYksNp3mZVY,7925
|
31
|
+
excel2moodle/ui/windowMain.py,sha256=FsaApCyWgngnWPb46R8_01z8q4XeWxRFVqewSIt742c,19027
|
32
|
+
excel2moodle-0.3.7.dist-info/licenses/LICENSE,sha256=ywQqe6Sitymkf2lV2NRcx_aGsaC-KbSl_EfEsRXmNRw,35135
|
33
|
+
excel2moodle-0.3.7.dist-info/METADATA,sha256=RmY9IFvitRlUaI6EugujfhXdZe8IFAsUdpJBmp_Oq-o,2972
|
34
|
+
excel2moodle-0.3.7.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
|
35
|
+
excel2moodle-0.3.7.dist-info/entry_points.txt,sha256=myfMLDThuGgWHMJDPPfILiZqo_7D3fhmDdJGqWOAjPw,60
|
36
|
+
excel2moodle-0.3.7.dist-info/top_level.txt,sha256=5V1xRUQ9o7UmOCmNoWCZPAuy5nXp3Qbzyqch8fUGT_c,13
|
37
|
+
excel2moodle-0.3.7.dist-info/RECORD,,
|